zorza
zorza

Reputation: 2884

Run reducer after state is updated by another reducer

Let's say I've got an app with two reducers - tables and footer combined using combineReducers().

When I click on some button two actions are being dispatched - one after another: "REFRESH_TABLES" and "REFRESH_FOOTER".

tables reducer is listening for the first action and it modifies the state of tables. The second action triggers footer reducer. The thing is it needs current state of tables in order to do it's thing.

My implementation looks something like below.

Button component:

import React from 'react';

const refreshButton = React.createClass({
    refresh () {
        this.props.refreshTables();
        this.props.refreshFooter(this.props.tables);
    },
    render() {
        return (
            <button onClick={this.refresh}>Refresh</button>
        )
    }
});

export default refreshButton;

ActionCreators:

export function refreshTables() {
    return {
        type: REFRESH_TABLES
    }
}

export function refreshFooter(tables) {
    return {
        type: REFRESH_FOOTER,
        tables
    }
}

The problem is that the props didn't update at this point so the state of tables that footer reducer gets is also not updated yet and it contains the data form before the tables reducer run.

So how do I get a fresh state to the reducer when multiple actions are dispatched one after another from the view?

Upvotes: 0

Views: 1317

Answers (2)

kwelch
kwelch

Reputation: 2469

Although splitting it asynchronous may help, the issue may be in the fact that you are using combineReducers. You should not have to rely on the tables from props, you want to use the source of truth which is state.

You need to look at rewriting the root reducer so you have access to all of state. I have done so by writing it like this.

const rootReducer = (state, action) => ({ tables: tableReducer(state.tables, action, state), footer: footerReducer(state.footer, action, state) });

With that you now have access to full state in both reducers so you shouldn't have to pass it around from props.

Your reducer could then looks like this.

const footerReducer = (state, action, { tables }) => { ... };

That way you are not actually pulling in all parts of state as it starts to grow and only access what you need.

Upvotes: 1

Lionel T
Lionel T

Reputation: 1597

Seems you need to handle the actions async so you can use a custom middleware like redux-thuk to do something like this:

actions.js

function refreshTables() {
  return {
    type: REFRESH_TABLES
  }
}

function refreshFooter(tables) {
  return {
    type: REFRESH_FOOTER,
    tables
  }
}

export function refresh() {
  return function (dispatch, getState) {
    dispatch(refreshTables())
      .then(() => dispatch(refreshFooter(getState().tables)))
  }
}

component

const refreshButton = React.createClass({
  refresh () {
    this.props.refresh();
  },
  {/* ... */}
});

Upvotes: 2

Related Questions