February 01, 2022 • Edit this file (Private, ask me for access if you wanna contribute)
Botando React Redux de joelho no milho
Você ai já teve aquela fase em que ouvia a palavra 'redux' e sentava em posição fetal chorando? Então esse post é para você. Bora simplificar o estado global e implementar um gg 10/10 izi
Primeiros passos
Vou assumir que você já tem um ambiente configurado. Caso não tenha, em breve rolará um post por aqui pra te ajudar com isso.
Bora começar criando um projeto com o Create React App.
npx create-react-app global-state-izipizi
Quando finalizar a criação, a gente entra na pasta e já podemos fazer uma limpezinha para eliminar alguns arquivos que não vamos utilizar e começar a instalar as dependências
cd global-state-izipizi
Deletei todos os arquivos que estavam soltos em
src/
menos App.js
e index.js
,
e removi as importações nos referidos arquivos.
Pro nosso projetinho vamos precisar apenas de algumas dependências
- react-redux
- redux
- redux-persist
- redux-saga
- uuidv4 opcional
npm install react-redux redux redux-persist redux-saga --save
Configurando
Em /src
vamos criar uma pasta /store
onde
irá ficar todo o core do estado da nossa aplicação. Vamos começar
criando uma pasta dentro de store
para separar nosso
state. Nesse post, irei fazer um estado para armazenar cursos. Nós
teremos uma estrutura como:
src
/store
/courses
Dentro da pasta courses iremos criar 3 arquivos.
-
courses.actions.js
-
courses.utils.js
-
courses.reducer.js
Você pode criar mais arquivos para armazenar os tipos, os seletores etc.
Cada dado da store
deve ter o seu próprio
reducer
e este é encarregado de lidar com todas as
ações relacionadas a esse estado e ditam como esse estado irá
mudar de acordo com a ação enviada para o store
Dito isso, vamos começar a escrever nosso reducer
para
courses.
const INITIAL_STATE = {
courses: [],
};
function CoursesReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case "LOAD_COURSES":
return {
...state,
courses: action.payload.data,
};
case "ADD_COURSE":
return {
...state,
courses: action.payload
};
case "REMOVE_COURSE":
return {
...state,
courses: action.payload
};
default:
return state;
}
}
export default CoursesReducer;
Já deixei 3 ações definidas como sendo LOAD_COURSES
,
ADD_COURSE
e REMOVE_COURSE
. Para uma maior
flexibilidade, iremos criar funções utils
para realizar
a manipulação do estado.
No arquivo courses.utils.js
:
import { v4 as uuidv4 } from "uuid";
export function addCourse(state, courseItem) {
return [...state?.courses, { id: uuidv4(), ...courseItem }];
}
export function removeCourse(state, id) {
return state?.courses.filter((course) => course.id !== id);
}
Criei duas funções que recebem o estado atual dos cursos e um
payload
, a partir disso retornam um novo estado. Vamos
chamar essa função no nosso reducer
.
import { addCourse, removeCourse } from "./courses.utils.js"
const INITIAL_STATE = {
courses: [],
};
function CoursesReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case "LOAD_COURSES":
return {
...state,
courses: ,
};
case "ADD_COURSE":
return {
...state,
courses: addCourse(state, action.payload)
};
case "REMOVE_COURSE":
return {
...state,
courses: removeCourse(state, action.payload)
};
default:
return state;
}
}
export default CoursesReducer;
Supimpa irmão, estamos quase lá. Vamos criar nossas
actions
No arquivo courses.actions.js
:
export function addCourseAction(course) {
return { type: "ADD_COURSE", payload: course };
}
export function removeCourseAction(id) {
return { type: "REMOVE_COURSE", payload: id };
}
Deveras brutal não é mesmo?
Com nosso primeiro reducer
criado, nós precisamos criar
nosso store
.
Na pasta store
iremos criar um arquivo chamado
root-reducer.js
, ele será o responsável por reunir
todos os reducers
da aplicação para formar um único
estado. E já vamos persistir os dados dos cursos com o
redux-persist
.
import { combineReducers } from "redux";
import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import CoursesReducer from "./courses/courses.reducer";
const persistConfig = {
key: "@root-courses",
storage,
whitelist: ["courses"],
};
const rootReducer = combineReducers({
courses: CoursesReducer,
});
export default persistReducer(persistConfig, rootReducer);
Estamos configurando o persistConfig utilizando o localstorage como banco para persistir os dados. Na configuração em persistConfig iremos passar a key do estado que iremos armazenar, passamos qual o tipo de storage (que optamos por localStorage) e uma whitelist ou blacklist, que nada mais são que um array com todos os nomes dos reducers que queremos persistir (whitelist) ou que não queremos persistir (blacklist).
Bora criar agora nosso arquivo principal. Na pasta store vamos criar
um index.js
import { createStore, applyMiddleware } from "redux";
import createMiddleware from "redux-saga";
import { persistStore } from "redux-persist";
import rootReducer from "./root-reducer";
export const store = createStore(rootReducer);
export const persistor = persistStore(store);
Nosso store
já está pegando todos os
reducers
que passaram pelo rootReducer
,
formando um grande objeto de estado. Maneiro né?
Bora fazer funcionar agora.
Vamos voltar para a pasta src
e no arquivo
index.js
onde fazemos a injeção do
App.js
na DOM
:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { store, persistor } from "./store";
ReactDOM.render(
<Provider store={store}>
<PersistGate persistor={persistor} loading={null}>
<React.StrictMode>
<App />
</React.StrictMode>
</PersistGate>
</Provider>,
document.getElementById("root")
);
Cabô dev, mamão com açúcar. Você já tem acesso ao seu
store
por toda a aplicação.
De brinde, bora aplicar um middleware para permitir os dispatchs de
forma assíncrona no estado. Voltando ao arquivo
src/store/index.js
vamos utilizar o
redux-saga
.
import { createStore, applyMiddleware } from "redux";
import createMiddleware from "redux-saga";
import { persistStore } from "redux-persist";
import rootReducer from "./root-reducer";
import rootSagas from "./sagas";
export const sagaMiddleware = createMiddleware();
export const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSagas);
export const persistor = persistStore(store);
Middlware configurado, agora todos os dispatchs irão passar pelo saga antes de irem ao reducer.
Agora vamos criar o arquivo rootSagas:
import { all, takeLatest, put, call } from "redux-saga/effects";
import { v4 as uuidv4 } from "uuid";
async function fetchCourses() {
const res = await fetch("http://your-api/v2/courses");
const toJson = await res.json();
const data = toJson.slice(0, 10);
const formated = data.map((item) => {
return {
id: uuidv4(),
...item,
};
});
return formated;
}
function* asyncGetCourses() {
const res = yield call(fetchCourses);
yield put({
type: "LOAD_COURSES",
payload: {
data: res,
},
});
}
export default function* root() {
yield all([takeLatest("ASYNC_LOAD_COURSES", asyncGetCourses)]);
}
Aqui nós estamos criando uma ação do tipo
"ASYNC_LOAD_COURSES"
que não é ouvida por nosso
reducer
de courses mas será ouvida pelo saga e ela fará
o dispatch.
Exemplo
Irei deixar um repositório pessoal onde fiz toda configuração apresentada e que apresenta uma interface com um exemplo simples. Você pode conferir aqui Redux-Study
Indicação do post
See you space cowboy!
See also
Files in the same folder:
- Donation - I accept donations from several platforms, this post serve as permalink to my documentation/credit pages for each project. These links allow supporters to easily find and donate to the projects they are interested in, while ensuring that all donations are properly credited and acknowledged.
- Creating a simple stargazing effect with p5.js - From a set of points, generate a stargazing effect.
- LeetCode, Two Sum problem solution using brute force in Dart - Two Sum LeetCode problem solution written in Dart in complexity O(N).
- Acho que esse é um dos maiores crimes que já cometi - Criei um blog estático em NodeJS com puro HTML e CSS misturado com React e JavaScript + Babel, API, database, regra de negócio tudo em 1 único arquivo com 700 linhas.
- Qual a maneira mais simples de evoluir a si mesmo? - Se você já se perguntou, 'Como posso ser melhor?' em qualquer aspecto. Essa dica é definitivamente para você!
- Mr. Fear - Medo de aprender, medo de perguntar, medo de errar, medo de falar em público, medo de altura, medo de cruzar aquele beco num sábado a noite no centro da zona leste. Essas 4 letras não tem espaço suficiente pra agrupar essa complexa quantidade de sentimentos.
- Listar os últimos usuários que deram star em um repositório do GitHub - Com a API rest é impossível listar em order decrescente, portanto utilizaremos a GraphQL API do GitHub.
Anonymous message
The form above is anonymous, although the following data is collected:
- Current page URL.
- Your message.
- Your username if provided.
- Don't forget to include contact info if you are asking something, otherwise I'll never be able to reach you back!
If you prefer, you can reach me directly on GitHub or E-mail.
This form is powered by Discord Webhook API, so it is totally hackable, enjoy.