tbw3
tbw3

Reputation: 82

infinite api calls in componentDidUpdate(prevProps) despite conditional that compares current prop with previous prop

My problem is in my subcomponent files where every state update in the parent component keeps re-rendering the subcomponents infinitely by making infinite api calls with the default or updated props value passed to the child components. I have a User directory page which contains multiple components in a single page.

class Users extends Component {
  constructor(props) {
    super(props);
    this.state = {
      user: "",
      listOfUsers: [],
      socialData:[],
      contactData:[],
      videoData:[],
      detailsData:[]
    }
   }

  componentDidMount(){
    //api call that gets list of users here
    //set response data to this.state.listOfUsers
  }
   
   userHandler = async (event) => {
    this.setState({
      user: event.target.value,
    });
   };

   render() {
      return (
       <div>
        <div>
           <select
             value={this.state.user}
             onChange={this.userHandler}
           >
             // list of users returned from api
           </select>
        </div>
        <div>
          <Social Media user={this.state.user} />
          <Contact user={this.state.user}/>
          <Video user={this.state.user}/>
          <Details user={this.state.user}/>
        </div>
       </div>
      );
    }
}

I have 1 API call for the parent component Users, and 4 for each of the subcomponents: Social Media, Contact, Video, and Details. The Users api will return a list of users in a dropdown and the value of the user selected is then fed to the other four API's. i.e. https://localhost:3000/social_media?user=${this.state.user}. Thus, the four subcomponents' API is dependent on the Users API. I currently have the parent api call in a componentDidMount() and the other 4 api calls in their respective subcomponents and use props to pass down the value of the user selected in the parent to the subcomponents. Each of the api calls is in a componentDidUpdate(prevProps). All the subcomponents follow this structure:

class Social Media extends Component {
  constructor(props) {
    super(props);
    this.state = {
      user: "",
      socialData:[],
    }
   }

   componentWillReceiveProps(props) {
    this.setState({ user: this.props.user })
   }
   
   componentDidUpdate(prevProps){
     if (this.props.user !== prevProps.user) {
       // make api call here
       fetch (`https://localhost:3000/social_media?user=${this.state.user}`)
        .then((response) => response.json())
        .catch((error) => console.error("Error: ", error))
        .then((data) => {
            this.setState({ socialData: Array.from(data) });
        }
   }

   render() {
      return (
        {this.socialData.length > 0 ? (
        <div>
          <Social Media data={this.state.socialData}/>
        </div>
        ) 
        :
        (<div> Loading ... </div>)
      );
    }
}

Upvotes: 2

Views: 341

Answers (2)

Aaron
Aaron

Reputation: 2227

Abortive attempt to answer your question

It's hard to say exactly what's going on here; based on the state shown in the Users component, user should be a string, which should be straightforward to compare, but clearly something is going wrong in the if (this.props.user !== prevProps.user) { comparison.

If we could see the results of the console.log(typeof this.props.user, this.props.user, typeof prevProps.user, typeof prevProps.user) call I suggested in my comment, we'd probably have a better idea what's going on here.

Suggestions that go beyond the scope of your question

Given the moderate complexity of your state, you may want to use some sort of shared state like React's Context API, Redux, or MobX.. I'm partial toward the Context API, as it's built into React and requires relatively less setup.

(Then again, I also prefer functional components and hooks to classes and componentDidUpdate, so my suggestion may not apply to your codebase without a rewrite.)

Upvotes: 2

WebbH
WebbH

Reputation: 2422

If this.props.user is an object, then this.props.user !== prevProps.user will evaluate to true because they are not the same object. If you want to compare if they have the same properties and values (i.e. they are shallowly equal) you can use an npm package like shallow-equal and do something like:

import { shallowEqualObjects } from "shallow-equal";
//...
componentDidUpdate(prevProps){
 if (!shallowEqualObjects(prevProps.user, this.props.user)) {
   // make api call here
   fetch (`https://localhost:3000/social_media?user=${this.state.user}`)
    .then((response) => response.json())
    .catch((error) => console.error("Error: ", error))
    .then((data) => {
        this.setState({ socialData: Array.from(data) });
    }
 }

Upvotes: 1

Related Questions