En el capítulo anterior de esta guía, estuvimos viendo una breve introducción a esta librería que nos va a ayudar a canalizar toda la información que pasa por nuestra aplicación. También, nos encargamos de empezar con los archivos principales de Redux.

En esta ocasión, vamos a proceder a trabajar con Redux de primera mano. Procederemos a crear las acciones, que como recodarás son el indicador en Redux de que algo ha cambiado. Son el primer elemento que se ejecutan y desembocan cambios en el estado de nuestra aplicación que el usuario verá reflejado posteriormente en cualquier vista.

Vamos a la teoría: Acciones

Acciones Síncronas

Las acciones asíncronas en Redux se caracterizan por ser un objeto de JavaScript, que retorna un tipo de acción, por el que será identificada y que definirá posteriormente en el reducer el modo en el que modificará el state así como la información que trae, en un espacio llamado payload.

Veamos un ejemplo de acción síncrona:

export function userLogedIn(loginInfo)
{
    return {
        type: 'USER_LOGEDIN',
        payload: loginInfo
    }
}

Acciones Asíncronas

Por defecto, la totalidad de las acciones en Redux deben de ser síncronas, tal como acabamos de explicar. Aunque, hay casos en los que sin duda necesitamos un tipo de acción que sea asíncrona, como por ejemplo pedir datos a un servidor que es en lo que se basa en gran parte nuestra aplicación.

Para ello, en la sección anterior instalamos el middleware de redux-thunk, y se lo aplicamos al store, por lo que a partir de ahora podremos crear acciones asíncronas sin problemas. Sólo debes tener algunas cosas en cuenta:

  • Las acciones asíncronas son funciones(tal como las síncronas) pero estas deben retornar otra función con dispatch y getState como parámetros.
    • dispatch es una función que nos ayudará a llamar a otras acciones dentro de esta
  • Una vez hagamos la llamada pertinente deberemos de llamar a otras acciones, para pasarles la información obtenida. Se recomienda crear dos acciones síncronas para cada asíncrona: una para cuando la petición se haga correctamente y otra para cuando esta falle.

Veamos un ejemplo de como sería una acción asíncrona siguiendo las recomendaciones:

// La acción asíncrona que creamos gracias a Redux Thunk
export function requestNews()
{
    return (dispatch, getState) => {
        fetch('https://openwebinars.net/news')
            .catch(err => dispatch(getNewsFailed(err)))
            .then(res => res.json())
            .then(posts => dispatch(getNewsSucceded(posts)))
            // dispatch se encarga de llamar a las acciones.
    }
}

// La acción que canalizará el resultado si este fue favorable
export function getNewsSucceded(posts)
{
    return {
        type: 'GET_NEWS_SUCCESS',
        payload: posts
    }
}

// La acción que canalizará el resultado si este falló en algún punto
export function getNewsFailed(error)
{
    return {
        type: 'GET_NEWS_FAILURE',
        payload: error
    }
}

Vamos a la práctica: Acciones para nuestro CRUD

Una vez tengas claro los conceptos, crearemos dentro del proyecto una carpeta redux y en esta otra llamada actions. En el archivo index.js localizaremos la totalidad de las acciones.

En esta ocasión hay que tener en cuenta que los tipos de las acciones los vamos a crear en la parte superior del archivo como constantes para luego, en el reducer exportarlas y que no haya ningún typo.

El archivo, teniendo en cuenta la totalidad de peticiones que se generan en nuestro CRUD tendría la siguiente estructura:

export const GET_POSTS = 'GET_POSTS'
export const GET_POSTS_SUCCESS = 'GET_POSTS_SUCCESS'
export const GET_POSTS_FAILURE = 'GET_POSTS_FAILURE'

export const GET_POST = 'GET_POST'
export const GET_POST_SUCCESS = 'GET_POST_SUCCESS'
export const GET_POST_FAILURE = 'GET_POST_FAILURE'

export const SAVE_POST = 'SAVE_POST'
export const SAVE_POST_SUCCESS = 'SAVE_POST_SUCCESS'
export const SAVE_POST_FAILURE = 'SAVE_POST_FAILURE'

export const UPDATE_POST = 'SAVE_POST'
export const UPDATE_POST_SUCCESS = 'SAVE_POST_SUCCESS'
export const UPDATE_POST_FAILURE = 'SAVE_POST_FAILURE'

export const DELETE_POST = 'DELETE_POST'
export const DELETE_POST_SUCCESS = 'DELETE_POST_SUCCESS'
export const DELETE_POST_FAILURE = 'DELETE_POST_FAILURE'

// Para llamar a todos los posts
export function getPosts()
{
    return (dispatch, getState) => {
        fetch('https://owcrud-api.now.sh/api/posts')
            .catch(err => dispatch(getPostsFailure(err)))
            .then(res => res.json())
            .then(posts => dispatch(getPostsSuccess(posts)))
    }
}

export function getPostsSuccess(posts)
{
    return {
        type: GET_POSTS_SUCCESS,
        payload: posts
    }
}

export function getPostsFailure(error)
{
    return {
        type: GET_POSTS_FAILURE,
        payload: error
    }
}

// Para llamar a un único post
export function getPost(id)
{
    return (dispatch, getState) => {
        fetch(`https://owcrud-api.now.sh/api/posts/${id}`)
            .catch(err => dispatch(getPostFailure(err)))
            .then(res => res.json())
            .then(post => dispatch(getPostSuccess(post)))
    }
}

export function getPostSuccess(post)
{
    return {
        type: GET_POST_SUCCESS,
        payload: post
    }
}

export function getPostFailure(error)
{
    return {
        type: GET_POST_FAILURE,
        payload: error
    }
}

// Para borrar un post
export function deletePost(id, i)
{
    return (dispatch, getState) => {
        fetch(`https://owcrud-api.now.sh/api/posts/${id}`, {
            method: 'DELETE'
        })
            .catch(err => dispatch(deletePostFailure(err)))
            .then(post => dispatch(deletePostSuccess(id, i)))
    }
}

export function deletePostSuccess(id, i)
{
    return {
        type: DELETE_POST_SUCCESS,
        payload: {
            index: i
        }
    }
}

export function deletePostFailure(error)
{
    return {
        type: DELETE_POST_FAILURE,
        payload: error
    }
}

// Para guardar un post
export function savePost(body)
{
    return (dispatch, getState) => {
        fetch('https://owcrud-api.now.sh/api/posts', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(body)
        })
        .catch(err => dispatch(savePostFailure(err)))
        .then(res => res.json())
        .then(post => dispatch(savePostSuccess(post)))
    }
}

export function savePostSuccess(post)
{
    return {
        type: SAVE_POST_SUCCESS,
        payload: post
    }
}

export function savePostFailure(error)
{
    return {
        type: SAVE_POST_FAILURE,
        payload: error
    }
}

// Para editar un post
export function updatePost(body)
{
    return (dispatch, getState) => {
        fetch('https://owcrud-api.now.sh/api/posts', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(body)
        })
        .catch(err => dispatch(savePostFailure(err)))
        .then(res => res.json())
        .then(post => dispatch(savePostSuccess(post)))
    }
}

export function updatePostSuccess(post)
{
    return {
        type: UPDATE_POST_SUCCESS,
        payload: post
    }
}

export function updatePostFailure(error)
{
    return {
        type: UPDATE_POST_FAILURE,
        payload: error
    }
}

Conclusiones finales

El uso de Redux nos va a ayudar gratamente a organizar nuestra aplicación y conocer todo lo que se realiza en ella. Por ejemplo, en esta ocasión hemos obtenido una visión clara de todas las peticiones que se realizan en nuestra aplicación, las cuales ahora tienen la consideración de acciones.

Incluso, podríamos llegar a decir que hemos implementado error handling pues al haber estado usando acciones asíncronas, hemos creado acciones que canalizarán las peticiones si hay algún error y este error se almacenaría, como veremos en pasos próximos, en el estado de la aplicación. Si lo deseáramos, podríamos trabajar en React con una vista de error sin ningún tipo de dificultad.

En el siguiente artículo crearemos el reducer, que se encargará de filtrar cada una de las acciones que hemos creado y, con los datos de esta modificará el state de nuestra aplicación.

Si te quedaste con alguna duda, no dudes en exponerla en los comentarios.