Redux en React

Permite definir estados globales.

No tiene sentido usar Redux y useState para una misma variable. Si una variable es global a nivel de aplicación, no tiene sentido hacerla global a nivel de componente. Estaríamos guardando la aplicación dos veces.

Diferencia entre Context API y Redux

  • Redux es una librería totalmente externa a react.
  • Redux usa middlewares de serie (es decir, permite ejecutar funciones cuando una variable ha cambiado). Esto es útil, por ejemplo, si queremos trackear con google analytics cuando el usuario cambia de url.
  • Con context API, al actualizarse una propiedad de un objeto, se actualizarán todos los componentes, mientras que con Redux, sólo se actualizarán los componentes que utilicen dicha propiedad. Por tanto, usaremos Context API con pequeños cambios en los estados globales y Redux para grandes aplicaciones con cambios constantes en los estados globales.

Necesitaremos instalar los siguientes módulos para poder usarlo:

npm i @reduxjs/toolkit
npm i react-redux # Para poder usar los hooks
npm i redux-logger # Para las trazas
  • src
    • redux
      • index.js
      • states
        • index.js
        • user
          • reducer.js
          • selecters.js
    • screens
      • login.js

Aquí empieza todo

Redux modificará la store, que es donde se encuentran las variables globales que afectan a toda la aplicación.

Para ello será necesario envolver todos los componentes que van a poder acceder a la store dentro de un provider que tendrá la store como prop.

./src/App.js
import { Provider } from "react-redux";
import { store } from "./redux";
import Login from './screens/login';

export default () => (
    <Provider store={store}>
        <Login />
    </Provider>
);

La store

El store un objeto que contiene todo el arbol de estados de la aplicación. Un estado es una propiedad del store.

El código que define la store es siempre igual, practicamente sólo cambian los middlewares que queremos utilizar (para mostrar trazas, para almacenar el estado global en el localStorage, etc).

En el store se registran los reducers, que como veremos más adelante serán los que modifiquen los estados.

./src/redux/index.js
import logger from 'redux-logger';
import { rootReducer } from "./states";
import { configureStore } from "@reduxjs/toolkit";

export const store = configureStore({
    reducer: rootReducer,
    middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), logger]
})

Actions: Type y Payload

La modificación de los estados del store no se hace directamente. Para efectuarla, redux define automáticamente a partir de los nombres asignados a los reducers unas funciones llamadas actions.

Estas funciones tienen una estructura específica. Deben devolver un objeto con las siguientes propiedades:

  • type: Esta propiedad es obligatoria y su valor debe ser un string. Es la propiedad que usará redux internamente en el reducer para determinar la propiedad o propiedades que hay que modificar.
  • payload: Esta propidad es opcional. Puede ser un valor de cualquier tipo (un string, un objeto, un array, etc). Es el contenido que queremos actualizar en la store. Por ejemplo, si queremos dar una persona de alta, sería un objeto con las propiedades de esa persona.

Los reducers

Los reducers materializarán los cambios en el estado global de la aplicación. Para ello toman siempre como parámetro de entrada el payload y modifican el state.

./src/redux/states/index.js
import { combineReducers } from "redux";
import user from './user/reducer';

export const rootReducer = combineReducers({
    user
});

En el reducer.js exportamos el reducer y las actions creators. Esta entidades son generadas por la función createSlice, que recibe como parámetro de entrada, el estado inicial del state y los reducers de la entidad con la que estamos trabajando (user).

./src/redux/states/user/reducer.js
import { createSlice } from "@reduxjs/toolkit";

const { actions, reducer } = createSlice({
    name: 'user',
    initialState: {
        name: '',
        isLoading: false
    },
    reducers: {
        setName: (state, { payload }) => {
            state.name = payload
        },
        setLoader: (state, { payload }) => {
            state.isLoading = payload
        }
    }
})

export default reducer;

export const { setName, setLoader } = actions;

El componente

Un componente que accede a un estado de Redux, debe recibirlo como prop. Si no lo hacemos, el código no dará error, pero tampoco funcionará.

El método dispatch recibe como parámetro de entrada un objeto de tipo action (con su correspondientes type y payload). Cuando llamamos a la función setName (que es el action creator que exportamos en reducer.js), esta nos devolverá este objeto action. Redux se encargará de pasar dicho objeto action a todos los reducers para ver con cual coincide el type y en función de eso, modificar el state.

./src/screens/login.js
import { useSelector, useDispatch } from "react-redux";
import { setName } from "../redux/states/user/reducer";
import { selectUserName } from "../redux/states/user/selecters";
export default () => {
    const name = useSelector(selectUserName); // useSelector me devuelve el estado de toda la aplicación. Llamaremos al método selectUserName para acceder concretamente a la propiedad de ese estado que nos interesa
    const dispatch = useDispatch();

    const onChange = ({ target }) => {
        dispatch(setName(target.value));
    }

    return (
        <div>
            <p>{name}</p>
            <input type="text" onChange={onChange} />
        </div>
    )
}

El método selectUserName lo hemos definido dentro de la carpeta del usuario:

./src/redux/states/user/selecters.js
export const selectUserName = (state) => state.user.name;

Ejercicios Redux

Modificaremos los ejercicios que vimos en la sección de recogida de datos:

  • Calcular grados Fahrenheit
  • Convertir euros en dólares
  • Calcular la superficie de un rectángulo.

Cada uno de estos ejercicios debe tener dos componentes. Uno en el que se introducirán los datos y otro en el que se pintarán los resultados. Utilizaremos redux para compartir del valor a través de los diferentes componentes.

export default () => (
    <div className="App">
        <Form/>
        <PintarResultado/>
    </div>
);

Ejercicio listado con Redux

Crear un componente que consulte un array con un listado de valores y los muestre por pantalla.

Al pulsar sobre un elemento del listado, debemos almacenar su id con Redux y utilizarla para pintar el título de ese elemento recuperándolo con Redux desde otro componente.

El componente del listado, quedaría de la siguiente forma:

import {connect} from "react-redux";
import { setActiveElement } from '../../store/elementoActivo/actions';

const Listado = ({ setActiveElement }) => {
    const elementos = [
        {id: 1, name: 'elemento1'},
        {id: 2, name: 'elemento2'},
        {id: 3, name: 'elemento3'},
        {id: 4, name: 'elemento4'},
        {id: 5, name: 'elemento5'},
        {id: 6, name: 'elemento6'}
    ];

    return (
        <ul>
            { elementos.map(({ id, name }) => (
                <li
                    onClick={() => setActiveElement(id)}
                    key={id}>{name}
                </li>
            ))}
        </ul>
    );
};

export default connect(null, { setActiveElement })(Listado)

Ejercicio listado de personas con Redux

El componente (Persona) debe recibir la prop del evento que se ha producido.

Listado
{ elementos.map(({ id, name }) => (
  <Persona
    onClick={() => setActiveElement(id)}
    key={id}
    nombre={name} />
  ))}
Persona
const Persona = ({ nombre, onClick }) => <div onClick={onClick}>{nombre}</div>
← Context API
React Developer Tool →

Aviso Legal | Política de privacidad