Search code examples
reactjsreduxreact-reduxredux-thunk

Initialize component with Async data


I'm trying to figure out how and where to load the data (ie call dispatch on my action) for my select box in react + redux + thunk. I'm not sure if it should go in the constructor of my App container, or should i load it inside my component (in my example: "MyDropdown")

My main App:

import MyDropdown from '../components/mydropdown';
// Should i import my action here and then...
// import { loadData } from '../actions';

class App extends Component {
  render() {
    return (
      <div className="page-content">
        <div className="option-bar">
          // SEND it as a PROP inside MyDropdown... 
          <MyDropdown />
        </div>
      </div>
    );
  }
}
export default App;

My Component

// OR.. Should i load it in my MyDropdown component here?
import { loadData } from '../actions';

class MyDropdown extends Component {
  // If i load it here on load, how do i do it?
  render() {
    return(
      <select>
         {renderOptions()}
      </select>
    );
  }
}

I've tried componentDidMount() inside my App class, but it didnt seem to work. It seems to make sense to put the initialize data and call to actions there as it'll be all centralized, instead of calling actions inside my child components. Also, i'll have multiple select boxes that need to be loaded on startup, so my App class might grow quite a bit, is that the correct way to do it? I'm not sure what the best practice is as i've only just started learning react.


Solution

  • You should separate data components from presentation components (see post here).

    So in your small example, MyDropdown should be passed all the data it needs in order to render the component. That would mean fetching the data in App (or some parent component of the component actually rendering the view.

    Since you're working with React and Redux, the react-redux library provides a helper function to generate containers that fetch the data required for your presentation component.

    To do that, change App to:

    import { connect } from 'react-redux'
    import MyDropdown from '../components/mydropdown';
    import { loadData } from '../actions';
    
    // This class is not exported
    class App extends Component {
      componentDidMount() {
        this.props.loadData()
      }
      render() {
        return (
          <div className="page-content">
            <div className="option-bar">
              <MyDropdown data={this.props.data}/>
            </div>
          </div>
        );
      }
    }
    
    function mapStateToProps(state) {
      const { data } = state
      return {
        data
      }
    }
    
    function mapDispatchToProps(dispatch) {
      return {
        loadData(){
          dispatch(loadData())
        }
      }
    }
    
    // Export a container that wraps App
    export default connect(mapStateToProps, mapDispatchToProps)(App);
    

    Alternatively, you could keep App the same and change MyDropdown to:

    import { connect } from 'react-redux'
    import { loadData } from '../actions';
    
    // Exporting this allows using only the presentational component
    export class MyDropdown extends Component {
      componentDidMount() {
        this.props.loadData()
      }
      render() {
        return(
          <select>
             {renderOptions(this.props.data)}
          </select>
        );
      }
    }
    
    function mapStateToProps(state) {
      const { data } = state
      return {
        data
      }
    }
    
    function mapDispatchToProps(dispatch) {
      return {
        loadData(){
          dispatch(loadData())
        }
      }
    }
    
    // By default, export the container that wraps the presentational component
    export default connect(mapStateToProps, mapDispatchToProps)(MyDropdown);
    

    In both cases, look at what is actually being exported as default at the end. It's not the component; it's the return of connect. That function wraps your presentational component and returns a container that is responsible for fetching the data and calling actions for the presentational component.

    This gives you the separation you need and allows you to be flexible in how you use the presentation component. In either example, if you already have the data you need to render MyDropdown, you can just use the presentation component and skip the data fetch!

    You can see a full example of this in the Redux docs here.