Como recordarás, en el artículo anterior comenzamos a profundizar con Redux y le añadimos a nuestro CRUD las acciones que serán necesarias para trabajar con el estado. En esta ocasión, vamos a proceder a trabajar con otro concepto de Redux, el reducer.

El reducer no es más que una instrucción sobre la que va a trabajar Redux y en la que vamos a indicarle en base al payload de una acción, que cuando esta ocurra el cambio que debe de hacerse en el estado de nuestra aplicación.

Imports y estado inicial

Para comenzar, en el archivo donde crearemos el reducer, localizado dentro de la carpeta redux(/redux/reducers/index.js) importaremos las constantes que creamos en el artículo anterior con el fin de que estas muestren el nombre de las acciones y no las escribamos nosotros ya que podríamos generar algún que otro typo innecesario.

import { GET_POSTS, GET_POSTS_SUCCESS, GET_POSTS_FAILURE,
        GET_POST, GET_POST_SUCCESS, GET_POST_FAILURE,
        SAVE_POST, SAVE_POST_SUCCESS, SAVE_POST_FAILURE,
        UPDATE_POST, UPDATE_POST_SUCCESS, UPDATE_POST_FAILURE,
        DELETE_POST, DELETE_POST_SUCCESS, DELETE_POST_FAILURE } from '../actions'

Una vez las tengamos importadas, crearemos una constante en la que definiremos nuestro estado inicial. Este será el que mostrará Redux por defecto y sobre este se realizarán los cambios, por ejemplo, la carga de los datos.

No hay una estructura a seguir por defecto aunque personalmente me gusta estructurarlo de manera que quede muy clara. Para ello, dentro de INITIAL_STATE he creado dos objetos:

  • El objeto posts, que llevará sobre este la totalidad de los posts. Este será de ayuda en vistas como la principal.
  • El objeto currentPost, que llevará el post con el que se esté trabajando en ese preciso momento o el último que haya sido requerido en este caso. Será de ayuda en vistas como la de post individual.

Cada uno de estos objetos, y aquí es dónde viene lo importante, sigue una estructura similar que te recomiendo siempre que trabajes con librerías para gestionar el estado:

  • El array data en el que será almacenado la totalidad de los datos que vengan del servidor atendiendo a las diferentes acciones.

  • El array error, definido previamente como null. Este nos permitirá alojar cualquier tipo de error que haya en una ejecución de acción(e.g.: el servidor al que se le está solicitando la información no existe). De esta manera podemos mediante estructuras if no enseñarle la vista al usuario o mostrar una vista predeterminada de error.

  • El booleano de loading que estará en activo mientras no haya datos en el array de data y nos servirá para mostrar la vista que requiere de los datos sólo cuando estos estén listos, ahorrándonos así un error e incluso para mostrar una vista diferente mientras los datos cargan.

Nuestro estado inicial quedaría conformado de la siguiente manera:

export const INITIAL_STATE = {
    posts: { data: [], error: null, loading: true },
    currentPost: { data: [], error: null, loading: true }
}

Manos a la masa: Trabajando con el reducer

Una vez tengamos estos elementos listos ya podremos comenzar a trabajar con el reducer, que no será más que una función, conformada por una estructura switch.

Los casos de esta estructura switch coincidirán con los type de las acciones(que hemos importado previamente).

La función que te comentaba hace un momento lleva como parámetros el estado, que puede ser uno que coloque Redux o el estado inicial y la acción que se esté despachando en el momento, de manera que el switch será de action.type.

En cada uno de los casos iremos modificando el estado como mejor nos convenga. Por ejemplo, en el caso de que la petición de todos los posts haya sido satisfactoria, editaremos state.posts.data y lo cambiaremos por el payload que traiga la acción.

En otros casos, como por ejemplo, la acción satisfactoria de eliminar un post, tendremos que cambiar algo la dinámica y eliminar dicho post del array anteriormente mencionado, con lo que iremos reforzando los métodos de JavaScript más básicos.

Finalmente, me gustaría destacar que también podemos incluir en el switch los tipos de las acciones asíncronas, y en este caso colocar true en el booleano de loading para poder informarle a nuestra aplicación de que el estado está proximo a ser modificado.

En mi caso, atendiendo a todos los conceptos explicados, resultó el siguiente reducer:

import { GET_POSTS, GET_POSTS_SUCCESS, GET_POSTS_FAILURE,
        GET_POST, GET_POST_SUCCESS, GET_POST_FAILURE,
        SAVE_POST, SAVE_POST_SUCCESS, SAVE_POST_FAILURE,
        UPDATE_POST, UPDATE_POST_SUCCESS, UPDATE_POST_FAILURE,
        DELETE_POST, DELETE_POST_SUCCESS, DELETE_POST_FAILURE } from '../actions'

export const INITIAL_STATE = {
    posts: { data: [], error: null, loading: true },
    currentPost: { data: [], error: null, loading: true }
}

export default function Reducer(state=INITIAL_STATE, action)
{
    let error
    switch (action.type) {
        case GET_POSTS:
            return {...state, posts: { data: [], error: null, loading: true }}
        case GET_POSTS_SUCCESS:
            return {...state, posts: { data: action.payload, error: null, loading: false }}
        case GET_POSTS_FAILURE:
            error = action.payload.error || { message: action.payload.message }
            return {...state, posts: { data: [], error, loading: false }}
        case GET_POST:
            return {...state, currentPost: { data: [], error: null, loading: true }}
        case GET_POST_SUCCESS:
            return {...state, currentPost: { data: action.payload, error: null, loading: false }}
        case GET_POST_FAILURE:
            error = action.payload.error || { message: action.payload.message }
            return {...state, currentPost: { data: [], error, loading: false }}
        case SAVE_POST:
            return {...state, posts: {...state.posts, loading: true }}
        case SAVE_POST_SUCCESS:
            state.posts.data.push(action.payload.data)
            return {...state, posts: { data: state.posts.data, error: null, loading: false }}
        case SAVE_POST_FAILURE:
            error = action.payload.error || { message: action.payload.message }
            return {...state, posts: {...state.posts, error }}
        case UPDATE_POST:
            return {...state, currentPost: { data: [], error: null, loading: true }}
        case UPDATE_POST_SUCCESS:
            return {...state, currentPost: { data: action.payload, error: null, loading: false }}
        case UPDATE_POST_FAILURE:
            error = action.payload.error || { message: action.payload.message }
            return {...state, currentPost: { data: [], error, loading: false }}
        case DELETE_POST:
            return {...state, posts: {...state.posts, loading: true}}
        case DELETE_POST_SUCCESS:
            return {...state, posts: { data: state.posts.data.filter((el, index) => index != action.payload.index), error: null, loading: false }}
        case DELETE_POST_FAILURE:
            error = action.payload.error || { message: action.payload.message }
            state.posts.data.error = error
            return state
        default:
            return state
    }
}

Conclusiones

En este artículo hemos conseguido canalizar la información que viene de las acciones de Redux y colocarlas en el state. Hasta ahora hemos creado el mencionado state, le hemos aportado formas de ser modificado mediante acciones y le hemos estructurado cómo tienen que ser estas modificaciones con el reducer.

En el siguiente artículo, con un state ya funcionando vamos a conectar este con React.JS, más concretamente con Next, ya que nuestro FrontEnd está construido con este.
Una vez conectado modificaremos el código del Frontend y sustituiremos la petición de datos por las llamadas de acciones.

Si tienes alguna duda, no dudes en dejarla en los comentarios.