Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using getDerivedStateFromProps to fetch API data is causing the component to render many times

I am trying to fetch some data from an API call. I am using getDerivedStateFromProps, componentDidMount,shouldComponentUpdateandcomponentDidUpdate`.

I am doing it like this because I need userToken which comes from userToken: store.signinScreen.userToken to be there before the call of the function GetPassengersData which needs userToken to get the data from the API.

This is the whole component:

// imports

class HomeScreen extends Component {
  static navigationOptions = {
    header: null,
  };

  state = {
    error: false,
  };

  static getDerivedStateFromProps(props, state) {
    if (props.userToken !== state.userToken) {
      return { userToken: props.userToken };
    }
    return null;
  }

  componentDidMount() {
    this.GetPassengersData();
  }

  shouldComponentUpdate(prevProps, state) {
    return this.props !== prevProps;
  }

  componentDidUpdate(prevProps, prevState) {
    const { error } = this.state;
    if (!error) {
      this.GetPassengersData();
    }
  }

  GetPassengersData = async () => {
    const { passengersDataActionHandler, userToken } = this.props;
    if (userToken && userToken !== null) {
      try {
        const response = await fetch(
          'http://myAPI/public/api/getPassengers',
          {
            method: 'POST',
            headers: {
              Authorization: `Bearer ${userToken}`,
              Accept: 'application/json',
              'Content-Type': 'application/json',
            },
          },
        );
        const responseJson = await response.json();
        if (has(responseJson, 'error')) {
          this.setState({ error: true });
          Alert.alert('Error', 'Please check your credentials.');
        } else {
          passengersDataActionHandler(responseJson.success.data);
        }
      } catch (error) {
        this.setState({ error: true });
        Alert.alert(
          'Error',
          'There was an error with your request, please try again later.',
        );
      }
    }
  };

  render() {
    return <TabView style={styles.container} />;
  }
}

HomeScreen.defaultProps = {
  userToken: null,
};

HomeScreen.propTypes = {
  navigation: PropTypes.shape({}).isRequired,
  passengersDataActionHandler: PropTypes.func.isRequired,
  userToken: PropTypes.oneOfType([PropTypes.string]),
};

export default compose(
  connect(
    store => ({
      userToken: store.signinScreen.userToken,
      passengersData: store.homeScreen.passengersData,
    }),
    dispatch => ({
      passengersDataActionHandler: token => {
        dispatch(passengersDataAction(token));
      },
    }),
  ),
)(HomeScreen);

With this implementation, the component renders many times so it breaks the application.

What could I be doing wrong?

like image 726
Non Avatar asked Dec 29 '25 09:12

Non


1 Answers

First of all you don't need to store userToken in state since you are not modifying it locally and hence you don't need getDerivedStateFromProps

Secondly, you need to trigger API call in componentDidUpdate only on as prop change and not directly without a check otherwise, the setState inside the API success or error will cause the component to re-render calling componentDidUpdate again and thus calling the API again resulting in a infinite loop

Third, the check inside shouldComponentUpdate comparing props is not exactly correct since nestedObjects props will give a false negative result and also if you write a deep equality check for props, the component won't re-render if the state changes.

// imports

class HomeScreen extends Component {
  static navigationOptions = {
    header: null,
  };

  state = {
    error: false,
  };

  componentDidMount() {
    this.GetPassengersData();
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevProps.userToken !== this.props.userToken) {
      this.GetPassengersData();
    }
  }

  GetPassengersData = async () => {
    const { passengersDataActionHandler, userToken } = this.props;
    if (userToken && userToken !== null) {
      try {
        const response = await fetch(
          'http://myAPI/public/api/getPassengers',
          {
            method: 'POST',
            headers: {
              Authorization: `Bearer ${userToken}`,
              Accept: 'application/json',
              'Content-Type': 'application/json',
            },
          },
        );
        const responseJson = await response.json();
        if (has(responseJson, 'error')) {
          this.setState({ error: true });
          Alert.alert('Error', 'Please check your credentials.');
        } else {
          passengersDataActionHandler(responseJson.success.data);
        }
      } catch (error) {
        this.setState({ error: true });
        Alert.alert(
          'Error',
          'There was an error with your request, please try again later.',
        );
      }
    }
  };

  render() {
    return <TabView style={styles.container} />;
  }
}

HomeScreen.defaultProps = {
  userToken: null,
};

HomeScreen.propTypes = {
  navigation: PropTypes.shape({}).isRequired,
  passengersDataActionHandler: PropTypes.func.isRequired,
  userToken: PropTypes.oneOfType([PropTypes.string]),
};

export default compose(
  connect(
    store => ({
      userToken: store.signinScreen.userToken,
      passengersData: store.homeScreen.passengersData,
    }),
    dispatch => ({
      passengersDataActionHandler: token => {
        dispatch(passengersDataAction(token));
      },
    }),
  ),
)(HomeScreen);
like image 108
Shubham Khatri Avatar answered Dec 31 '25 23:12

Shubham Khatri