javascript - React/Redux - How to invoke a second function in componentDidMount after the first one finishes fetching AJAX


Keywords:javascript 


Question: 

I have this React-Native app, which fetches a list of items, and then, an image gallery for each of those items. So basically, I have two ajax function, and the second one needs the list of items fetched in the first function. I make the first invoke in componentDidMount(), but I don't know how to "wait" for it to finish to make the second call. If I just place them one after the other, the second one won't do anything because there is no list of items yet. How can I solve this? I read similar questions here on StackOverflow but couldn't find a clear example for this case. Also, in the code, I commented some of my unsuccessful tries.

Thanks in advance, I'm new to React so I can be missing something simple.

component.js

class EtiquetasList extends Component {

  componentDidMount() {
    this.props.FetchEtiquetas();
    if ( this.props.etiquetas.length > 0 ) { // I tried this but didn't do anything
      this.props.FetchGalleries( this.props.etiquetas );
    }
  }

  renderEtiquetas() {
      if ( this.props.galleries.length > 0 ) {
        // if I invoke FetchGalleries here, it works, but loops indefinitely
        return this.props.etiquetas.map(etiqueta =>
          <EtiquetaDetail key={etiqueta.id} etiqueta={etiqueta} galleries={ this.props.galleries } />
        );
      }
  }

  render() {
    return (
      <ScrollView>
        { this.renderEtiquetas() }
      </ScrollView>
    );

  }

}

const mapStateToProps = (state) => {
  return {
    etiquetas: state.data.etiquetas,
    isMounted: state.data.isMounted,
    galleries: state.slides.entries
  };
};

export default connect(mapStateToProps, { FetchEtiquetas, FetchGalleries })(EtiquetasList);

actions.js

export function FetchEtiquetas() {
  return function (dispatch) {
      axios.get( url )
        .then(response => {
          dispatch({ type: FETCH_ETIQUETAS_SUCCESS, payload: response.data })

        }
      );
  }
}

export function FetchGalleries( etiquetas ) {

    return function (dispatch) {
        return Promise.all(
          etiquetas.map( record =>
            axios.get('https://e.dgyd.com.ar/wp-json/wp/v2/media?_embed&parent='+record.id)
        )).then(galleries => {

          let curated_data = [];
          let data_json = '';

          galleries.map( record => {
            record.data.map( subrecord => {
              // this is simplified for this example, it works as intended
              data_json = data_json + '{ title: "' + subrecord.title+'"}';
          });

        my_data.push( data_json );

      });

          return dispatch({ type: FETCH_GALLERIES_SUCCESS, payload: curated_data });

        });
    }

}

reducer.js

export default (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case FETCH_ETIQUETAS_SUCCESS:
      return { ...state, etiquetas: action.payload, isMounted: true }; // I think it ran faster like this tho
    default:
      return state;
  }
};

4 Answers: 

componentWillReceiveProps(nextProps) {
 if(nextProps.etiquetas.length !== 0 && this.props.etiquetas.length === 0) {
   this.props.FetchGalleries( nextProps.etiquetas );
 }
}

I think componentWillReceiveProps lifecycle can do the job easily. You should use componentWillReceiveProps whenever you need to trigger something in update phase of react components

 

You may try modifying your "Fetch" functions to return the Promise object. Then you can chain them togather like this:

export function FetchEtiquetas() {
  return function (dispatch) {
      return axios.get( url ).then(response => {
        dispatch({ type: FETCH_ETIQUETAS_SUCCESS, payload: response.data }) 
      })
  }
}

Invoke in "componentDidMount":

this.props.FetchEtiquetas().then(() => {
  this.props.FetchGalleries( this.props.etiquetas );
})
 

To chain your actions, I would recommend chaining Promise or using async/await if you can.
To do it with Promises, you should return the Promise returned by the request in the action.

export function FetchEtiquetas() {
  return function (dispatch) {
      return axios.get( url )
        .then(response => {
          dispatch({ type: FETCH_ETIQUETAS_SUCCESS, payload: response.data })

        }
      );
  }
}

And use mapDispatchToProps on redux connect call, it should look like

function mapDispatchToProps(dispatch) {
    return {
     fetchData: () => {
         dispatch(FetchEtiquetas)
         .then((results) => {
             dispatch(FetchGalleries(results))
         });
      }
    }
}

Then in your component, just call the right prop in componentDidMount

componentDidMount() {
    this.props.fetchData();
}
 

From your code I assume you are using redux-thunk.

I suggest combining them together to an action creator like this (which is possible in redux-thunk):

export function FetchEtiquetasThenGalleries() {
  return function (dispatch) {
    axios.get(url)
      .then(response => {
        dispatch({ type: FETCH_ETIQUETAS_SUCCESS, payload: response.data })
        const etiquetas = response.data; // Assuming response.data is that array.
        return Promise.all(
          etiquetas.map(record =>
            axios.get('https://e.dgyd.com.ar/wp-json/wp/v2/media?_embed&parent=' + record.id)
          )).then(galleries => {

            let curated_data = [];
            let data_json = '';

            galleries.map(record => {
              record.data.map(subrecord => {
                // this is simplified for this example, it works as intended
                data_json = data_json + '{ title: "' + subrecord.title + '"}';
              });

              my_data.push(data_json);

            });

            dispatch({ type: FETCH_GALLERIES_SUCCESS, payload: curated_data });
          });
      });
  }
}