2

I am using react-localize-redux for my multilingual application and MySQL to fetch data. One of my actions needs locale data to pass it to backend as an argument so that backend responds with proper data. But by the time locale is available, action gets called and application crashes, how can I resolve the issue? Here is code:

import React, { Component } from 'react'
import RestaurantCard from './RestaurantCard';
import {Row} from 'react-bootstrap';
import { connect } from 'react-redux';
import {getAllRestaurants} from "../actions/restaurantActions";
import { withLocalize } from 'react-localize-redux';

class RestaurantCardColumns extends Component {

  constructor(props){
    super(props);
  }

  componentDidMount(){
    this.props.getAllRestaurants(this.props.activeLanguage);
  }

  render() {
    if(this.props.loading || this.props.restaurants === null){
      return <p>Loading...</p>
    } else {
      return (
        <Row>
            <RestaurantCard data = {this.props.restaurants[0]}/>
            <RestaurantCard data = {this.props.restaurants[1]}/>
            <RestaurantCard data = {this.props.restaurants[2]}/>
            <RestaurantCard data = {this.props.restaurants[3]}/>
        </Row>)
    }
  }
}
const mapStateToProps = (state) =>{
  return {
      auth: state.auth,
      errors: state.errors,
      restaurants: state.restaurData.restaurants,
      loading: state.restaurData.loading
  }
}

export default connect(mapStateToProps, {getAllRestaurants})(withLocalize(RestaurantCardColumns));

My problem is in this particular line:

this.props.getAllRestaurants(this.props.activeLanguage);

When I debug I can see that activeLanguage is available in render() lifecycle. How can I await for that property before calling getAllRestaurants

Nodir Nasirov
  • 1,488
  • 3
  • 26
  • 44
  • 1
    Map it during `mapStateToProps` (which can take in a `props` argument), and only set `loading` to false once the connector sees the data in the correct language. A Thunk in your reducer can handle that part. – Derek Apr 18 '19 at 04:47

3 Answers3

1

Use a store enhancer middleware like Thunk. You seem to be making an async request,and store enhancers enable you to make async calls and retrieve data from backend. Middlewares like Thunk stops default action dispatch, perform async requests ad call the dispatch to pass the action along with the updated payload to the reducer. Using proper async - await in the componentDidMount will handle this as well, but store enhancers actually handle that for you. Here's an example:

async componentDidMount() {
    await this.props.getAllRestaurants(this.props.activeLanguage);
}
Dblaze47
  • 868
  • 5
  • 17
  • Minor detail: `componentWillMount` is a deprecated lifecycle. it can be called multiple times before a render occurs and probably shouldn't be used. If you update the local state of the react component then a re-render will occur so that would be a more stable solution. [For reference](https://stackoverflow.com/questions/41612200/in-react-js-should-i-make-my-initial-network-request-in-componentwillmount-or-co/41612993#41612993) – ngood97 Apr 18 '19 at 04:36
  • I do use `thunk`, no luck – Nodir Nasirov Apr 18 '19 at 04:40
  • @NodirNasirov Use `async - await` in the `componentDidMount` – Dblaze47 Apr 18 '19 at 04:43
  • like this? `await this.props.activeLanguage; this.props.getAllRestaurants(this.props.activeLanguage.code);` – Nodir Nasirov Apr 18 '19 at 04:49
  • Check my answer for an example – Dblaze47 Apr 18 '19 at 04:58
1

Check for availability of this.props.activeLanguage before fetching data. Trigger fetching data once activeLanguage is available. And finally ensure that fetching happening only once (if you need)

class RestaurantCardColumns extends Component {

  constructor(props){
    super(props);
    this.didFetch = false; // store outside of state to not trigger rendering
  }

  componentDidMount(){
    this.fetchAllRestaurants();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.activeLanguage !== this.props.activeLanguage) {
      this.fetchAllRestaurants();
    }
  }

  fetchAllRestaurants() {
    if (!!this.props.activeLanguage && !this.didFetch) {
      this.props.getAllRestaurants(this.props.activeLanguage);
      this.didFetch = true;
    }
  }

Be aware that this approach is entirely relied on the component's existence, i.e. if the component is not in virtual DOM, the API call will not happen. You should consider trigger the call using a redux's middleware, like redux-thunk or redux-saga as other people in here suggest.

blaz
  • 3,981
  • 22
  • 37
0

ComponentDidMount should be an async function, and you should await for getAllRestaurants to complete.

In addition to that, you should have a local state variable (e.g. IsLoading), that indicates that data is not ready. After the 'await getAllRestaurants' statement, you set isLoading to falase.

Render will check this local state in order to display a spinner or the data itself, or an error message, if getAllRestaurants fails (in addition to checking isLoading, you should check the redux store, where you will store not only the data, but also a variable indicating whether getAllRestaurants succeeded or failed).

Yossi
  • 5,577
  • 7
  • 41
  • 76
  • I can't await for `getAllRestaurants` until I get `this.props.activeLanguage` argument, my question is how can I await for `this.props.activeLanguage` so that I could pass it to `getAllRestaurants`? – Nodir Nasirov Apr 18 '19 at 04:42
  • I see that somebody else is helping you. If you find yourself stuck, drop here another comment and I will try to help. – Yossi Apr 18 '19 at 06:40