1

What is the best way to call a dispatch to get initial data on a React component. My understanding is that ComponentWillMount is called before render. So in theory if I call dispatch on ComponentWillMount, by the time I hit render and then ComponentDidMount I should have my data in the component's props, right? I'm not seeing that.

I'm seeing that render gets called twice and that on the first go when the component is being initialized, I cannot access the data in props. It also seems like dispatch does not actually get called until the second render. I'm basically looking to have some light shed on the best way to call a dispatch when initially setting up a component. I'm essentially trying to do something like the following where I use a container component to get my data from dispatch and then pass it to a child component as props. But I also want to initialize some state variables in the ContainerComponent and then pass them to the ChildComponent as props. The thing is that the state variables I want to initialize depend on the data returned from dispatch and ideally I would do the initialization in ComponentWillMount or ComponentDidMount.

import React from 'react'; import axios from 'axios'; import { connect } from 'react-redux'; import ChildComponent from './ChildComponent.js'; import { getTransactionsAll } from '../actions/actions.js'; class ContainerComponent extends React.Component { constructor() { super(); this.state = { acctList:[], acctChecked:[], categoryList:[] } } componentWillMount() { console.log("componentWillMount entered"); this.props.get_data(); console.log(this.props.searchProps.transactions_all);//this is undefined meaning the dispatch has not assigned the data yet...?? } componentDidMount() { console.log("componentDidMount entered"); console.log(this.props.searchProps.transactions_all);//this is undefined meaning the dispatch has not assigned the data yet...?? } render() { console.log("TransactionManagerContainer render entered"); console.log(this.props.searchProps.transactions_all);//this is undefined the first time around meaning the dispatch has not assigned the data yet...??, but is defined on the second call to render after the dispatch has actually occurred... return <ChildComponent data={this.props.searchProps.data}/>; } const mapStateToProps = (state) => ({ searchProps: state.searchProps }); export default connect(mapStateToProps, {getTransactionsAll})(TransactionManagerContainer); 

Here is my reducer that assigns the state:

import { combineReducers } from 'redux' import {GET_TRANSACTIONS } from '../actions/actions.js' import {GET_TRANSACTIONS_ALL } from '../actions/actions.js' const INITIAL_STATE = { defaultYear: 2016, transactions: []}; function get_transactions(state = INITIAL_STATE, action) { // console.log("this is in the reducer: get_transactions"); // console.log(action); switch(action.type) { case GET_TRANSACTIONS: // return { ...state, transactions: action.payload }; return Object.assign({}, state, { transactions: action.payload, selectedYear: action.selectedYear }) default: return state; } } function get_transactions_all(state = INITIAL_STATE, action) { console.log("this is the value of action in the reducer: get_transactions_all"); console.log(action); switch(action.type) { case GET_TRANSACTIONS_ALL: // return { ...state, transactions: action.payload }; return Object.assign({}, state, { transactions_all: action.payload }) console.log("this is the value of state in the reducer after being set"); console.log(state); default: return state; } } const rootReducer = combineReducers({ //stateProps: get_transactions, searchProps: get_transactions_all }) export default rootReducer 

Here are my actions:

import axios from 'axios'; export const GET_TRANSACTIONS = 'GET_TRANSACTIONS'; export function getTransactions(year) { return function(dispatch) { axios.get(`http://localhost:3001/api/transfilter?year=${year}&grouping=2`) .then(response => { dispatch({ type: GET_TRANSACTIONS, payload: response.data, selectedYear: year }); }) .catch((error) => { console.log(error); }) } } export const GET_TRANSACTIONS_ALL = 'GET_TRANSACTIONS_ALL'; export function getTransactionsAll(year) { return function(dispatch) { axios.get(`http://localhost:3001/api/trans?limit=20`) .then(response => { dispatch({ type: GET_TRANSACTIONS_ALL, payload: response.data }); }) .catch((error) => { console.log(error); }) } } 
7
  • in a react/nuclearjs project we're calling the fetch API data method in componentDidMount so that should be working. can you should us how you're using the component in an HTML template? Commented Jan 6, 2017 at 21:56
  • The fetch works. Everything works. I just want to know the best way to do it and get a better understanding of the dispatch "lifecycle". As in when does the fetch actually get called because it does not seem to be right away since the render is called twice and the data is not available until the second time render is called. Commented Jan 6, 2017 at 22:12
  • where are you passing searchProps. You should show this containers parent component and your get_data function Commented Jan 6, 2017 at 23:06
  • Edits made to include all the files (actions, reducer, etc). I'm passing searchProps from my reducer. Commented Jan 6, 2017 at 23:24
  • I think I misunderstand your question. Are you asking why your data only exists on the second render? Commented Jan 7, 2017 at 1:08

1 Answer 1

5

I believe your main question is:

What is the best way to call a dispatch to get initial data on a React component?

Getting initial data requests (or any AJAX requests in general) should go in the componentDidMount lifecycle event.

There are a few reasons for this, here are two important:

  1. Fiber, the next implementation of React’s reconciliation algorithm, will have the ability to start and stop rendering as needed for performance benefits. One of the trade-offs of this is that componentWillMount, the other lifecycle event where it might make sense to make an AJAX request, will be “non-deterministic”. What this means is that React may start calling componentWillMount at various times whenever it feels like it needs to. This would obviously be a bad formula for AJAX requests.

  2. You can’t guarantee the AJAX request won’t resolve before the component mounts. If it did, that would mean that you’d be trying to setState on an unmounted component, which not only won’t work, but React will yell at you for. Doing AJAX in componentDidMount will guarantee that there’s a component to update.

Credits: I learned that from here, there is also a discussion here.

Then, there are a lot of smaller question you've raised and it will be hard for me to answer all, but I'll try to cover most:

  • After reading the above, you now should understand why your data is undefined in componentWillMount and componentDidMount. That's simply because the data has not arrived yet;
  • It's normal that your data is undefined during the first render of the component. Initial render happens before data arrival;
  • It's normal that the data is defined during the second render. The dispatch triggers asynchronous data fetch. Right after data comes, a reducer is hit and component gets re-rendered (that's the second re-render).
  • If the child components in your main component require the data - check in the parent render method if data exists pass internal components conditionally, only if data is present. Like so:

    class ContainerComponent extends React.Component { // ... omitted for brevity render() { return ( { this.props.searchProps.data ? <ChildComponent data={this.props.searchProps.data} /> : <p>Loading</p> } ); } } 
Sign up to request clarification or add additional context in comments.

13 Comments

Thanks for the conceptual explanations. It makes sense. So I copied and pasted the render you have above and I get an error: 111 | return ( > 112 | { this.props.searchProps.data? | ^ 113 | <TransactionManagerView 114 | transactions={this.props.searchProps.data} /> 115 | : <p>Loading</p> It is not cleanly formatted, not sure how to formate in comments. But essentially I get a syntax error unexpected token where the period is on the first call to this.props.
You're welcome. Hm, I am not sure - the error doesn't speak much to me. Maybe the issue is caused by this.props.searchProps being undefined. It's usually a good practice to set defaults to props that initially doesn't exist. Because otherwise, they will be always undefined during the first render. Declare default props and see if the issue is resolved.
Thanks. I did that as you suggested in my other post that you responded to. And there too, setting default props is not working. I'm stumped. It seems as though my default props are being ignored. I've even gone to my babel.rc and made sure I have the 2016 initializers set. I've tried both the static method of setting default props in the constructor and doing it outside the class using Component.defaultProps. Neither seem to work cause I'm initially still getting undefined.
I am starting to think that there are too many places in your use-case that might cause troubles. My advice is that you split each question you have, try to reproduce it via simpler example, try to create a jsfiddle. Otherwise - it will be hard for me or anybody else to help :) PS: See my edit in the other question you asked. There is a huge chance the two issues you see are co-related and my answer there to help you to resolve both.
I still have a question that is relevant to this post and was part of my initial question. What is the right way or best way to manipulate state based on the data returned from Redux? If I can only access the data on the second render, then I can't assign state in my component in the render because it will cause an infinite loop. For example in the data returned from Redux I want to get a list of the distince account types and put than in an acctList state object that is local to my ContainerComponent. I understand I could do it in Redux, but I want to isolate this to the ContainerComponent.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.