Redux: How can we access redux in componentDidMount with react-router 1.0?

Created on 9 Jul 2015  Â·  16Comments  Â·  Source: reduxjs/redux

Hi,

I feel that this question is maybe half a redux question for the best practices of how to use redux and half a react-router question for the technical part regarding how to pass props to child components.

I use redux 0.12 and react-router 1.0 beta.

I am having a hard time figuring how to access redux and to read in its store from componentDidMount.

Here is an extract of the code I use:

./routes.jsx

export default (
  <Route name="app" component={require('./app.jsx')}>
    <Route path="/" name="home" component={require('./home-page-container.jsx')}/>
  </Route>
)

./client.js

import * as stores from './stores.js'
import { history } from 'react-router/lib/BrowserHistory'
import routes from './routes.jsx'
import { Provider } from 'redux/react'

var redux = createRedux(stores, window.__INITIAL_STATE__);

React.render(
  <Provider redux={redux}>
    { () => <Router history={history} children={routes} /> }
  </Provider>,
  document.getElementById('app-wrapper')
)

./app.jsx

export default class App extends React.Component {
  render() {
    return (
        <div>
            { this.props.children }
        </div>
    );
  }
}

./home-page-container.js

import HomePage from './home-page.jsx';
import  * as HomeActions from './home-actions.js';
import { bindActionCreators } from 'redux';
import { Connector } from 'redux/react';

function select(state) {
  return { list: state.home.list };
}

export default class HomePageContainer extends React.Component {
  static fetchData(redux) {
    var homeActions = bindActionCreators(HomeActions, redux.dispatch);
    return Promise.all([
      homeActions.loadList()
    ]);
  }

  componentDidMount() {
    /*
        I'd like to access redux here and read in its store (synchronously) to see if 
        I need to call an action to fetch some data (asynchronously)
        Ex:
        if (!redux.getState().home.list) {
          this.constructor.fetchData(redux);
        }
    */
  }

  render() {
    return (
      <Connector select={select}>
        { ({ list, dispatch }) => <HomePage list={ list } /> }
      </Connector>
    );
  }
}

With react-router 0.13, I could pass redux as a prop to the Handler chosen by the router but with react-router 1.0 there is no such thing as a Handler component to pass props to.

Example

With react-router 0.13 I could do:
React.render(
  <Handler redux={redux} />,
  document.getElementById('app-wrapper')
)

and if Handler == HomePageContainer I could access this.props.redux in HomePageContainer::componentDidMount

But with react-router 1.0 we now have
React.render(
  <Provider redux={redux}>
    { () => <Router history={history} children={routes} /> }
  </Provider>,
  document.getElementById('app-wrapper')
)

and if App::this.props.children == HomePageContainer I don't know how to access redux in HomePageContainer::componentDidMount.

I didn't even figured out how the association Router / Provider is working (I guess I lack some understanding of react router 1.0 itself here).

So here are my questions:

  • How can I pass redux to HomePageContainer?
  • Is it supposed to be automatically done by the redux Provider somehow?
  • How can I tell the router that I want to pass props (like redux) to the component that will passed in App::this.props.children?
  • Am I going all the wrong way about this and shouldn't I be trying to read from redux store to initiate data fetching from componentDidMount (I was doing so with flummox but maybe it's different with redux)?

Sorry for the many questions and thanks for any help!

help wanted

Most helpful comment

Am I going all the wrong way about this and shouldn't I be trying to read from redux store to initiate data fetching from componentDidMount (I was doing so with flummox but maybe it's different with redux)?

Indeed, you're going a slightly wrong way.
There are two better solutions than reading from context directly.

Alternative 1: Use connect decorator instead of Connector.

We're probably going to remove Connector anyway in favor of a more powerful connect-centric API.

The reason you should use connect on your container component is that _then you can access this.props.list and this.props.dispatch right from it_ instead of trying to read the state from a Redux store instance.

Something like:

import HomePage from './home-page.jsx';
import  * as HomeActions from './home-actions.js';
import { bindActionCreators } from 'redux';
import { connect } from 'redux/react';

function select(state) {
  return { list: state.home.list };
}

@connect(select)
export default class HomePageContainer extends React.Component {
  static fetchData(dispatch) {
    var homeActions = bindActionCreators(HomeActions, dispatch);
    return Promise.all([
      homeActions.loadList()
    ]);
  }

  componentDidMount() {
    if (!this.props.list) {
      this.constructor.fetchData(this.props.dispatch);
    }
  }

  render() {
    return <HomePage list={this.props.list} />;
  }
}

or, if you don't want to use decorator syntax,

import HomePage from './home-page.jsx';
import  * as HomeActions from './home-actions.js';
import { bindActionCreators } from 'redux';
import { connect } from 'redux/react';

function select(state) {
  return { list: state.home.list };
}

class HomePageContainer extends React.Component {
  static fetchData(dispatch) {
    var homeActions = bindActionCreators(HomeActions, dispatch);
    return Promise.all([
      homeActions.loadList()
    ]);
  }

  componentDidMount() {
    if (!this.props.list) {
      this.constructor.fetchData(this.props.dispatch);
    }
  }

  render() {
    return <HomePage list={this.props.list} />;
  }
}

export default connect(select)(HomePageContainer);

One Step Further: Smarter action creators + middleware

You can use redux-thunk middleware (it used to be included with 0.12, but in 1.0RC it's a separate package) to write “smarter” action creators that can check the current state and exit early.

For example:

// HomeActions.js

export function loadList() {
  // I don't know how you wrote it—maybe using redux-promise or something
}

export function loadListIfNeeded() {
  // This “return a function” form is supported thanks to redux-thunk
  return (dispatch, getState) => {
    if (getState().home.list) {
      return; // Exit early!
    }

    return dispatch(loadList()); // OK, do that loady thing!
  };
}

Now you can dispatch loadListIfNeeded() and avoid a check inside componentDidMount.

All 16 comments

The instance of redux is in the react context, so you can simply access it as:

class Foo extends React.Component {
  static contextTypes = {
    redux: React.PropTypes.object
  }

  componentDidMount () {
    this.context.redux.getState()
  }
}

You might want to look at some of the examples.

Thanks for the quick answer!

I think I saw this in one of the many examples about redux but since I didn't found it the the redux documentation I felt that this was more a hack that the actual good way to go.

Just to make sure I understand correctly, is it the Provider that make the redux instance accessible through react context?

Correct, the instance of redux is put into context in the Provider.

Thanks!

Is this applicable in v1.0.0-rc? I got undefined in context.redux. :(

@mewben

It's called store in 1.0 RC. Use this.context.store.
That said, context is a private API and is not supposed to be used directly.

(@emmenko Better not to suggest this to people :-)

Am I going all the wrong way about this and shouldn't I be trying to read from redux store to initiate data fetching from componentDidMount (I was doing so with flummox but maybe it's different with redux)?

Indeed, you're going a slightly wrong way.
There are two better solutions than reading from context directly.

Alternative 1: Use connect decorator instead of Connector.

We're probably going to remove Connector anyway in favor of a more powerful connect-centric API.

The reason you should use connect on your container component is that _then you can access this.props.list and this.props.dispatch right from it_ instead of trying to read the state from a Redux store instance.

Something like:

import HomePage from './home-page.jsx';
import  * as HomeActions from './home-actions.js';
import { bindActionCreators } from 'redux';
import { connect } from 'redux/react';

function select(state) {
  return { list: state.home.list };
}

@connect(select)
export default class HomePageContainer extends React.Component {
  static fetchData(dispatch) {
    var homeActions = bindActionCreators(HomeActions, dispatch);
    return Promise.all([
      homeActions.loadList()
    ]);
  }

  componentDidMount() {
    if (!this.props.list) {
      this.constructor.fetchData(this.props.dispatch);
    }
  }

  render() {
    return <HomePage list={this.props.list} />;
  }
}

or, if you don't want to use decorator syntax,

import HomePage from './home-page.jsx';
import  * as HomeActions from './home-actions.js';
import { bindActionCreators } from 'redux';
import { connect } from 'redux/react';

function select(state) {
  return { list: state.home.list };
}

class HomePageContainer extends React.Component {
  static fetchData(dispatch) {
    var homeActions = bindActionCreators(HomeActions, dispatch);
    return Promise.all([
      homeActions.loadList()
    ]);
  }

  componentDidMount() {
    if (!this.props.list) {
      this.constructor.fetchData(this.props.dispatch);
    }
  }

  render() {
    return <HomePage list={this.props.list} />;
  }
}

export default connect(select)(HomePageContainer);

One Step Further: Smarter action creators + middleware

You can use redux-thunk middleware (it used to be included with 0.12, but in 1.0RC it's a separate package) to write “smarter” action creators that can check the current state and exit early.

For example:

// HomeActions.js

export function loadList() {
  // I don't know how you wrote it—maybe using redux-promise or something
}

export function loadListIfNeeded() {
  // This “return a function” form is supported thanks to redux-thunk
  return (dispatch, getState) => {
    if (getState().home.list) {
      return; // Exit early!
    }

    return dispatch(loadList()); // OK, do that loady thing!
  };
}

Now you can dispatch loadListIfNeeded() and avoid a check inside componentDidMount.

@gaearon thanks a lot for your insights!

Regarding the alternative 1

I actually started using the connect decorator but I had to go back to the Connector instead because I need to access to a params props (written by react-router) to extract the correct data from redux state.

For example:

import HomePage from './home-page.jsx';
import  * as HomeActions from './home-actions.js';
import { bindActionCreators } from 'redux';
import { Connector } from 'redux/react';

function select(state) {
  return { list: state.home.list[this.props.params.listID] };
}

// ...

  render() {
    return (
      <Connector select={select.bind(this)}>
        { ({ list, dispatch }) => <HomePage list={ list } /> }
      </Connector>
    );
  }
}

Is it possible to achieve similar extraction of props using the connect decorator? Isn't the decorator only limited to access class variables and methods (instead of instance variables and methods)?

And I see that you're using this.props.dispatch in componentDidMount but don't I need to access redux from context to get this dispatch method?

Regarding the alternative 2

I am using a promise middleware and I imagine that I could call the thunk middleware before it to achieve what you suggest. I really like the idea of avoiding the test in componentDidMount and I guess I could pass props to the action if I need to take a decision that depends on props. Am I correct?

Again, thanks for the time you took to explain and thanks for redux! :)

Is it possible to achieve similar extraction of props using the connect decorator? Isn't the decorator only limited to access class variables and methods (instead of instance variables and methods)?

With connect decorator, you'll get the instance's props as the second parameter to your select function.

Thanks @gaearon. I chose not to use connect since I'm only after an Action and not load any store like this:

const actions = bindActionCreators(AppActions, this.context.store.dispatch);
actions.login(credentials);

or maybe you have a more beautiful approach?

@mewben

Just noting that you don't have to use bindActionCreators if you don't pass the result object down.
You can just call dispatch directly:

this.context.store.dispatch(actions.login(credentials))

Ahh I see.. Thank you very much.. :)

@gaearon

Thanks, I didn't knew about the connect second parameter. I am still wondering how you get that dispatch you're using in this.constructor.fetchData(this.props.dispatch);. Do I have to add it in a parent container at some point, extracting it from context.redux?

@happypoulp That's what @connect decorator injects into your component.

Thanks a lot!

Also connect is used to subscribe your component to changes.

But as @gaearon mentioned, the new upcoming connect API will be more powerful.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ramakay picture ramakay  Â·  3Comments

cloudfroster picture cloudfroster  Â·  3Comments

olalonde picture olalonde  Â·  3Comments

benoneal picture benoneal  Â·  3Comments

ilearnio picture ilearnio  Â·  3Comments