- Redux.
- Usando API através de JSON Server e Axios.
- Formatação de preço (R$).
- Configurção de Redux e integração com React.
- Criação de store com: { createStore } from 'redux'.
- Reducers (vide 10. Adicionando ao Carrinho):
- dispatch action, reducers, State
- Reactotron + Redux
- mapStatetoProps e mapDispatchToProps.
- immer para tratar de produtos duplicados.
- Refatorando actions.
- Configurando Redux Saga
- Middlewares p/ sideEffects.
- Separar actions em 2 Ex. addToCartRequest e addToCartSuccess
- Produto vs. Estoque e mensagens de ERRO via React Toastify.
Um website 'Rocketshoes', que tem uma lista de produtos (tênis). O site permite adicionar os produtos ao 'Carrinho'. Na página do Carrinho, podemos alterar a quantidade de cada produto, remover o produto e temos o retorno do valor subtotal e total de produtos em R$.
- Iniciar a biblioteca JSON Server:
json-server server.json -p 3333
- Iniciar o React
yarn start
-
Criando o projeto do zero.
- Terminal:
yarn create react-app modulo05
- webpack, babel se encontram em "react-scripts"
- deletar eslintConfig: {}, pois vamos configurar do zero.
<div id="root">"
é onde vai todo o código React.- Delete: comments, manifest, manifest.json, App.css, App.test.js, index.css, logo, serviceWOrker.js
- Terminal:
-
ESLint, Prettier & EditorConfig.
- Criar modulo05/.editorconfig
- Add/Create Eslint (Popular Language: AirBNB)
yarn add eslint -D yarn eslint --init
- Excluir package-lock.json e yarn.
yarn add prettier eslint-config-prettier eslint-plugin-prettier babel-eslint -D
- modulo05/.eslintrc.js
- modulo05/.prettierrc
-
Estilos globais
- Criar src/styles/global.js (padrão Rocketseat)
yarn add styled-components
- Add Roboto
- Google -> Roboto -> https://fonts.google.com/specimen/Roboto
- Select, @import
- Inserir background
- import background from "../assets/images/background.svg";
- src/App.js: import GlobalStyle from "./styles/global"; Criar componente.
- Criar src/styles/global.js (padrão Rocketseat)
-
Criando Header
- Criar src/components/Header/index.js e styles.js
- Home/index.js: import logo.svg.
- App.js: import Header.
- HEader/index.js: adicionar o comp: Link e Cart.
- Add Icon to Cart:
yarn add react-icons
6.Header/styles.js: 1. Estilizar Container e Cart 1. Cart = styled(Link)``; 2. Adicionar opacity e transition.
-
Estilizando a Home
- Criar o arquivo src/pages/Home/styles.js
- Home/index.js: Trocar Container por ProductList.
- Add img, strong, span, button, MdAddShoppingCart.
- Copiar o li 5 vezes para ter 5 produtos.
- Home/styles.js: estilizar a grid/lista.
- button: margin-top: auto (mantem o botão sempre para baixo).
- adicionar hover.
- Polished lida com cor dentro do JavaScript.
yarn add polished
- Polished lida com cor dentro do JavaScript.
-
Estilizando o Carrinho
- Cart/index.js
- Cart/styles.js
-
Configurando API
- Usar API fake da Rocketseat (através de JSON server).
- Biblioteca JSON Server:
- Google -> json-server -> https://github.com/typicode/json-server
- Criar json e criar uma API a partir desse arquivo.
yarn global add json-server
- arquivo: server.json
- Axios.
yarn add axios
- Criar src/services/api.js (localhost:3333)
- rodar server:
json-server server.json -p 3333
- Vide: http://localhost:3333/products
- Biblioteca JSON Server:
- Usar API fake da Rocketseat (através de JSON server).
-
Buscando Produtos da API
- pages/Home/index.js
- import api from '../../services/api';
- Transformar function Home => class, criar render:
- Criar a função componentDidMount().
- Apagar os li a mais, adicionar key= ao li.
- Adicionar um products.map dentro do ProductList e li fica dentro do prducts.map.
- img: trocar o src, alt, strong, span.
- Formatar o preço.
- Criar pasta src/util/format.js
- Intl é uma função do javascript.
- Home/index.js: import { formatPrice } from '../../util/format';
- Criar o formatPrice dentro de componentDidMount (assim que chamar a API).
- pages/Home/index.js
-
Configurando o Redux
- Redux e integração Redux com React:
yarn add redux react-redux
- Criar src/store/index.js: import { createStore } from 'redux';
- src/App.js
- Adicionar Provider: Provider vai deixar o store disponivel para todos os compnentes. Provider fica em volta de todos os componentes.
- import store from './store'; Provider store={store}
- Criar o reducer cart:
- Criar store/modules/cart/reducer.js.
- Criar rootReducer:
- Criar store/modules/rootReducer.js
- combine todos os reducers em um unico export.
- import rootReducer em store/index.js.
- Redux e integração Redux com React:
-
Adicionando ao Carrinho (Cart)
- Home/index.js: Conectar o component ao state do Redux.
import { connect } from 'react-redux';
- Tirar o export default do class e add a ultima linha: export default connect()(Home);
- Disparar a action:
- onClick no button: add função handleAddProduct.
- dispatch serve para disparar uma action (this.props.dispatch).
- type é obrigatória. product = produto a adicionar.
- um dispatch de dentro de um componente do react = todos os reducers são ativados (ouvem).
- Criar switch para ouvir apenas as actions que interessam ao reducer e ignorar as demais.
- case: 'ADD_TO_CART'
- return: adicionar o product ao array: state.
- Todo reducer recebe (state, action).
- State é o estado antes da action (array vazio).
- o Reducer avisa todos os componentes que tem connect que state foi atualizado.
- Home/index.js: Conectar o component ao state do Redux.
-
Reactotron + Redux
-
yarn add reactotron-react-js reactotron-redux
- Criar src/config/ReactotronConfig.js
- .eslintrc.js: eliminar o erro do console.tron.
- Integrando a parte do Redux no Reactotron.
- store/index.js: add enhancer.
- import './config/ReactotronConfig';
- Reactotron:
- vide ACTION
- Criar State Subscription: Cart
-
-
Listando no carrinho
- Cart/index.js
- Tirar o export default do class e add a ultima linha: export default connect(mapStateToProps)(Cart);
- add mapsStateToProps (snip-it). A partir desse momento, o Cart() ja tem acesso a { cart }.
- cortar tr entre tbody e colar entre {cart.map(product => () )}.
- cart/reducer.js: adicionar produto como objeto com amount: {...action.product, amount: 1,},
- add product.amount
- Cart/index.js
-
Produto Duplicado
- Até agora, ao adicionar o produto novamente, ele está sendo listado 2 vezes,
- immer: lidar com objects e arrays imutaveis.
- Cria Draft e usa ToDos.
- cria flexibilidade para alterar states.
- Google -> immerjs: https://github.com/immerjs/immer
yarn add immer
- cart/reducer.js
- import produce from 'immer';
- refazer ADD return produce(), incluir if(productIndex == duplicado).
- eslint error: incluir regra: 'no-param-reassign': 'off',
-
Remover produto
- Cart/index.js: Adicionar o parametro dispath no Cart(), prop onClick no button de delete item.
- cart/reducer.js: adicionar o case 'REMOVE_FROM_CART':
-
Refatorando as actions
- Criar 1 arquivo para cada action
- criar o arquivo cart/actions.js
- copiar oq ta dentro do dispatch no Home/index.js e criar export function addToCart()
- idem para removeFromCart
- Home/index.js: import * as CartActions from '../../store/modules/cart/actions';
- o * da acesso a CartActions.addToCart e CartActions.removeFromCart
- dispatch(CartActions.addToCart(product));
- import { bindActionCreators } from 'redux';
- nova função mapDispatchToProps (com snip-it da Rocketseat)
- converte actions do redux em componentes.
- add ao: export default connect(null, mapDispatchToProps)(Home);
-
handleAddProduct = product => { const { addToCart } = this.props; addToCart(product);
- idem para Cart/index.js
...
function Cart({ cart, removeFromCart }) { <button type="button"> <MdDelete size={20} color="#7159c1" onClick={() => removeFromCart(product.id)} /> </button>
- Refatorando actions deixa elas reutilizavel.
- No Reactotron, é bom saber qual modulo a action está disparando, portanto nomear actions:
- cart/actions.js
- type: '@cart/ADD', type: '@cart/REMOVE',
- cart/reducer.js
- case '@cart/ADD': case '@cart/REMOVE':
- cart/actions.js
-
Alterando a Quantidade
- cart/actions.js
- Criar export function updateAmount(id, amount)
- Cart/index.js
- Criar functions (+) increment(product) e (-) decrement(product).
- cart/reducer.js
- case '@cart/UPDATE_AMOUNT':
- obs. o reducer é responsavel por todas as regras de utilidade (ex. amount < 0). Não é responsabilidade da interface.
- cart/actions.js
-
Calculando totais
- Os cálculos, não ocorrerão no reducer, nem no render, e sim no mapStateToProps.
- Cart/index.js
- import { formatPrice } from '../../util/format';
- mapStateToProps: o cart pode ser retornado do jeito que quiser. cart: state.cart.map()
- Calculando subtotal:
- Adicionar ao strong com subtotal.
- Calculo só vai executar quando alguma informação do reducer atualizar.
- Calculando Total:
- mapsStateToProps: total: state.cart.reduce((total, product).
- reduce: 1 unico valor para todo array.
- total: formatPrice(...)
- mapsStateToProps: total: state.cart.reduce((total, product).
-
Exibindo quantidades
- Home/index.js
- Criar um mapStateToProps, return amount.
- Add amount ao carrinho. MdAddShoppingCart: {amount[product.id] || '-'}
- Home/index.js
-
Configurando Redux Saga (Segue o roteiro abaixo, não tem jeito :/ )
- Sagas: Middlewares dentro do Redux. (interceptors p/actions).
- toda vez que disparar uma action, um middleware pode ser acionada para fazer algum efeito colateral = sideEffect.
- Vamos buscar otras infos do produto ao adicionar ao carrinho.
-
yarn add redux-saga
- Criar modules/cart/sagas.js
-
- = generator (como se fosse uma async)
function* addToCart(action) {}
- Trocar o parametro addToCart(product -> id) em actions.js
- Home/index.js: puxar somente o id.
<button type="button" onClick={() => this.handleAddProduct(product.id)} > handleAddProduct = id => { const { addToCart } = this.props; addToCart(id);
- sagas.js: import call
import { call } from 'redux-saga/effects'; import api from '../../../services/api'; function* addToCart({ id }) { const response = yield call(api.get, `/products/${id}`); }
- actions.js
- Separar addToCart em addToCartRequest e addToCartSuccess
- obs. request é ouvido apenas pelo saga (e não pelo reducer).
- saga finaliza a chamada API e tem dados do produto, chama a Success.
- Success é recebida pelo reducer (É como um passo a mais).
- Separar addToCart em addToCartRequest e addToCartSuccess
- cart/reducer.js
- alterar: case '@cart/ADD_SUCCESS':
- Home/index.js: alterar const { addToCartRequest } = this.props;
- sagas.js:
- import put. put dispara uma action no redux
- import { addToCartSuccess } from './actions';
- yield put(addToCartSuccess(response.data));
-
import { call, put, all, takeLatest } from 'redux-saga/effects'; export default all([takeLatest('@cart/ADD_REQUEST', addToCart)]);
- export default all...cadastrar varios linsteners.
- takeLatest = ultimo click, takeEvery = todos os clicks rapidos serão considerados.
- takeLatest -> 2 parametros: action a ouvir, action a disparar.
-
- Configurar o saga dentro do redux
- Criar modules/rootSaga.js
- vai juntar todas as Sagas em 1 arquivo (como rootReducer).
- atualizar store/index.js
- Criar modules/rootSaga.js
- Sagas: Middlewares dentro do Redux. (interceptors p/actions).
-
Reactotron + Saga 1. plugins no Reactotron.
yarn add reactotron-redux-saga
2. config/ReactotronConfig.js: import reactotronSaga from 'reactotron-redux-saga';
3. store/index.js: criar sagaMonitor
-
Separando actions
- cart/reducer.js: alterar ADD_SUCCUESS
- apenas: draft.push(product); o resto acontece no sagas.
- cart/sagas.js:
- adicionar const data com as informações de reducer.
- Não duplicar o produto:
- import { select } from 'redux-saga/effects'; buscar informações dentro do state.
- const productExists = yield select...
- cart/reducer.js: alterar ADD_SUCCUESS
-
Estoque na adição
- cart/sagas.js:
- Criar const stock
- ...
if (amount > stockAmount) { console.tron.warn('ERRO'); return; }
- cart/sagas.js:
-
React Toastify
- Mensagem de ERRO. p/ front user.
- src/App.js: import { ToastContainer } from 'react-toastify'; adicionar ToastContainer />
- src/styles/global.js: import 'react-toastify/dist/ReactToastify.css';
- modules/cart/sagas.js: add toast.error('Quantidade solicitada fora de estoque');
-
Estoque na alteração ( quantidade)
- modules/cart/actions.js
- separar updateAmount em 2 (sempre que for trabalhar com Saga)
- updateAmountRequest(id, amount)
- updateAmountSuccess(id, amount)
- separar updateAmount em 2 (sempre que for trabalhar com Saga)
- pages/Cart/index.js
- Alterar occurences: updateAmount para updateAmountRequest.
- modules/cart/sagas.js
- Alterar all occurences: updateAmount para updateAmountSuccess.
- modules/cart/actions.js
-
Navegação de página dentro do Saga (Funcionalidade excluída do projeto)
-
Ao clicar em "Adicionar ao Carrinho", ir direto a pagina /Cart.
-
Poŕem, a navegação acontece depois de um dispatch do Saga, portanto vamos ter que fazer a navegação via Saga. Obs. javascript nao sabe que o saga esta sendo executado, portanto pode acontecer a mudança de pagina antes da adição ao carrinho.
-
yarn add history
-
Criar services/history.js
-
src/App.js:
- import history from './services/history'; import { Router } from 'react-router-dom';
- Router history={history}>
-
cart/sagas.js: incluir history.push
-
teste: adicionar ao carrinho direciona a pagina do carrinho.
- colocar um delay no carregamento, Terminal:
json-server server.json -p 3333 -d 2000
-