carlos Publicada 8 de septiembre de 2021 · 12 min read

Añade react-redux a tu app de expo

Una imagen decorativa para esta página.

Cualquier aplicación por pequeña que sea tiene que mantener un estado con los datos necesarios para que puedan ser mostrados al usuario. La mejor forma de integrarlo en nuestro proyecto de expo para que sea eficiente, fácil de usar y para evitar que se corrompa es usar react-redux.

expo

Figure explaining the redux data flow.

Redux es la mejor forma de mantener nuestro estado dentro de la aplicación pues nos permite acceder a él desde cualquier parte de la misma, simplifica la forma de acceder a la información y de mantener limpio nuestros datos. El funcionamiento de react-redux se basa en el flujo de datos.

Este es un esquema simplificado de cómo funciona Redux.

Partimos de que estamos viendo la interfaz en la que podemos modificar algún dato de nuestra aplicación, por ejemplo nuestro nombre de usuario.

Supongamos que tenemos un input de texto donde introducir nuestro nuevo nombre de usuario y un botón de guardar.

Cuando pulsemos el botón de guardar vamos a lanzar una acción, que es la forma mediante la cual podemos modificar el estado de nuestra aplicación. Esta acción es “recogida” por un reductor, que es el encargado de actualizar el estado.

Ahora nuestro perfil que muestra el nombre de usuario y que obtiene ese dato del estado de la aplicación se actualiza de manera “automática”.

Instalar react-redux

Instalamos los paquetes necesarios desde la raíz del proyecto.

yarn add react-redux
yarn add redux-thunk
yarn add redux-persist
yarn add @react-native-async-storage/async-storage

Añadiendo redux al proyecto

Lo primero que tenemos que hacer es añadir el provider de redux y de redux-persist a nuestro App.js, el primero es el componente que hace que el estado esté disponible en toda nuestra aplicación y el segundo el que se ocupa de cargar los datos cuando la aplicación se vuelve a abrir.

Los importamos

import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';

And wrap the main component in the Provider and PersistGate components:

export default function App() {
  return (
    <Provider>
        <PersistGate>
            <View style={styles.container}>
                <Navigator />
            </View>
        </PersistGate>
    </Provider>
  );
}

Ahora tenemos que configurar el store, que mantiene el estado de la aplicación. Para ello crearemos un nuevo directorio dentro de nuestro src, al que yo he llamado state, en el cual vamos añadir dos archivos:

  • store.js
  • reducers.js

Por otro lado vamos a crear un directorio llamado actions que va a albergar todas las acciones de la aplicación, y en el cual vamos a añadir un archivo user.js que contendrá las acciones relacionadas con los usuarios.

Es probable que tu aplicación llegue a tener un gran número de acciones por lo que es recomendable poder tenerlas clasificadas para que en el momento que algo no funcione el proceso de debugging sea lo más sencillo posible.

.
├── src
│   ├── state
│   │   ├── store.js
│   │   └── reducers.js
│   ├── actions
│   │   └── user.js
│   ├── navigation
│   ├── scenes
│   ├── components
│   └── atoms
└── App.js

Creando la primera acción

Dentro de src/actions/user.js vamos a crear nuestra primera acción encargada de actualizar el nombre de usuario que usa nuestra aplicación.

Las acciones que actualizan el estado de la aplicación tienen un tipo, que es el encargado de decirle al reductor que parte del estado tiene que actualizar.

Cremos el tipo, el cual es necesario exportar para poder ser utilizado luego por el reductor

export const UPDATE_USERNAME = 'UPDATE_USERNAME';

Y creamos la acción correspondiente.

export const updateUsername = (username) => ({ type: UPDATE_USERNAME, username });

Creando el primer reductor

En reducer.js introducimos el primer reductor:

const user  = (user = { username: ''}, action) => {
    switch (action.type) {
        case UPDATE_USERNAME:
            return { username: action.username }
        default:
            return user;
    }
}

Es un reductor que se va a encargar de mantener la información de nuestro usuario. Al cual le llegan dos parámetros:

  1. El primero es el estado por defecto cuando la aplicación se lanza por primera vez, en este caso la cadena vacía pues el usuario aún no ha introducido su nombre de usuario.
  2. El segundo es la acción que llega, ahora solo tenemos una pero en el futuro tendremos una para poder actualizar el correo electrónico, la contraseña, la foto de perfil…

Mediante el switch manejamos el caso en el que actualizamos el nombre de usuario y devolvemos el objeto usuario actualizado con el nuevo nombre. En caso de que nos llegue una acción que, por ejemplo, actualizase otra parte de la aplicación y que no queremos que cambie nada del usuario devolvemos el usuario tal como está (default case).

Recordar añadir el import de la acción:

import { UPDATE_USERNAME } from '@actions/users';

Si no sabes como configurar los alias puedes visitar nuestro post sobre Primeros pasos y buenas prácticas.

En caso de que te de pereza, la ruta debería ser la siguiente:

import { UPDATE_USERNAME } from '../actions/users';

Y ahora vamos a exportar nuestro reductor de la siguiente manera:

export default combineReducers({ user });

Imaginemos que en el futuro quieres mantener el estado sobre películas, tendrás un reductor de películas, con sus respectivas acciones, la mejor forma de mantener un control sobre el estado es separar en diferentes reductores cada tipo de objeto que contiene tu aplicación. Pero a la hora de exportarlo hay que combinarlos, de eso se encarga la función combineReducers.

import { combineReducers } from 'redux';
export default combineReducers({ user });

Configurando la store

Por último vamos a configurar el estado de nuestra aplicación y ya podríamos empezar a hacer algo un poco más visual.

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

import reducers from './reducers';
import { persistStore, persistReducer } from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import AsyncStorage from '@react-native-async-storage/async-storage';

const persistConfig = {
    key: 'root',
    storage: AsyncStorage,
    stateReconciler: autoMergeLevel2,
}

Creamos nuestro objeto de configuración de persistencia especificando que lo queremos guardar de manera asíncrona y que utilizaremos la función autoMergeLevel2 para reconciliar el estado en caso de que el sistema encuentre algun error.

Finalmente creamos nuestro estado y su correspondiente persistor y lo exportamos de la siguiente manera:

const initialState = {};

const persistedReducer = persistReducer(persistConfig, reducers);
const store = createStore(persistedReducer, initialState, applyMiddleware(thunk));
const persistor = persistStore(store);

export { store, persistor };

Solo nos falta añadirlo a nuestra aplicación, para ello importamos el store y el persistor desde App.js

import { store, persistor } from '@state/store';

y se los pasamoss como prop al Provider y al PersistGate.

export default function App() {
  return (
    <Provider store={store}>
        <PersistGate persistor={persistor} loading={null}>
            <View style={styles.container}>
                <Navigator />
            </View>
        </PersistGate>
    </Provider>
  );
}

Accediendo y modificando el estado

Vamos a modificar nuestro Profile.js. Si no sabes de lo que estoy hablando en el post anterior configuramos la estructura de nuestra aplicación con react-navigation.

Vamos a obtener nuestro nombre de usuario utilizando el hook de useSelector y a mostrarlo por pantalla.

Android screen showing the change name interface.
import React from 'react';
import { SafeAreaView, StyleSheet, Text } from 'react-native';
import { useSelector } from 'react-redux'


export const ProfileScreen = ({ }) => {

    const user = useSelector( state => state.user);

    return (
        <SafeAreaView style={styles.container}>
            <Text style={{color: 'white'}}>
                Welcome {user.username}!
            </Text>
        </SafeAreaView>
    )
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'blue',
        alignItems: 'center',
        justifyContent: 'center',
    },
});

Ahora mismo solo deberías estar viendo Welcome ! pues no hemos introducido aún ningún nombre de usuario.

Para poder modificarlo vamos a introducir un TextInput y un botón de guardar.

<View style={{marginTop: 40}}>
    <TextInput 
        style={{ height: 40, borderColor: 'white', borderWidth: 1, borderRadius: 12, padding: 8, color: 'white'}}
        onChangeText={text => setNewUsername(text)}
        value={newUsername}
        placeholder='New Username'
        placeholderTextColor='white'
    />
    <Button 
        style={{height: 40, width: 160, backgroundColor: 'white', borderRadius: 8, marginTop: 10}} 
        text='Save' 
    />
</View>

Ya que añadimos un TextInput explico por encima las props principales.

  • Desde la prop de style modificamos la forma del botón que contendrá al text input, en este caso con una altura de 40px, un borde color blanco y esquinas redondeadas. Pero tambien podemos cambiar el color del texto que se va a escribir mediante la clave color, en este caso también blanco.

  • Luego para guardar el nuevo nombre de usuario vamos a utilizar una variable de estado de la pantalla y especificamos con la prop de value que el valor del text input es newUsername y que cuando el texto cambie modifiquemos dicho valor con setNewUsername mediante la prop onChangeText.

Si no os acordais como se crea una variable con estado dentro de una pantalla la sintaxis es la siguiente:

const [newUsername, setNewUsername] = useState('');

especificando que el valor inicial es la cadena vacía.

Por ultimo añadimos que el valor que aparezca cuando no hay nada introducido sea New Username mediante la prop de placeholder y que el color de dicho texto sea blanco.

Finalmente vamos a añadir la lógica de actualización del nombre de usuario, para ello vamos a crear una nueva función saveUsername y a unirla con nuestro botón.

<Button 
    style={{ height: 40, width: 160, backgroundColor: 'white', borderRadius: 8, marginTop: 10 }} 
    text='Save' 
    onPress={ () => saveUsername()}
/>

const saveUsername = () => {
    // in case the username hasnt been updated
    if(newUsername === '') return;

    dispatch( updateUsername(newUsername) );
}

Importamos la acción desde nuestro repositorio de acciones y la ejecutamos mediante la función dispatch, que es la manera de llamar a las acciones en react-redux.

El código completo de Profile.js debería ser el siguiente:

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { SafeAreaView, StyleSheet, Text, TextInput, View } from 'react-native';
import { Button } from '@atoms';
import { updateUsername } from '@actions/users';



export const ProfileScreen = ({ }) => {
    const dispatch = useDispatch();

    const user = useSelector( state => state.user );
    const [newUsername, setNewUsername] = useState('');

    const saveUsername = () => {
        // in case the username hasnt been updated
        if(newUsername === '') return;

        dispatch( updateUsername(newUsername));
    }

    return (
        <SafeAreaView style={styles.container}>
            <Text style={{color: 'white'}}>Welcome {user.username}</Text>

            <View style={{marginTop: 40}}>
                <TextInput 
                    style={{ height: 40, borderColor: 'white', borderWidth: 1, borderRadius: 12, padding: 8, color: 'white'}}
                    onChangeText={text => setNewUsername(text)}
                    value={newUsername}
                    placeholder='New Username'
                    placeholderTextColor='white'
                />
                <Button 
                    style={{ height: 40, width: 160, backgroundColor: 'white', borderRadius: 8, marginTop: 10 }} 
                    text='Save' 
                    onPress={ () => saveUsername()}
                />
            </View>
        </SafeAreaView>
    )
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'blue',
        alignItems: 'center',
        justifyContent: 'center',
    },
});

Y si has copiado bien el código y seguidos todos los pasos debería estar funcionando como se muestra en el video. A partir de aquí ya solo es añadir los objetos que necesites en tu aplicación.

Espero que te haya servido y nos vemos en el siguiente!

Da el primer paso para hacer realidad tu proyecto.

Get in touch