React-redux-starter-kit: Populating a Component On Load

Created on 12 May 2016  路  8Comments  路  Source: davezuko/react-redux-starter-kit

What's the cleanest way to populate a component at load time? Lets say I have a fractal section with a main container and an action that fetches all the data for a page (fetchAll).

import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

import actions from '../modules'

import MainComponent from '../components/MainComponent'

class MainContainer extends Component {

  //
  // ??? BEST PLACE TO FETCH ALL ??
  //
  componentWillMount() {
    this.props.actions.fetchAll()
  }

  render() {
    return (
      <MainComponent {...this.props} />
    )
  }
}

const mapStateToProps = (state) => ({
  users: state.users
})

const mapDispatchToProps(dispatch) => ({
  actions: bindActionCreators(actions, dispatch)
})

export default connect(mapStateToProps, mapDispatchToProps)(MainContainer)

Is the best place to put a method that loads all the content for a page at componentWillMount? Will this cause any type of double rendering / performance hit?

Most helpful comment

The docs for nextState are kind of lacking.

yea it was recently added in 2.2.0... nextState has same signature as RouterState:

type RouterState = {
  location: Location,
  routes: Array<Route>,
  params: Params,
  components: Array<Component>
}

Can you elaborate

surely, here's some code...

import { injectReducer } from '../../store/reducers'

export default (store) => ({
  path: 'example/:id',
  getComponent (nextState, cb) {
    require.ensure([], (require) => {

      // side note: *always* capitalize react components! it's a widely used
      // heuristic for detecting components in things like babel plugins
      const MainContainer = require('./containers/Main').default

      const actions = require('./modules').actions
      const reducer = require('./modules').reducers

      // async reducer contains state management logic for this fetch,
      // ie. FETCH_EXAMPLE_START => state.ui.loading = true
      injectReducer(store, { key: 'example', reducer })

      // use param from nextState to fetch specific example by id
      store.dispatch(actions.fetch(nextState.params.exampleId))

      // return component immediately with fetch in flight (non blocking)
      cb(null, MainContainer)

      // alternatively, you could use redux-thunk to block transition
      // if you define an onError handler on Router itself, something like...
      store
        .dispatch(actions.fetch(nextState.params.exampleId)) // set global spinner
        .then(() => cb(null, MainContainer)) // success, data loaded, render component
        .catch(cb) // return error to router (or render NotFoundComponent)
    })
  }
})

i hope this helps!

All 8 comments

avoid using component lifecycle methods in general, especially for data loading

a simple solution would be dispatching an action to fetch data in the route definition's getComponent callback, which is somewhat unique to this structure. the router will pass nextState for accessing things like route params. avoid blocking and set appropriate state so your component can render a spinner etc.

you should not need jsx at all in the container component

Excellent. Would the following be "kosher"?

import { injectReducer } from '../../store/reducers'

export default (store) => ({
  path: 'example',
  getComponent (nextState, cb) {
    require.ensure([], (require) => {
      const container = require('./containers/Main').default
      const actions = require('./modules').actions
      const reducer = require('./modules').reducers

      injectReducer(store, { key: 'example', reducer })

      // Call fetchAll
      store.dispatch(actions.fetchAll())

      cb(null, container)
    })
  }
})

Can you elaborate on "the router will pass nextState for accessing things like route params. avoid blocking and set appropriate state so your component can render a spinner etc." The docs for nextState are kind of lacking.

The docs for nextState are kind of lacking.

yea it was recently added in 2.2.0... nextState has same signature as RouterState:

type RouterState = {
  location: Location,
  routes: Array<Route>,
  params: Params,
  components: Array<Component>
}

Can you elaborate

surely, here's some code...

import { injectReducer } from '../../store/reducers'

export default (store) => ({
  path: 'example/:id',
  getComponent (nextState, cb) {
    require.ensure([], (require) => {

      // side note: *always* capitalize react components! it's a widely used
      // heuristic for detecting components in things like babel plugins
      const MainContainer = require('./containers/Main').default

      const actions = require('./modules').actions
      const reducer = require('./modules').reducers

      // async reducer contains state management logic for this fetch,
      // ie. FETCH_EXAMPLE_START => state.ui.loading = true
      injectReducer(store, { key: 'example', reducer })

      // use param from nextState to fetch specific example by id
      store.dispatch(actions.fetch(nextState.params.exampleId))

      // return component immediately with fetch in flight (non blocking)
      cb(null, MainContainer)

      // alternatively, you could use redux-thunk to block transition
      // if you define an onError handler on Router itself, something like...
      store
        .dispatch(actions.fetch(nextState.params.exampleId)) // set global spinner
        .then(() => cb(null, MainContainer)) // success, data loaded, render component
        .catch(cb) // return error to router (or render NotFoundComponent)
    })
  }
})

i hope this helps!

@justingreenberg that's perfect... Thanks so much for the help.

@justingreenberg saves the day as usual.

Closing since there's no additional action to be taken in this project.

It may be worth adding this one to FAQ? :) even though its not specific to this tutorial.

I am finding a lot of answers for my questions as closed issues here. It may be worth to link some of them to "Newbies" section of FAQ. So people like me can find them easier :)

@justingreenberg

avoid using component lifecycle methods in general, especially for data loading

What about a scenario where we are trying to dynamically load a list of rooms in a side navigation pane and the data that needs to load isn't route specific?

The navigation pane is a container, I could imagine sending a dispatch on ComponentDidMount to populate the nav items would work.

I only want to load the nav items once, when the page initially loads, regardless of the current route.

What is your suggestion in that case?

@kylesavant I think you might want the data be in the top-level store, the pass the data as items props in your navigation.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

postalservice14 picture postalservice14  路  4Comments

marcelloromanelli picture marcelloromanelli  路  5Comments

nie-xin picture nie-xin  路  4Comments

BigPrimeNumbers picture BigPrimeNumbers  路  4Comments

rpribadi-ds picture rpribadi-ds  路  5Comments