371

Say I have the following:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
  }
}

And in that action creator, I want to access the global store state (all reducers). Is it better to do this:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

or this:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
ffxsam
  • 26,428
  • 32
  • 94
  • 144

8 Answers8

626

There are differing opinions on whether accessing state in action creators is a good idea:

  • Redux creator Dan Abramov feels that it should be limited: "The few use cases where I think it’s acceptable is for checking cached data before you make a request, or for checking whether you are authenticated (in other words, doing a conditional dispatch). I think that passing data such as state.something.items in an action creator is definitely an anti-pattern and is discouraged because it obscured the change history: if there is a bug and items are incorrect, it is hard to trace where those incorrect values come from because they are already part of the action, rather than directly computed by a reducer in response to an action. So do this with care."
  • Current Redux maintainer Mark Erikson says it's fine and even encouraged to use getState in thunks - that's why it exists. He discusses the pros and cons of accessing state in action creators in his blog post Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability.

If you find that you need this, both approaches you suggested are fine. The first approach does not require any middleware:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

However you can see that it relies on store being a singleton exported from some module. We don’t recommend that because it makes it much harder to add server rendering to your app because in most cases on the server you’ll want to have a separate store per request. So while technically this approach works, we don’t recommend exporting a store from a module.

This is why we recommend the second approach:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

It would require you to use Redux Thunk middleware but it works fine both on the client and on the server. You can read more about Redux Thunk and why it’s necessary in this case here.

Ideally, your actions should not be “fat” and should contain as little information as possible, but you should feel free to do what works best for you in your own application. The Redux FAQ has information on splitting logic between action creators and reducers and times when it may be useful to use getState in an action creator.

Remolten
  • 2,614
  • 2
  • 25
  • 29
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • 5
    I have one situation when selecting something in a component might trigger a PUT or a POST, depending if the store contains or not data related to the component. Is it better to put the business logic for PUT/POST selection in the component instead of the thunk-based action creator? – vicusbass May 03 '16 at 08:06
  • 3
    What is the best practice? I'm now facing the similar problem where I'm using getState in my action creator. In my case I use it to determine if values if a form has pending changes (and if so I'll dispatch a action that shows a dialog). – Arne H. Bitubekk Jun 06 '16 at 13:38
  • 51
    It’s fine to read from the store in the action creator. I’d encourage you to [use a selector](https://egghead.io/lessons/javascript-redux-colocating-selectors-with-reducers) so that you don’t depend on the exact state shape. – Dan Abramov Jun 06 '16 at 20:30
  • 2
    I am using a middleware to send data to mixpanel. So I have a meta key inside the action. I need to pass in different variables from the state to the mixpanel. Setting them on the action creators seems to be an anti-pattern. What would be the best approach to handle these kind of use cases? – Aakash Sigdel Sep 21 '16 at 09:35
  • Hai, Should i have to use each and every time by fetch(`${store.getState().authentication.baseurl}/api/getAllHomeWorkRecord`,{}) under actions.Because i may be use bunch of action creators.So my doubt is can i store this baseurl in separate file? if yes, how to connect the variable with store updates? – Johncy Sep 20 '18 at 12:40
  • 3
    Oh man! I didn't knot that redux thunk receive `getState` as the second parameter, I was cracking my head, thank you so so much – Lai32290 Feb 19 '19 at 23:45
  • 1
    Wow this is answered and edited my two heros Dan Abramov and Mark Erikson. – assiegee Aug 27 '19 at 20:56
  • 1
    That last link is broken – Etienne Martin Jan 23 '20 at 04:35
  • 1
    I'm trying to use the same pattern as in your first example but getting odd failures. I think it's due to a cyclic dependency importing store a level up, which in turns imports the reducer which is a sibling file to the actions. Have I hit a weird edge case or is this something you need to be wary of? If you'd like to see an example https://stackoverflow.com/questions/61506762/redux-combinereducers-complaining-when-initialising-for-tests – Ian Apr 29 '20 at 17:43
  • How does this NOT cause cyclical dependencies? You are importing the store and the store imports your reducer which imports your thunk which then imports the store and so on... – Liam Pillay May 04 '23 at 10:39
48

When your scenario is simple you can use

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

But sometimes your action creator need to trigger multi actions

for example async request so you need REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAIL actions

export const [REQUEST_LOAD, REQUEST_LOAD_SUCCESS, REQUEST_LOAD_FAIL] = [`REQUEST_LOAD`
    `REQUEST_LOAD_SUCCESS`
    `REQUEST_LOAD_FAIL`
]
export function someAction() {
    return (dispatch, getState) => {
        const {
            items
        } = getState().otherReducer;
        dispatch({
            type: REQUEST_LOAD,
            loading: true
        });
        $.ajax('url', {
            success: (data) => {
                dispatch({
                    type: REQUEST_LOAD_SUCCESS,
                    loading: false,
                    data: data
                });
            },
            error: (error) => {
                dispatch({
                    type: REQUEST_LOAD_FAIL,
                    loading: false,
                    error: error
                });
            }
        })
    }
}

Note: you need redux-thunk to return function in action creator

Nour Sammour
  • 2,822
  • 1
  • 20
  • 19
  • 3
    Can I just ask if there should be a check on the status of the state 'loading' so that another ajax request isn't made whilst the first one is finishing? – JoeTidee Jan 16 '17 at 02:01
  • @JoeTidee In the example the dispatch of loading state is done. If you make this action with the button for example, you would check if `loading === true` there and disable the button. – croraf Aug 18 '19 at 07:32
5

I agree with @Bloomca. Passing the value needed from the store into the dispatch function as an argument seems simpler than exporting the store. I made an example here:

import React from "react";
import {connect} from "react-redux";
import * as actions from '../actions';

class App extends React.Component {

  handleClick(){
    const data = this.props.someStateObject.data;
    this.props.someDispatchFunction(data);
  }

  render(){
    return (
      <div>       
      <div onClick={ this.handleClick.bind(this)}>Click Me!</div>      
      </div>
    );
  }
}


const mapStateToProps = (state) => {
  return { someStateObject: state.someStateObject };
};

const mapDispatchToProps = (dispatch) => {
  return {
    someDispatchFunction:(data) => { dispatch(actions.someDispatchFunction(data))},

  };
}


export default connect(mapStateToProps, mapDispatchToProps)(App);
Jason Allshorn
  • 1,625
  • 1
  • 18
  • 27
3

I would like to point out that it is not that bad to read from the store -- it might be just much more convenient to decide what should be done based on the store, than to pass everything to the component and then as a parameter of a function. I agree with Dan completely, that it is much better not to use store as a singletone, unless you are 100% sure that you will use only for client-side rendering (otherwise hard to trace bugs might appear).

I have created a library recently to deal with verbosity of redux, and I think it is a good idea to put everything in the middleware, so you have everyhing as a dependency injection.

So, your example will look like that:

import { createSyncTile } from 'redux-tiles';

const someTile = createSyncTile({
  type: ['some', 'tile'],
  fn: ({ params, selectors, getState }) => {
    return {
      data: params.data,
      items: selectors.another.tile(getState())
    };
  },
});

However, as you can see, we don't really modify data here, so there is a good chance that we can just use this selector in other place to combine it somewhere else.

Bloomca
  • 1,784
  • 13
  • 17
1

Presenting an alternative way of solving this. This may be better or worse than Dan's solution, depending on your application.

You can get the state from the reducers into the actions by splitting the action in 2 separate functions: first ask for the data, second act on the data. You can do that by using redux-loop.

First 'kindly ask for the data'

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
    return {
        type: SOME_ACTION,
    }
}

In the reducer, intercept the ask and provide the data to the second stage action by using redux-loop.

import { loop, Cmd } from 'redux-loop';
const initialState = { data: '' }
export default (state=initialState, action) => {
    switch(action.type) {
        case SOME_ACTION: {
            return loop(state, Cmd.action(anotherAction(state.data))
        }
    }
}

With the data in hand, do whatever you initially wanted

export const ANOTHER_ACTION = 'ANOTHER_ACTION';
export function anotherAction(data) {
    return {
        type: ANOTHER_ACTION,
        payload: data,
    }
}

Hope this helps someone.

Andrei Cioara
  • 3,404
  • 5
  • 34
  • 62
0

I know I'm late to the party here, but I came here for opinions on my own desire to use state in actions, and then formed my own, when I realized what I think is the correct behavior.

This is where a selector makes the most sense to me. Your component that issues this request should be told wether it's time to issue it through selection.

export const SOME_ACTION = 'SOME_ACTION';
export function someAction(items) {
  return (dispatch) => {
    dispatch(anotherAction(items));
  }
}

It might feel like leaking abstractions, but your component clearly needs to send a message and the message payload should contain pertinent state. Unfortunately your question doesn't have a concrete example because we could work through a 'better model' of selectors and actions that way.

SqueeDee
  • 21
  • 2
0

I would like to suggest yet another alternative that I find the cleanest, but it requires react-redux or something simular - also I'm using a few other fancy features along the way:

// actions.js
export const someAction = (items) => ({
    type: 'SOME_ACTION',
    payload: {items},
});
// Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction}) => (<div
    onClick={boundSomeAction}
/>);

const mapState = ({otherReducer: {items}}) => ({
    items,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => mappedDispatches.someAction(mappedState.items),
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
// (with  other mapped state or dispatches) Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction, otherAction, otherMappedState}) => (<div
    onClick={boundSomeAction}
    onSomeOtherEvent={otherAction}
>
    {JSON.stringify(otherMappedState)}
</div>);

const mapState = ({otherReducer: {items}, otherMappedState}) => ({
    items,
    otherMappedState,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
    otherAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    const {items, ...remainingMappedState} = mappedState;
    const {someAction, ...remainingMappedDispatch} = mappedDispatch;
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => someAction(items),
        ...remainingMappedState,
        ...remainingMappedDispatch,
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);

If you want to reuse this you'll have to extract the specific mapState, mapDispatch and mergeProps into functions to reuse elsewhere, but this makes dependencies perfectly clear.

Sam96
  • 1,712
  • 1
  • 9
  • 15
0

I wouldn't access state in the Action Creator. I would use mapStateToProps() and import the entire state object and import a combinedReducer file (or import * from './reducers';) in the component the Action Creator is eventually going to. Then use destructuring in the component to use whatever you need from the state prop. If the Action Creator is passing the state onto a Reducer for the given TYPE, you don't need to mention state because the reducer has access to everything that is currently set in state. Your example is not updating anything. I would only use the Action Creator to pass along state from its parameters.

In the reducer do something like:

const state = this.state;
const apple = this.state.apples;

If you need to perform an action on state for the TYPE you are referencing, please do it in the reducer.

Please correct me if I'm wrong!!!