frontendbr / forum

:beer: Portando discussões feitas em grupos (Facebook, Google Groups, Slack, Disqus) para o GitHub Discussions
MIT License
4.26k stars 234 forks source link

Redux e boas práticas no seu ecossistema #906

Closed thulioph closed 5 years ago

thulioph commented 6 years ago

Lendo alguns códigos diferentes, terminei com algumas dúvidas relacionadas a utilização do Redux, são elas:

Redux como middleware para chamadas em API

Porque utilizar o redux (que é um state container) para realizar chamadas pra API? Qual o sentido de utilizar ferramentas como o redux-api-middleware?

Exemplo de uma chamada utilizando o redux-api-middleware

  export const recoverPassword = (parameters) => ({
    [RSAA]: {
      types: [
          RECOVER_PASSWORD_REQUEST, 
          {
            type: RECOVER_PASSWORD_SUCCESS,
            meta: (action, state, res) => {
                history.push('/auth/login');
                return { successMessage: "Nova senha criada com sucesso" }
            },
          },
          {
            type: RECOVER_PASSWORD_FAILURE,
            meta: { errorMessage: "Dados invalidos" },
          }
        ],
      endpoint: '/api-auth/reset/',
      method: 'POST',
      headers: { 
        'Content-Type': 'application/json' 
      },
      body: JSON.stringify(parameters)
    }
  });

Porque não utilizar simplesmente o fetch com promises?

Exemplo de uma chamada utilizando o fetch

fetch(url, config).then(data).catch(error);

Eu vejo o primeiro código muito mais verboso do que o segundo, nesse caso seria gosto pessoal? Tem algum real benefício em utilizar um ao invés do outro? Imagino que pela responsabilidade do redux ser uma, uma ferramenta como essa estaria "violando" o principío de responsabilidade única?

Reducers e outras manipulações

É correto realizar outro tipo de manipulação dentro de um reducer? Ou essa responsabilidade é de quem vai chamar o reducer?

Eexmplo de um código com manipulação de localStorage em um reducer.

export default function auth(state = initialState, action) {
  const { type, payload } = action;

  switch (type) {
    case LOGIN_SUCCESS:
      localStorage.setItem('token', payload.access_token);
      return {
        ...state,
        isAuthenticated: true,
        token: payload.access_token
      };

    case LOGOUT:
      localStorage.removeItem('token');
      return {
        ...state,
        isAuthenticated: false,
        token: ''
      };
}

Eu imagino que para o código acima, o container no qual foi realizado o dispatch para a action de login, deveria ser o responsável por realizar o localStorage.setItem no then da promise; E o container que realizou o dispatch de logout, deveria realizar o localStorage.removeItem.

Gostaria de saber a opinião de quem trabalha com isso a mais tempo e gerar uma discussão legal em volta do assunto.

Faz sentido? Entendi errado? Ou estou viajando demais? 😄

felipemfp commented 6 years ago

Cara, nunca utilizei esse redux-api-middleware. Sempre fui de axios com redux-thunk. Seguindo o padrão: CHAMADA_REQUEST, CHAMADA_SUCCESS e CHAMADA_ERROR. Ou seja, para cada chamada à API, tem essas três actions. Funciona muito bem para mim.

Tem uma galera que utiliza muito o redux-saga, então vale a pena dá uma olhada.

fdaciuk commented 6 years ago

Normalmente você usa a action do Redux com algum middleware para chamadas assíncronas, pois você vai precisar do resultado que será usado para disparar a action.

Essa é basicamente a ideia de usar redux-thunk, redux-api-middleware, redux-saga, ou até algum middleware próprio; pois o Redux foi pensado para ser previsível. E para isso, as actions precisam ser síncronas.

Sobre o localStorage: ele deve ser manipulado no seu action creator, não no reducer.

O reducer deve ser uma função pura, e sua única responsabilidade é receber o state anterior, computar e retornar o novo state.

Qualquer manipulação impura deve ser feita sempre nas actions.

Dessa forma você mantém o Redux funcionando de forma previsível, exatamente para o que ele foi pensado para ser :)

yanmagale commented 6 years ago

@thulioph com relação aos midlewares, eu apenas adicionaria ao projeto se realmente fosse necessário, pois realmente adiciona uma grande complexidade e uma carga a mais de código, e ai o Redux deixa de ser simples e previsível.

ninetails commented 6 years ago

creio que o redux-api-middleware tenha muito mais relação com syntax sugar, a idéia dele é apresentar uma "interface" padronizada para não ter que fazer promises/fetch na mão dado que chamadas pra API são recorrentes, assim evitando que o desenvolvedor escreva os 3 dispatchs toda vez que tiver que fazer uma requisição pra API, evitando também copia-e-colas (ter um padrão de escrita pra chamadas e não correr o risco de ter metade do código em fetch, outra metade em promise, outra parte com saga... ou mesmo falando em "mass detect"). E sobre ter 3 types, meio que virou padrão pois com isso vc pode controlar sua view em 3 estados: Quando envia a requisição (vc pode botar um "loading"), quando deu certo (sucesso, mostra o dado que voltou por exemplo) ou se deu erro (mostrar uma mensagem amigável de erro).

sobre o localStorage, como já disseram, também concordo em deixar no action, reducer é função pura pra manipular estado. sugeriria redux-multi se vc quiser realmente separar a responsabilidade e chamar diferentes actions de uma vez.

victorsilent commented 6 years ago

Muito bom o tópico, comecei no react a um tempo e estava com essas mesmas dúvidas suas :v porem eu ainda tenho uma outra, normalmente eu utilizo o firebase como banco e ele faz um papel similar ao redux, só que no banco. Dai fiquei no problema: ao adicionar um item no firebase ele automaticamente ja atualiza a listagem no meu app, porem eu que deveria fazer isso com o redux não? Procurei um pouco e vi algumas pessoas dizendo que tratar os dados com firebase + redux não teria sentido, ja que ele faz isso por si só. Queria saber o que vocês acham nesse caso

fdaciuk commented 6 years ago

@victorsilent se você for usar o firebase como seu state container, aí você não precisa do Redux mesmo. Pode, inclusive, criar um componente que usa o pattern de render functions ou um HOC pra isolar o comportamento do Firebase e conectar aos componentes que você vai precisar das informações.

Mas se for usar o Firebase apenas como DB - onde ele não tenha, por exemplo, informações do estado de UI, ou coisas do tipo - , com possibilidade de substituí-lo no futuro, aí faz sentido usar em conjunto com Redux, pra facilitar a substituição =)

victorsilent commented 6 years ago

@fdaciuk Você não sabe o quanto eu fico mais tranquilo fazendo as coisas depois de ler isso kkk Eu estava fazendo o uso do redux apenas para garantir o uso das ações e tratar o sucesso ou erro para uma mensagem, tipo "adicionado com sucesso". Eu tinha visto outros usos com bancos que não eram real-time em que você mandava um "ADD_TODO" e no sucesso você adicionava o todo no estado e na falha você não adicionava e mostrava a mensagem de erro, porem com o firebase a questão de adicionar ou não fica por parte dele, eu estava fazendo apenas a UI mesmo :) Vou dar uma olhada nessas duas soluções, principalmente a do render functions, porque o HOC eu acredito que estou fazendo um pouco, devido ao fato de quebrar os componentes em componentes e containers e nos containers usar o firebase.

thulioph commented 6 years ago

@felipemfp eu não tive a necessidade de utilizar um thunk nem saga ainda, mas já dei uma olhada neles, só não conhecia o redux-api-middleware.

thulioph commented 6 years ago

Sim @yanmagale, concordo com você nesse ponto. Só tive a dúvida por está lendo um outro código diferente do meu, e tive esse questionamento.

thulioph commented 6 years ago

@ninetails, não sei se meu raciocínio está correto, mas o padrão das 3 estados que você citou, eu consigo realiza-lo de uma maneira um pouco diferente:

function apiRequest() {

 this.setState({ loading: true });

 const request = () => fetch(url, config);

  request.then(data => {
    this.setState({ loading: false, foo: data });
  }).catch(err => {
    this.setState({ loading: false, error: err });
  });
};
<button onClick={() => apiRequest()}>go</button>

Faz sentido?

fdaciuk commented 6 years ago

É isso mesmo @thulioph! =) A ideia de usar o redux-thunk é só não precisar fazer isso direto no componente, e deixar o redux lidar com o resultado do request =)

thulioph commented 6 years ago

Valeu @fdaciuk _o/

ninetails commented 6 years ago

@thulioph então, sim, mas aí vc está usando o estado no teu Componente.

A idéia do Redux é tirar o estado dos componentes para centralizar em um só lugar na sua aplicação, e não em diversos componentes.