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.

Diferencias 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.

Creación de un proyecto básico con Redux

Necesitaremos instalar el siguiente módulos para poder usarlo:

npm i redux react-redux
  • src
    • store
      • index.js
      • user
        • actions
          • index.js
          • actions.js
    • components
      • Component1.js
      • Component2.js

App

Un provider es un componente que hace visibles ciertos datos o métodos para ser usados en sus nodos hijos.

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 Component1 from './components/Component1.js';
import Component2 from './components/Component2.js';
import { Provider } from "./store";



const App = () => (
    <Provider store={store}>
        <Component1/>
        <Component2/>
    </Provider>
);

export default App;

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/store/index.js
import { createStore, combineReducers } from "redux";
import { Provider as ReduxProvider, useSelector, useDispatch } from "react-redux";
import user from "./user";

const store = createStore(combineReducers({ user }));

const Provider = ({ children }) => <ReduxProvider store={store}>{children}</ReduxProvider>

export {
    Provider,
    useSelector,
    useDispatch
};

Actions: Type y Payload

La modificación de los estados del store se hace a través de unas funciones llamadas actions. Llamaremos a los actions a partir de los reducers, que será lo siguiente que estudiemos.

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.

./src/store/user/actions.js

export const setUserNameAction = 'SET_USER_NAME';

export const setUserName = (userName) => ({
    type: setUserNameAction,
    payload: userName,
});

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/store/user/index.js
import * as actions from './actions';

const initialState = {
    name: null
};

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actions.setUserNameAction :
            return { ...state, name: action.payload }
        default:
            return state;
    }
}

export const selectUserName = (store) => store.user.name;

export default reducer;

En el reducer.js exportamos el reducer y los selectores.

Los componentes

Dispatch es una función que acepta un objeto de tipo acción como parámetro (generalmente incluye un type y/o un payload). Esta se utiliza para causar cambios en la store a la que esté vinculado.

Al ejecutarse, mediante el type incluido en el objeto de tipo acción (creado a través de un actionCreator, por ejemplo ‘setUserName’), se lanzará el reducer vinculado con ese type y usando o no un payload generará una modificación en un estado concreto.

./src/components/Component1.js
import { useDispatch } from "../store";
import { setUserName } from "../store/user/actions";

const Component1 = () => {
    const dispatch = useDispatch();

    const onNameChange = ({ target }) => dispatch(setUserName(target.value));

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

export default  Component1;

./src/components/Component2.js

import { useSelector } from "../store";
import { selectUserName } from "../store/user";

const Component2 = () => {
    const userName = useSelector(selectUserName);

    return (
        <p>{userName}</p>
    );
}

export default  Component2;

Ejercicio: Añadiendo una propiedad a la entidad user

Añadiendo una nueva entidad

Ejercicios Redux

1. Añadir la propiedad age a la entidad user.

2. Añadir la entidad AppPreferences con la propiedad theme.

3. 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.

const App = () => (
    <Provider>
        <Form/>
        <PintarResultado/>
    </Provider>
);

export default App;

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:

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
antJS →