
Con este tutorial quisiera que, habiendo metido los pies al agua, te avientes el primer chapuzón a un stack que más comúnmente te encuentras allá afuera: React + Redux.
Aquí definirás los conceptos más importantes de este ecosistema: acciones, reductores, despachar, selectores, etc; aprenderás a diseñar la forma de tu estado y la importancia de moldearlo con base en tu dominio; y construirás una aplicación básica, pero con un caso de uso extremadamente común.
En el tutorial pasado expliqué React sin sufrimiento, pero sabemos que una de las mancuernas más usadas y más útiles de React es Redux.
Personalmente, imaginar React sin Redux es como imaginar un hot-dog sin ketchup: ¿Comible? Sí. ¿Sabroso? Pues… sí. ¿Puede ser mejor? ¡Por supuesto!
Prerrequisitos
Lo ideal es que ya hayas leído el tutorial de React sin sufrimiento, pero si llegaste directamente a este tutorial asumiré que conoces las bases de React y jsx, y por consiguiente, te sientes cómodo programando en JavaScript para navegador.
¿Qué es Redux?
Redux se define a sí mismo como “un contenedor predecible del estado de aplicaciones JavaScript”. Desmenuzando esta definición obtenemos las palabras clave: contenedor de estado.
Redux opera a nivel datos en tu aplicación. En una estructura Modelo-Vista-Controlador, Redux sería el Modelo —y React sería la Vista-Controlador. Es la única y celosa fuente de la verdad en tu aplicación, así que mi primer consejo al adentrarte en esta librería sería que no intentes engañar a Redux.
Si recuerdas, en React tenemos otras formas de guardar el estado, principalmente usando el hook useState. ¿Por qué nos convendría usar Redux entonces?
- Redux está diseñado para ser consistente. Las razones las entenderás cuando hablemos de los reductores, pero te adelanto que uno no cambia las variables arbitrariamente, sino que los cambios se dan únicamente por funciones puras.
- Redux hace predecible tu estado. No hay ambigüedades en el estado resultante cuando juegas con sus reglas. Dado cierta secuencia de eventos, el estado resultante será el mismo.
- Y lo de arriba nos lleva al tercer punto: Redux hace que el estado y sus mutaciones sean perfectamente testeables en pruebas unitarias y por lo tanto, candidatos perfectos para Test Driven Development.
Pensando en Acciones y Reductores
Pensemos en un ejemplo sencillo. Imagina que estás creando un menú en tu aplicación que puede estar abierto o cerrado y tienes un botón que lo abre y cierra:
Comencemos diseñando el state (“estado”). El state es representado por un objeto simple de javascript, lo que permite serializarlo, visualizarlo e inicializarlo fácilmente.
Sabemos que el menú puede estar o abierto o cerrado, esto significa que podemos asignarle un tipo booleano. En un objeto simple de javascript quedaría así:
{
menuOpen: false
}
El valor inicial sería false, ya que queremos que la aplicación inicie con el menú cerrado.
Ahora analicemos qué acciones se desprenden de la interacción del usuario:
En este caso, al dar click sobre el botón, el menú debe alternar entre su estado abierto y cerrado. A esta acción podemos llamarle toggle.
En Redux, las acciones también son representadas por objetos simples de javascript. El único requerimiento es que debe tener una propiedad “type”, que básicamente es el nombre de la acción:
{
type: 'menu/toggle'
}
El hecho de tomar una acción y mandarla a Redux se conoce como dispatch (“despachar”, en español). Dicha acción será enviada a un reducer (“reductor”). Ésta es la piedra angular de Redux: los reducers.
Un reducer es una función sencilla y determinista, esto es, que dado el mismo input, regresa el mismo output. Dicha función recibe dos inputs: un estado inicial y una acción; y ésta debe regresar el nuevo estado que debe adoptar el sistema.
Aquí vuelvo a mi comentario anterior: Redux no te permite cambiar el estado arbitrariamente, sino que debes escribir funciones deterministas que dado el estado actual y una acción puedan siempre producir el mismo output cada vez que se ejecuta.
Otra regla importantísima y vital que debes tatuarte en el cerebro y recordarla como un mantra de por vida es: No mutarás el estado. Nunca. Jamás.
Escribamos primero la prueba unitaria (en pseudo-código) para el reductor que necesitamos y veamos lo sencillo que es de implementar y testear:
Confirma que:
Dado un estado
{ menuOpen: false }
y una acción
{
type: 'menu/toggle'
}
Regresa
{ menuOpen: true }
Confirma que:
Dado un estado
{ menuOpen: true }
y una acción
{ type: 'menu/toggle' }
Regresa
{ menuOpen: false }
Ahora escribamos un código que satisfaga esas pruebas unitarias (ahora sí en javascript):
const toggleReducer = (state, action) => {
if (action.type === ‘menu/toggle’) {
return {
menuOpen: !state.menuOpen
}
}
return state // Ver nota
}
Nota: Si no fuera por el último return, si la acción no es menu/toggle estaríamos convirtiendo el estado en undefined.
Con esto nos podemos dar cuenta de lo poderoso que se vuelve Redux al moldear el estado, las acciones y los reductores tomando en cuenta el dominio de nuestra aplicación: nuestras variables dejan de servir al código y comienzan a servir a la aplicación; ya no hablamos sólo de una variable booleana, sino del estado abierto o cerrado del menú; ya no sólo es cambiar el valor de esa variable de verdadero a falso, sino que es avisarle al sistema que un evento de alternación del menú (menu/toggle) ha ocurrido y que se actúe en consecuencia.
Finalmente hay un término que es vital para las acciones y es aquello que nos permite pasar argumentos a los reductores: el payload. El payload es otra de las llaves que posee una acción pura.
Por ejemplo, si quisiéramos hacer una acción llamada setMenu que en vez de alternar el estado del menú especificara si éste debe estar abierto o cerrado, la acción para abrirlo podría verse así:
{
type: 'menu/set',
payload: true
}
Y el valor estará disponible en el reductor dentro de la acción.
Action Creators
Resultaría un fastidio escribir acciones directamente cada que tenemos que manejarlas y obviamente nos llevaría a muchos errores de dedo y dolores de cabeza. Por esta razón hay una pieza de código que automatiza la creación de acciones a las que se les conoce como action creators.
Un action creator no es más que una función que regresa un objeto puro de acción ya formateado. Retomando el ejemplo anterior de setMenu, su action creator se vería así:
const setMenu = newValue => ({
type: 'menu/set',
payload: newValue
})
Con esto podemos obtener un objeto de acción correcto y consistente en cualquier lado y con menos código:
setMenu(true)
// resulta en { type: 'menu/set', payload: true }
Basta de términos: Nuestro primer proyecto
Para nuestro hello world vamos a hacer algo más realista que una app de To Dos que va a involucrar mantener un estado conectado a la UI, interacciones del usuario y llamadas asíncronas.
La aplicación que vamos a hacer es un pequeño buscador de repositorios Github, aprovechando que su API es abierta.
Este es nuestro mock up:
El setup
Cuando trabajamos con la dupla de React con Redux, generalmente se trabaja con la librería react-redux. Se le llama binding porque es el hilo que une los conceptos de Redux al mundo de React.
Hay una forma manual de instalar react-redux en tu aplicación React por si deseas introducirlo a una aplicación existente.
Si ya entiendes React, no debe llevarte mucho tiempo siguiendo la guía oficial. Pero aquí no queremos que haya lágrimas, así que te mostraré la forma más sencilla (aunque algo opinionada) de comenzar a escribir tu aplicación con React y Redux con un template.
Te presento a tu nuevo mejor amigo: redux-toolkit. Es el template oficial del equipo de Redux creado para funcionar con create-react-app que seguro recuerdas del tutorial anterior.
Comencemos creando un nuevo proyecto.
$ npx create-react-app mi-app --template redux
Veremos como la carpeta de node_modules comienza a crecer y crecer, como en todos nuestros proyectos de javascript. Las dependencias que se instalan son todas las que React necesita, pero también Redux, react-redux y otras más que serán de muchísima utilidad para reducir la cantidad de código que tenemos que escribir. Si quieres saber exactamente qué contiene, mira este enlace.
Al terminar la instalación podemos entrar a la carpeta e iniciar la aplicación.
$ cd mi-app
$ yarn start
Una nueva ventana abrirá con la aplicación de ejemplo de redux-toolkit.
La estructura de archivos es similar a un proyecto de create-react-app, pero hay algunas diferencias dentro de la carpeta src/.
Lo primero que vas a notar es que existen dos carpetas en dicha carpeta: app y features. Si recuerdas el tutorial de React, esto se parece mucho a lo que hicimos en la sección “Preparándonos para extender”. Lo que nos falta aquí es agregar una carpeta llamada services y mover el componente principal App a la carpeta src/app (y actualizar los import).
Implementando un feature
Vamos a implementar nuestro buscador de repositorios comenzando con la UI. La parte de React te será fácil de entender.
Éste es el input de búsqueda. Recuerda considerar el estado de “ocupado”
import React from 'react';
import './SearchInput.css';
function SearchInput ({ busy, onSubmit, value, onChange }) {
const icon = busy
? <span role="img" aria-label="Loading">💤</span>
: <span role="img" aria-label="Search">🔍</span>
const hndKeyUp = e => {
if (e.keyCode === 13) { // ← Submit al presionar enter
onSubmit(value)
}
}
const hndClick = e => {
onSubmit(value)
}
const hndChange = e => {
onChange(e.target.value)
}
return (
<div className="SearchInput">
<input value={value} onChange={hndChange} onKeyUp={hndKeyUp} disabled={busy} />
<button type="submit" disabled={busy} onClick={hndClick}>{icon}</button>
</div>
);
}
export default SearchInput;
No hay sorpresas aquí, es un simple componente visual sin estado, pero que requiere un prop llamado onSubmit, que se ejecuta al dar click al botón de buscar o presionar enter.
Ahora necesitamos un componente que muestre los resultados.
import React from 'react';
import './SearchResults.css';
const SearchResults = ({ busy, results }) => {
const trim = text => text.substr(0, 200)
return busy ? (
<div className="SearchResults">
...
</div>
) : (
<div className="SearchResults">
{results.map(({ name, description, html_url: url, owner }) => (
<div key={name + owner.login} className="SearchResults-item">
<h2><a href={url}>{name}</a></h2>
<h3>Owner: <a href={owner.html_url}>{owner.login}</a></h3>
<p>{description && trim(description)}</p>
</div>
))}
</div>
);
}
export default SearchResults;
Recuerda considerar el estado “ocupado”. Aquí también puedes considerar el estado “vacío”, pero está fuera de los objetivos del tutorial, así que lo omitiremos por ahora.
Así se ve nuestro componente:
Ahora crearemos un componente nuevo cuya responsabilidad será mostrar ambos componentes y posteriormente conectarlos al estado. Llamémoslo SearchPage.
import React from 'react';
const SearchPage = () => {
const query = "Temp value";
const results = [];
const busy = false;
const hndChange = query => { /* NOOP */ }
const hndSubmit = query => { /* NOOP */ }
return (
<div>
<SearchInput onSubmit={hndSubmit} onChange={hndChange} value={query} busy={busy} />
<SearchResults results={results} busy={busy} />
</div>
);
}
export default SearchPage;
Por el momento, todos los valores son constantes y las funciones no hacen nada, pero no te preocupes por ahora.
Ahora importamos el componente a la App.
import SearchPage from 'features/search/SearchPage';
function App() {
return (
<div className="App">
<header className="App-header">
Githubby
</header>
<main className="App-main">
<SearchPage />
</main>
</div>
);
}
He aquí nuestra app:
Ahora diseñaremos el estado…
Este paso es muy importante para mantener la consistencia y predictibilidad. Es fácil perderse en la idea de que son simples variables o que podremos controlar qué acciones se ejecutan antes y qué acciones se ejecutan después, pero ahí es cuando los problemas (y los bugs) comienzan a surgir.
Diseñando el estado
Si pensamos en el feature como una máquina de estados finitos, podemos entender que, en general, pasamos por estos puntos:
Comenzamos con el estado “inicial” esperando la entrada del usuario. Al ejecutar el submit, entramos en el estado “ocupado” y al entrar en ese estado, la llamada a la API debe ejecutarse. Dependiendo del resultado de la llamada, la aplicación puede entrar en estado “resultados” o en estado “error”. Si el usuario lo desea, puede ejecutar una nueva búsqueda y regresar al estado “ocupado”, despachando una llamada a la API. Así ad infinitum.
Yo destilé el siguiente objeto simple de este diagrama:
{
query: '',
results: [],
busy: false,
error: null
}
La llave query es usada para guardar el valor del input de búsqueda.
La llave results es un array que representa los repositorios encontrados.
La llave busy es el flag que especifica que la aplicación está ocupada.
La llave error representa un error en la llamada. El consenso sería que si error es null, no existe error, pero si es cualquier otra cosa (eg: un string), la llamada anterior falló.
Ahora podemos ver el mismo diagrama y cómo cambia el estado. Recuerda que query es para almacenar el input:
En mi experiencia, el 90% (si no es que más) de tus features serán una variación de este diagrama, probablemente pasando por un par de estados más, como “validando” y “validado” vs “no-validado”.
De cualquier forma, este ejercicio de abstracción puedes realizarlo cuando el estado no sea tan claro. A mí me ha ayudado en múltiples ocasiones cuando múltiples features mandan acciones concurrentes y asíncronas y me ha evitado hacer hacks que al principio parecían inevitables.
Una vez sabiendo la forma del estado y qué transiciones y mutaciones deben ocurrir a partir de qué acciones. Escribir el código es más bien trivial.
Slices
Introduciré ahora algo que ha evolucionado a lo largo de estos años y que vale la pena conocer por obtener el contexto.
Cuando Redux vio la luz y los developers comenzamos a adoptarlo, poco a poco nos fuimos dando cuenta de un patrón que repetíamos en cada feature: siempre teníamos un reductor, un set de tipos de acciones y un set de creadores de acciones.
Fue entonces que Erik Rasmussen creó una propuesta llamada Ducks (“patos”). Los ducks —llamados así por redux que suena similar a ducks— son módulos listos para usarse en Redux. Si quieres leer la propuesta, la puedes encontrar en su github.
Posteriormente, Redux Toolkit hizo unas funciones que automatizan el acto de crear estos conjuntos de elementos y les llamó slices (rebanadas).
Entonces, ¿qué es un slice? Un slice es el conjunto del estado, las acciones y el reductor de un feature en particular.
Hagamos nuestro slice para nuestra búsqueda. Para esto creamos un archivo en src/features/search llamado searchSlice.js. En este archivo importamos la función createSlice desde @reduxjs/toolkit.
import { createSlice } from '@reduxjs/toolkit';
El slice se crea llamando a la función y pasando todos los detalles como un objeto de argumentos.
export const searchSlice = createSlice({
name: 'search', // name es el nombre único de tu feature
initialState: { // initialState recibe el estado inicial
query: '',
results: [],
busy: false,
error: null
},
reducers: {
// reducers recibe una lista de reductores con el nombre
// del action creator como llave
setQuery: (state, { payload }) => ({
...state,
query: payload
}),
// ...y el resto de los reductores
}
});
¿Recuerdas la regla de oro de Redux? Nunca, jamás, ni por tu vida mutes el estado en tus reductores. Quisiera que te detuvieras un momento y leas nuevamente con cuidado el reductor setQuery.
setQuery: (state, { payload }) => ({
...state,
query: payload
})
Es muy sencillo en teoría. Su objetivo es simplemente establecer el valor de query con el payload de la acción. Pero observa cómo tomamos el state y lo desdoblamos en un objeto nuevo y posteriormente sobreescribimos la llave query con nuestro payload. Este es el estilo que regularmente encuentras en reductores.
Pero aquí entra una ventaja enorme de usar Redux Toolkit en vez de usar Redux directo. Redux Toolkit incluye dentro de sus librerías Immer. Lo que esta librería nos permite es simular que editamos el estado, pero realmente estamos creando el siguiente estado inmutable. Tal vez estas palabras tengan más sentido al ver el ejemplo:
setQuery: (state, { payload }) => {
state.query = payload
},
Así es como queda nuestro reductor con la ayuda de Immer. La sintaxis es más directa y entendible y no tenemos que hacer contorsiones mentales para entender cómo se desdobla el estado y cómo queda al final el siguiente estado.
Con esto en mente, escribamos el resto de los reductores tomando en cuenta nuestro diagrama de estados.
load: state => {
state.results = [];
state.busy = true;
state.error = null;
},
success: (state, { payload }) => {
state.results = payload;
state.busy = false;
state.error = null;
},
error: (state, { payload }) => {
state.results = [];
state.busy = false;
state.error = payload;
},
Finalmente exportamos el reductor de tu slice para ser utilizado por el store.
export default searchSlice.reducer;
Ya que tenemos nuestro slice creado, entonces podemos conectarlo a Redux.
Abre src/app/store.js. Tu store es donde el estado se guarda y los reductores se conectan.
Importa tu nuevo slice e intégralo en la lista de reducers. Recuerda que la llave que elijas se convertirá en la raíz del estado de tu slice. De una vez puedes remover el slice de ejemplo:
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from 'features/counter/counterSlice';
import searchReducer from 'features/search/searchSlice';
export default configureStore({
reducer: {
search: searchReducer
},
});
Ahora podemos consumir nuestro slice en los componentes de nuestra aplicación.
El árbol de estado completo se vería así:
{
search: {
query: '',
results: [],
busy: false,
error: null
}
}
El Estado y React: Aquí está la Magia
Abramos src/features/search/SearchPage.js. ¿Recuerdas que teníamos los valores hard coded en constantes? Bueno, es momento de hacerlo dinámico.
Las últimas versiones de react-redux incluyen un hook llamado useSelector. Un selector es una función simple que recibe el estado y regresa los valores requeridos de un estado abstrayendo la ubicación en el árbol y dándole un nombre que tiene sentido en el dominio de la aplicación.
Los selectores los puedes agregar en el archivo de slice. Creemos uno para obtener la query.
export const selectQuery = state => state.search.query;
Así creamos el resto de los selectores para obtener el estado de “ocupado” y los resultados.
export const selectResults = state => state.search.results;
export const selectBusy = state => state.search.busy;
Ahora, de regreso a SearchPage.js podemos importarlos y usarlos.
import React from 'react';
import { useSelector } from 'react-redux';
import SearchInput from 'features/search/SearchInput';
import SearchResults from 'features/search/SearchResults';
import { selectQuery, selectResults, selectBusy } from 'features/search/searchSlice';
const SearchPage = () => {
const query = useSelector(selectQuery);
const results = useSelector(selectResults);
const busy = useSelector(selectBusy);
const hndChange = query => { /* NOOP */ }
const hndSubmit = query => { /* NOOP */ }
return (
<div>
<SearchInput onSubmit={hndSubmit} onChange={hndChange} value={query} busy={busy} />
<SearchResults results={results} busy={busy} />
</div>
);
}
export default SearchPage;
Con esto estamos efectivamente leyendo del estado. Ahora aprendamos a escribir en el estado.
Nuestro slice ya provee los action creators, sólo tenemos que exportarlos desde searchSlice.js:
export const { setQuery, load, success, error } = searchSlice.actions;
Otro de los hooks que provee react-redux es useDispatch. Este hook regresa la función dispatch que nos permitirá despachar acciones a Redux desde nuestros componentes. Su utilización es muy intuitivo. Importémoslo en SearchPage.js y usémoslo. También tenemos que importar los action creators:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import SearchInput from 'features/search/SearchInput';
import SearchResults from 'features/search/SearchResults';
import { selectQuery, setQuery, selectResults, selectBusy } from 'features/search/searchSlice';
const SearchPage = () => {
const query = useSelector(selectQuery);
const results = useSelector(selectResults);
const busy = useSelector(selectBusy);
const dispatch = useDispatch();
const hndChange = query => dispatch(setQuery(query))
const hndSubmit = query => { /* NOOP */ }
return (
<div>
<SearchInput onSubmit={hndSubmit} onChange={hndChange} value={query} busy={busy} />
<SearchResults results={results} busy={busy} />
</div>
);
}
export default SearchPage;
Ya puedes probar escribir en el input y verás que el valor ya se actualiza.
Una herramienta indispensable para desarrollar aplicaciones con Redux son los Redux DevTools. Los puedes encontrar para Firefox y Chrome.
Una vez instalado, puedes encontrar un nuevo tab en los DevTools llamada Redux. Ahí encontrarás las acciones despachadas y los estados resultantes. Son tus ojos al interior de Redux.
Así es como se vería escribir “testing 1 2 3” en el input. Observa cómo por cada tecla se despacha una acción de tipo “search/setQuery“. Mira cómo muta el estado:
También puedes ver una vista de la diferencia entre el estado anterior y el actual:
Asincronía. Llamando una API
Finalmente veamos un caso de uso tan común que seguramente será lo primero que hagas con este stack: llamar APIs.
La forma que prescribe Redux Toolkit es usando una construcción que llamamos thunk, donde un thunk es una función que envuelve una expresión para retrasar su evaluación. Muchas veces, lo que se envuelve es una promesa.
La librería que incluye Redux Toolkit es redux-thunk. La construcción de los thunks puede ser un poco compleja, pero vayamos poco a poco.
Nuestro ejemplo será un thunk que sume dos números pasado un segundo.
Comenzamos con un action creator para mantener consistencia. Éste recibirá los parámetros que necesita el thunk.
function addAsync (a, b) {
// … body
}
El action creator debe regresar una función que recibe dos funciones como parámetros dispatch y getState:
function addAsync (a, b) {
return (dispatch, getState) => {
// … body
}
}
Lo que será despachado es dicha función, que será ejecutada en ese momento. El hecho de que reciba como argumentos dispatch nos permite despachar otras acciones en la ejecución del thunk y getState nos da acceso al estado de la aplicación.
Ahora hagamos la parte asíncrona.
function addAsync (a, b) {
return (dispatch, getState) => {
setTimeout(() => {
dispatch(setValue(a + b))
}, 1000)
}
}
Con esta construcción, cuando nosotros despachamos el thunk haciendo algo como dispatch(addAsync(2, 2)), después de un segundo, la acción setValue va a ser despachada con el resultado.
Ahora vamos a un ejemplo más práctico usando la llamada a la API.
Como recordarás, nosotros tenemos un set de acciones llamadas load, success y error que corresponden al estado de “ocupado” y los de “resultados” y “error”. Lo que vamos a crear ahora es un thunk que despache estas acciones dependiendo del resultado de una llamada asíncrona. Ésta va a vivir en nuestro searchSlice.js.
Comenzamos con el action creator que va a requerir de parámetro un query string. Podríamos tomarlo directamente del state, pero usar un parámetro lo hace reutilizable y más testeable.
Aquí elegí usar la sintaxis de constante para exportarlo de forma consistente a lo demás que estoy exportando.
export const request = query => { /* thunk */ }
Luego agregamos la función que acepta dispatch. Como vamos a hacer una llamada a una función async, podemos crearla como async también:
export const request = query =>
async dispatch => {
/* body */
}
Ahora vamos a implementar la llamada a la API. Por ahora llamaremos un servicio mágico que luego implementaremos llamado GithubApi.
export const request = query => async dispatch => {
dispatch(load()) // Entramos a estado “ocupado”
try {
const results = await GithubApi.search(query) // llamamos la API
return dispatch(success(results)) // Entramos a estado "resultados" usando la respuesta de la API
} catch (e) {
// Si el request falla
dispatch(error(e.message)) // Entramos a estado "error"
}
}
Como puedes ver, el thunk orquesta las acciones y hace las llamadas externas.
Ahora, si recuerdas el previo tutorial de React, es buena idea abstraer llamadas al mundo exterior usando “servicios”, pues lo mismo haremos en este tutorial de React + Redux.
Crearemos un nuevo archivo en src/services llamado GithubApi.js y será el objeto mágico que importaremos en el slice.
class GithubApi {
getHeaders () {
const headers = new Headers()
headers.append('Authorization', `token ${process.env.REACT_APP_GITHUB_PERSONAL_TOKEN}`)
return headers
}
async search (query) {
try {
const url = `https://api.github.com/search/repositories?q=${query}&sort=stars&order=desc`
const headers = this.getHeaders()
const res = await fetch(url, { headers })
const { items: results } = await res.json()
return results;
} catch (e) {
throw e
}
}
}
export default new GithubApi();
Ahora importamos GithubApi en nuestro slice para que pueda ser usado por el thunk.
Ya sólo nos falta conectar el submit a este thunk. Abrimos SearchPage.js e importamos el thunk request:
import { selectQuery, setQuery, request, selectResults, selectBusy } from 'features/search/searchSlice';
Y finalmente despachamos el thunk al ejecutarse el submit:
const SearchPage = () => {
const query = useSelector(selectQuery);
const results = useSelector(selectResults);
const busy = useSelector(selectBusy);
const dispatch = useDispatch();
const hndChange = query => dispatch(setQuery(query))
const hndSubmit = query => dispatch(request(query))
return (
<div>
<SearchInput onSubmit={hndSubmit} onChange={hndChange} value={query} busy={busy} />
<SearchResults results={results} busy={busy} />
</div>
);
}
Ya podemos probar el feature. Podemos escribir un término en el input y ejecutar el submit. El submit va a despachar el thunk, que ejecutará la llamada a la API usando GithubApi y dependiendo de si la llamada es satisfactoria o fallida, la acción success o error será despachada y los resultados serán mostrados.
Lo interesante de este acercamiento es que podríamos leer el estado desde cualquier parte de la aplicación y, por ejemplo, mostrar el estado de “ocupado” en otro componente o crear una barra de búsqueda en el header reutilizando las mismas acciones y estado.
Conclusiones
Espero que este tutorial de React + Redux haya enfatizado que el último nos provee de un poderoso y sencillo contenedor de estado que permite describir mutaciones de forma casi declarativa. Esto combinado con la capacidad de React de crear interfaces de usuario también declarativamente los hace una combinación muy versátil y ampliamente usada.
Si lo que vas a construir es una aplicación mediana o robusta, o que va a crecer eventualmente, te recomiendo implementarla desde el principio pues verás que las ventajas de separar la capa de datos de la capa de vista hará más manejable el crecimiento y mantendrá el caos alejado por más tiempo.
Pero por el contrario, si tu aplicación es más bien pequeña, tal vez Redux sea una optimización prematura que podría llevarte a generar mucho código de soporte comparado con el código de tus features.
Lo que sí es verdad es que como desarrollador React, conocerlo es imprescindible para el mercado pues lo más probable es que en un porcentaje muy importante de los proyectos que encontrarás necesitarás utilizarlo.