Fix/pt translation redux selectors (#34657)

* creation of a redux-saga tutorial

* Expanded the Redux selectors portuguese translation in the guide
This commit is contained in:
jonniebigodes
2018-12-22 00:01:25 +00:00
committed by Fabio Trilho Pereira
parent 8f8977abce
commit 0b2af3d792
2 changed files with 436 additions and 7 deletions

View File

@ -1,15 +1,312 @@
--- ---
title: Redux Sagas title: Redux Sagas
--- ---
## Redux Sagas
This is a stub. <a href='https://github.com/freecodecamp/guides/tree/master/src/pages/redux/redux-sagas/index.md' target='_blank' rel='nofollow'>Help our community expand it</a>. ## Introduction
In this guide it will be presented to the reader the basic concept of Redux Sagas, coupled with a minimal working example.
It's assumed that the reader has already grasped the basic concepts of [Redux](http://redux.js.org/) and [React](https://reactjs.org/).
## Definition
Ranging from fetching content from the browser local storage to fetching data from HTTP call or from GraphQL server, Redux Sagas help any Redux application to achieve this in a more organized and efficient way.
A analogy that can be used to ilustrate what Redux Sagas are, is to think of a Saga like a separate process running side by side with the application and can be controlled via Redux actions.
## Simple Example
Bellow will be presented to the reader a very simple example that covers some of the key features of this library.
Assuming that the [Redux Tutorial](https://guide.freecodecamp.org/redux/tutorial) was followed and a similar project structure is present.
```
project_root
│ index.js
|
└───client
| App.jsx
| NotFound.jsx
|
└───common
└───actions
| │ appActions.js
|
└───constants
| │ Actiontypes.js
|
└───reducers
| │ appReducer.js
└───store
│ store.js
```
## Installation
Start by adding the library to the project.
With npm by issuing the command:
```sh
npm install --save redux-saga
```
Or with yarn:
```sh
yarn add redux-saga
```
Also for the scope of this guide it will be used the [axios](https://github.com/axios/axios) library that handles [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) based API calls, but any other is acceptable.
## Redux Changes
It will be necessary to make some changes to the tutorial to accommodate the addition of Sagas.
Start by modifying the `Actiontypes.js` file located in the *constants* folder and add the following code.
```javascript
export const API_REQUEST = "API_REQUEST";
export const API_SUCCESS = "API_SUCCESS";
export const API_FAILURE = "API_FAILURE";
```
Modify the `appReducer.js` file located in the *reducers* folder to contain the code bellow.
```javascript
import {
API_REQUEST,
API_SUCCESS,
API_FAILURE
} from '../constants/Actiontypes';
// initial state of the reducer
const initialState = {
fetching: false,
data: null,
error: null
};
// the reducer
export const reducer=(state = initialState, action)=>{
switch (action.type) {
case API_REQUEST:
return { ...state, fetching: true, error: null };
case API_SUCCESS:
return { ...state, fetching: false, data: action.data };
case API_FAILURE:
return { ...state, fetching: false, data: null, error: action.error };
default:
return state;
}
};
```
## Saga Implementation
After the changes made to the Redux part of the application, now time to move onto the Saga implementation.
Inside the *common* folder create a new one named *sagas*, and inside that folder a new file named `sagas.js` with the following code.
```javascript
import { takeLatest, call, put } from "redux-saga/effects"; // helper functions imported from redux-sagas
import axios from "axios"; // promise library used for the guide
// watcher saga: the functon that watches for actions dispatched to the store and starts worker saga
export function* watcherSaga() {
yield takeLatest("API_REQUEST", workerSaga);
}
// function that makes the api request and returns a Promise for response
function fetchData() {
return axios({
method: "get",
url: "https://your_api_endpoint"
});
}
// worker saga: makes the api call when watcher saga sees the action
function* workerSaga() {
try {
const response = yield call(fetchData);
const data = response.data.message;
// dispatch a success action to the store with the new data
yield put({ type: "API_SUCCESS", data });
} catch (error) {
// dispatch a failure action to the store with the error
yield put({ type: "API_FAILURE", error });
}
}
```
While reading the code above, the reader might notice some things that are different from standard javascript code.
One is the `function*` syntax. Using this creates a special kind of function called a [generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*).
These functions have a special feature built in, and that is, they can be paused and restarted and also remember the state over time.
Also another diference is the `yield` keyword inside these functions, it represents a asynchronous step in a synchronous/sequencial process.
An analogy that can be applied to the `yield` keyword is to to think of it as the `await` in the await/async pattern.
Breaking the saga code into smaller pieces will result in the following:
1. The `watcherSaga` generator function is what will *watch* for any action dispatched to the store and will trigger the `workerSaga` function.
2. [takeLatest](https://github.com/redux-saga/redux-saga/tree/master/docs/api#takelatestpattern-saga-args) is a helper function built in the library
that will trigger a new `workerSaga` when a `API_REQUEST` action is triggered, while cancelling any previously triggered ones still being processed.
3. The `fetchData` function will make use of the axios library to make a request to a given API endpoint and returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) as a response.
4. The `workerSaga` generator function will attempt to `fetchData`, using the other imported helper function [call](https://github.com/redux-saga/redux-saga/tree/master/docs/api#callfn-args) and will store the result.
5. If the `fetchData` function succeed, it's result will be extracted from the response and sent as the payload of the action `API_SUCCESS`, using the `put` helper function that was imported.
6. If there was an error with the request. The store is notified via the action `API_FAILURE` sending the error as the payload.
## Connect React with Redux and Redux-Saga
Now that a simple Redux Saga is implemented, now to proceed in connecting all the parts.
Open the `store.js` file located inside *store* folder, and modify it by adding the following code:
```javascript
import { createStore, applyMiddleware, compose } from "redux";
import reducer from '../reducers/ExampleAppReducer'; // the reducer created
import createSagaMiddleware from "redux-saga"; // Redux Saga middleware
import { watcherSaga } from "../sagas/sagas"; // imports the watched saga defined earlier
// create the saga middleware
const sagaMiddleware = createSagaMiddleware();
// dev tools middleware
const reduxDevTools =
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
export default createStore(
reducer,
compose(applyMiddleware(sagaMiddleware), reduxDevTools)
);
// run the saga
sagaMiddleware.run(watcherSaga);
```
The `index.js` located at the *project_root* folder should look like this.
```javascript
import React from "react";
import ReactDOM from "react-dom";
import {Router,Route,browserHistory} from 'react-router';
import store from "../common/store/store";
import App from '../client/App';
import NotFound from '../client/NotFound';
import { Provider } from "react-redux";
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={App}/>
<Route path="*" component={NotFound}/>
</Router>
</Provider>,
document.getElementById("root")
);
```
The `app.js` file inside the *client* folder needs to be modified in order to reflect the changes made to the application.
Bellow is a simple React Component connected to redux that will make use of what was implemented.
```javascript
import React, { Component } from "react";
import { connect } from "react-redux";
class App extends Component {
render() {
const { fetching, item, onRequestData, error } = this.props;
return (
<div>
<header>
<h1>Redux Saga Guide</h1>
</header>
<h4>{item}</h4>
{fetching ? (
<button disabled>Fetching...</button>
) : (
<button onClick={onRequestData}>Make a async request to your saga</button>
)}
{error && <p style={{ color: "red" }}>Uh oh - something went wrong!</p>}
</div>
);
}
}
/**
* es6 fat arrow function to get information from the application state
* @param {*} state current state of the application
*/
const mapStateToProps = state => {
return {
fetching: state.fetching,
item: state.data,
error: state.error
};
};
/**
* es6 fat arrow function to connect the actions declared in the saga file to the the component as if they were common react props
* @param {*} dispatch function send to action file to be later processed in the reducer
*/
const mapDispatchToProps = dispatch => {
return {
onRequestData: () => dispatch({ type: "API_REQUEST" })
};
};
/**
* this function connects the application component to be possible to interact with the store
* @param {*} mapStateToProps allows the retrieval of information from the state of the application
* @param {*} mapDispatchToProps allows the state to change via the actions defined inside
*/
export default connect(mapStateToProps, mapDispatchToProps)(App);
```
# In summary
Now that everything is connected, what will happen every time a user interacts with the application is the following:
1. An event takes placee.g. user does something (clicks “onRequestData” button) or an update occurs (like componentDidMount).
2. Based on the event, an action is dispatched, likely through a function declared in `mapDispatchToProps` (e.g.onRequestData)
3. A `watcherSaga` sees the action and triggers a `workerSaga`. Use saga helpers to watch for actions differently.
4. While the saga is starting, the action also hits a reducer and updates some piece of state to indicate that the saga has begun and is in process (e.g. fetching).
5. The `workerSaga` performs some side-effect operation (e.g. `fetchData`).
6. Based on the result of the `workerSagas` operation, it dispatches an action to indicate that result. If it's successful then (`API_SUCCESS`), with a payload of the data recieved. If an error (`API_FAILURE`), you might send along an error object for more details on what went wrong.
7. The reducer handles the success or failure action from the `workerSaga` and updates the store accordingly with any new data, as well as sets the “in process” indicator (e.g. fetching) to false.
<a href='https://github.com/freecodecamp/guides/blob/master/README.md' target='_blank' rel='nofollow'>This quick style guide will help ensure your pull request gets accepted</a>.
<!-- The article goes here, in GitHub-flavored Markdown. Feel free to add YouTube videos, images, and CodePen/JSBin embeds --> <!-- The article goes here, in GitHub-flavored Markdown. Feel free to add YouTube videos, images, and CodePen/JSBin embeds -->
#### More Information: #### More Information:
<!-- Please add any articles you think might be helpful to read before writing the article --> <!-- Please add any articles you think might be helpful to read before writing the article -->
[Redux Saga Docs](https://redux-saga.js.org/)
[Redux Docs](https://redux.js.org/)
[Redux Saga Examples](https://github.com/redux-saga/redux-saga/tree/master/examples)
[Redux Tutorial](https://guide.freecodecamp.org/redux/tutorial)

View File

@ -2,10 +2,142 @@
title: Redux Selectors title: Redux Selectors
localeTitle: Seletores de Redux localeTitle: Seletores de Redux
--- ---
## Seletores de Redux
Este é um esboço. [Ajude nossa comunidade a expandi-lo](https://github.com/freecodecamp/guides/tree/master/src/pages/redux/redux-selectors/index.md) . ## Seletores em Redux
[Este guia de estilo rápido ajudará a garantir que sua solicitação de recebimento seja aceita](https://github.com/freecodecamp/guides/blob/master/README.md) . Seletores em Redux são na sua essência funções que são usadas para selecionar um subconjunto de dados existentes num determinado estado, ou em termos de Redux, efetuar cálculos de dados derivados.
#### Mais Informações: Estas funções recebem um determinado estado como argumento e efetuam algumas computações e retornam os dados que irão ser fornecidos a um qualquer componente
## Porque usar este padrão?
Uma das razões para o uso deste padrão consiste na eliminação de dados duplicados.
Partindo do pressuposto que possuímos uma lista de itens em Redux e somente iria ser necessário apresentar uma lista filtrada desses dados.
Uma abordagem considerada ingénua a ser usada com Redux iria consistir em filtrar a lista, guardar os dados e então devolver estes para o componente em questão.
Com esta abordagem iriamos ter duas cópias de itens o que iria ser mais fácil de garantir que os dados não iriam estar sincronizados.
No caso de uma qualquer operação sobre os dados, seria necessário efetuar a atualização duas vezes.
Outra abordagem considerada ingénua, seria a transformação direta no componente, tirando proveito da função Redux `mapStateToProps`, tal como apresentado no bloco de código abaixo.
```javascript
// redutor
const listofItems=(state={items:[]},action)=>{
switch(action.type){
case "SHOW_ALL":
return action.data.items;
default:
return state;
}
};
```
Os itens guardados poderiam ser algo deste tipo:
```javascript
{
id:1,
text:"Lorem ipsum dolor sit amet",
complete:false
}
```
```javascript
import React, { Component } from "react";
import {connect} from "react-redux";
class ItemShow extends Component{
render(){
const {incompleteItems}= this.props
return (
<ul>
{
incompleteItems.map(item=>(
<li key={item.id}>
{item.text}
</li>
))
}
</ul>
)
}
}
const mapStateToProps =(state)=>{
return {
incompleteItems:state.listofItems.filter(item=>{
return !item.complete
});
}
};
export default connect(mapStateToProps,null)(ItemShow);
```
Com isto, o que iria acontecer na realidade era tornar os componentes cada vez mais dependentes do estado Redux e cada vez menos genéricos e reutilizáveis.
O que iria também causar impacto na performance da aplicação, isto porque a função `mapStateToProps` iria ser invocada inúmeras vezes durante o ciclo de vida da aplicação e utiliza-la para este tipo de cálculo não e considerado uma boa prática.
## Função Seletora em ação
Para resolver o problema acima descrito, será necessário criar uma função seletora.
De forma a que siga em conformidade com as boas práticas Redux, esta terá que estar definida o mais próximo possível do redutor.
Expandindo o exemplo já mencionado, agora o redutor será algo tal como o que o bloco de código seguinte.
```javascript
// redutor
const listofItems=(state={items:[]},action)=>{
switch(action.type){
case "SHOW_ALL":
return action.data.items;
default:
return state;
}
};
const getIncompleteItems=state=>{
const {listofItems}=state;
return listofItems.filter(item=>{
return !item.complete
});
}
```
E o componente que iria usar os dados algo deste género.
```javascript
import React, { Component } from "react";
import {connect} from "react-redux";
class ItemShow extends Component{
render(){
const {incompleteItems}= this.props
return (
<ul>
{
incompleteItems.map(item=>(
<li key={item.id}>
{item.text}
</li>
))
}
</ul>
)
}
}
const mapStateToProps =(state)=>{
return {
incompleteItems:getIncompleteItems(state);
}
};
export default connect(mapStateToProps,null)(ItemShow);
```
#### Mais Informações:
[explicação simples reselect](https://guide.freecodecamp.org/redux/reselect)
[Documentação reselect](https://guide.freecodecamp.org/redux/reselect)