Como acessar o estado dentro do redutor Redux?

88

Tenho um redutor e, para calcular o novo estado, preciso de dados da ação e também de dados de uma parte do estado não gerenciada por esse redutor. Especificamente, no redutor que mostrarei abaixo, preciso acessar o accountDetails.stateOfResidenceIdcampo.

initialState.js:

export default {
    accountDetails: {
        stateOfResidenceId: '',
        accountType: '',
        accountNumber: '',
        product: ''
    },
    forms: {
        blueprints: [

        ]
    }
};

formsReducer.js:

import * as types from '../constants/actionTypes';
import objectAssign from 'object-assign';
import initialState from './initialState';
import formsHelper from '../utils/FormsHelper';
export default function formsReducer(state = initialState.forms, action) {
  switch (action.type) {
    case types.UPDATE_PRODUCT: {
        //I NEED accountDetails.stateOfResidenceId HERE
        console.log(state);
        const formBlueprints = formsHelper.getFormsByProductId(action.product.id);
        return objectAssign({}, state, {blueprints: formBlueprints});
    }

    default:
      return state;
  }
}

index.js (redutor de raiz):

import { combineReducers } from 'redux';
import accountDetails from './accountDetailsReducer';
import forms from './formsReducer';

const rootReducer = combineReducers({
    accountDetails,
    forms
});

export default rootReducer;

Como posso acessar este campo?

Kibowki
fonte
8
Essa afirmação não faz nenhum sentido. O objetivo de uma função redutora é que você toma decisões com base no estado atual e na ação.
markerikson

Respostas:

105

Eu usaria thunk para isso, aqui está um exemplo:

export function updateProduct(product) {
  return (dispatch, getState) => {
    const { accountDetails } = getState();

    dispatch({
      type: UPDATE_PRODUCT,
      stateOfResidenceId: accountDetails.stateOfResidenceId,
      product,
    });
  };
}

Basicamente, você obtém todos os dados de que precisa sobre a ação e, em seguida, pode enviar esses dados ao seu redutor.

Crysfel
fonte
4
Alguma ideia de por que o estado atual não é exposto ao redutor pelo próprio REDUX ..? parece estranho que, ao despachar, eu precise adicionar coisas irrelevantes ao redutor apenas para que o estado inicial não substitua o estado atual de outras coisas não relacionadas à chamada de despacho atual
vsync
10

Suas opções são escrever mais lógica além de apenas usar combineReducersou incluir mais dados na ação. O Redux FAQ cobre este tópico:

https://redux.js.org/faq/reducers/

Além disso, estou atualmente trabalhando em um novo conjunto de páginas para os documentos do Redux no tópico "Estruturação de redutores", que você pode achar útil. As páginas WIP atuais estão em https://github.com/markerikson/redux/blob/structuring-reducers-page/docs/recipes/StructuringReducers.md .

marcadorikson
fonte
Obrigado pela sua resposta. Eu explorei alguns dos recursos que você vinculou e continuarei a fazê-lo. No entanto, pergunte para você, já que você parece ter conhecimento. Outra resposta sugeriu o uso de thunk. Você acha que essa seria uma boa solução para o meu problema? Se isso vai bagunçar meu código ou não é uma prática recomendada, não é uma solução que desejo buscar, mas não me sinto bem informado para determinar os efeitos de longo prazo dessas escolhas.
kibowki 01 de
3
Sim, thunks são a abordagem básica padrão para gerenciar efeitos colaterais e executar lógica complexa que envolve vários despachos e usar o estado atual do aplicativo. É totalmente normal escrever uma função thunk que leia o estado atual do aplicativo e faça coisas como despachar condicionalmente, despachar várias ações ou capturar parte do estado e incluir isso em uma ação despachada. Tenho alguns exemplos de padrões de conversão comuns em gist.github.com/markerikson/ea4d0a6ce56ee479fe8b356e099f857e .
markerikson
2

Não tenho certeza se essa abordagem é um antipadrão, mas funcionou para mim. Use uma função curried em suas ações.

export const myAction = (actionData) => (dispatch, getState) => {
   dispatch({
      type: 'SOME_ACTION_TYPE',
      data: actionData,
      state: getState()
   });
}
user3377090
fonte
1

É simples escrever sua própria função de combinação que faz exatamente o que você deseja:

import accountDetails from './accountDetailsReducer';
import forms from './formsReducer';

const rootReducer = (state, action) => {
        const newState = {};

        newState.accountDetails = accountDetails(state.accountDetails, action);
        newState.forms = forms(state.forms, action, state.accountDetails);

        return newState;
    };

export default rootReducer; 

Seu FormReducer seria então:

export default function formsReducer(state = initialState.forms, action, accountDetails) {

O formsReducer agora tem acesso a accountDetails.

O benefício dessa abordagem é que você só expõe as fatias de estado de que precisa, em vez de todo o estado.

GhostEcho
fonte
0

Sugiro que você passe para o criador da ação. Então, em algum lugar você terá um criador de ação que faz algo assim:

updateProduct(arg1, arg2, stateOfResidenceId) {
  return {
    type: UPDATE_PRODUCT,
    stateOfResidenceId
  }
}

No local onde você aciona a ação, suponha que esteja usando o react, você pode usar

function mapStateToProps(state, ownProps) {
  return {
    stateOfResidenceId: state.accountdetails.stateOfResidenceId
  }  
}

e conecte-se ao seu componente react usando a conexão react-redux.

connect(mapStateToProps)(YourReactComponent);

Agora, em seu componente de reação onde você aciona a ação updateProduct, você deve ter o stateOfResidenceId como um suporte e pode passá-lo para o criador da ação.

Parece complicado, mas na verdade se trata de separação de interesses.

Xing Wang
fonte
0

Você pode tentar usar:

redux-nomeados-redutores

O que permite que você obtenha o estado em qualquer lugar em seu código, como:

const localState1 = getState(reducerA.state1)
const localState2 = getState(reducerB.state2)

Mas pense primeiro se seria melhor passar o estado externo como uma carga útil na ação.

miles_christian
fonte
0

Uma maneira alternativa, se você usar o react-redux e precisar dessa ação apenas em um lugar OU está bem com a criação de um HOC (componente superior de oder, realmente não precisa entender que o importante é que isso pode inchar seu html) em todos os lugares que você precisar esse acesso é para usar mergeprops com os parâmetros adicionais sendo passados ​​para a ação:

const mapState = ({accountDetails: {stateOfResidenceId}}) => stateOfResidenceId;

const mapDispatch = (dispatch) => ({
  pureUpdateProduct: (stateOfResidenceId) => dispatch({ type: types.UPDATE_PRODUCT, payload: stateOfResidenceId })
});

const mergeProps = (stateOfResidenceId, { pureUpdateProduct}) => ({hydratedUpdateProduct: () => pureUpdateProduct(stateOfResidenceId )});

const addHydratedUpdateProduct = connect(mapState, mapDispatch, mergeProps)

export default addHydratedUpdateProduct(ReactComponent);

export const OtherHydratedComponent = addHydratedUpdateProduct(OtherComponent)

Quando você usa mergeProps, o que você retorna será adicionado aos props, mapState e mapDispatch servirão apenas para fornecer os argumentos para mergeProps. Então, em outras palavras, esta função irá adicionar isso aos seus componentes (sintaxe de texto digitado):

{hydratedUpdateProduct: () => void}

(observe que a função realmente retorna a própria ação e não nula, mas você irá ignorar isso na maioria dos casos).

Mas o que você pode fazer é:

const mapState = ({ accountDetails }) => accountDetails;

const mapDispatch = (dispatch) => ({
  pureUpdateProduct: (stateOfResidenceId) => dispatch({ type: types.UPDATE_PRODUCT, payload: stateOfResidenceId })
  otherAction: (param) => dispatch(otherAction(param))
});

const mergeProps = ({ stateOfResidenceId, ...passAlong }, { pureUpdateProduct, ... otherActions}) => ({
  ...passAlong,
  ...otherActions,
  hydratedUpdateProduct: () => pureUpdateProduct(stateOfResidenceId ),
});

const reduxPropsIncludingHydratedAction= connect(mapState, mapDispatch, mergeProps)

export default reduxPropsIncludingHydratedAction(ReactComponent);

isso fornecerá o seguinte material aos adereços:

{
  hydratedUpdateProduct: () => void,
  otherAction: (param) => void,
  accountType: string,
  accountNumber: string,
  product: string,
}

No geral, embora a desaprovação completa dos redux-mantenedores mostram para expandir a funcionalidade de seu pacote para incluir tais desejos de uma boa maneira, o que criaria um padrão para essas funcionalidades SEM apoiar a fragmentação do ecossistema, é impressionante.

Pacotes como o Vuex, que não são tão teimosos, não têm tantos problemas com pessoas abusando de antipadrões porque eles se perdem, ao mesmo tempo em que oferecem uma sintaxe mais limpa com menos clichê do que você jamais arquivará com redux e os melhores pacotes de suporte. E apesar do pacote ser muito mais versátil, a documentação é mais fácil de entender porque eles não se perdem nos detalhes como a documentação do redux tende a fazer.

Sam96
fonte
-1

Ao despachar uma ação, você pode passar um parâmetro. Nesse caso, você poderia passar accountDetails.stateOfResidenceIdpara a ação e depois passá-la para o redutor como carga útil.

ddavy
fonte