Home > Software design >  React useEffect() infinite re-render for getting all despite dependencies
React useEffect() infinite re-render for getting all despite dependencies

Time:12-07

I'm relatively new to React, but I have been using useEffect to get all of my news and events from the database on load of a page. I recently tried to implement a loading spinner, and now I seem to be hitting an infinite loop, but I cannot understand why. I have the news and events in the dependency array, because if they change, then we need to update what is displayed to the user. The only potential issue I can see is that I am building up the news and events arrays in my action in my state store, but I haven't seemed to encounter this issue until trying to implement this functionality. Its probably something stupid, but can anyone help me?

Front end code:


export default observer(function NewsAndEvents() {
    const { generalStore } = useStore();
    const { getNewsAndEvents, events, news, loading } = generalStore

    useEffect(() => {
        getNewsAndEvents();
    }, [news, events]);

    return (
        <main id="newsAndEvents">
            <div className="infoContainer">
                <h1>News</h1>
                { loading ? <LoadingSpinner /> : news.map((news, index) => {
                   return <News key={`news_${index}`} title={news.title} content={news.content} />
                })}
            </div>

Get action:


export default class GeneralStore {
  isAuthenticated: boolean = false;

  selectedEvent: Event | undefined = undefined;
  events: Event[] = [];

  selectedNews: News | undefined = undefined;
  news: News[] = [];

  loading: boolean = false;

  constructor() {
    makeAutoObservable(this);
  }

  getNewsAndEvents = async () => {
    this.loading = true;
    try {
      let response: any = await agent.newsAndEvents.list();
      if (response !== null) {
        runInAction(() => {
            this.events = [];
            this.news = [];
          response.events.forEach((item: any) => {
            let newEvent: Event = {
              id: item._id,
              date_time: item.date_time,
              name: item.name,
              description: item.description,
              price: item.price,
              type: ContentType.event,
            };
            this.events.push(newEvent);
          });
          response.news.forEach((item: any) => {
            let newNews: News = {
              id: item._id,
              title: item.title,
              content: item.content,
              type: ContentType.news,
            };
            this.news.push(newNews);
          });
          this.setLoading(false);
        });
      }
    } catch (ex) {
      console.log(ex);
      this.setLoading(false);
    }
  };
}

I am using TS and MobX.

CodePudding user response:

if your getNewsAndEvents() function is updating news or events, your useEffect will run infinitely. You need to create a flag to prevent this.

const [flag, setFlag] = useState(true);

useEffect(() => {
  if (flag) {
    getNewsAndEvents();
    setFlag(false);
  }
}, [flag]);

Ideally flag should be a global state too but I used useState here cuz I'm not familiar with mobx.

Then every time you want getNewsAndEvents() to run, you can set the flag to true.

CodePudding user response:

I think the problem is when the loading ends, its value (true) stays the same because loading is not a component state, so the component will not be rerendered if the loading ends, so to solve that, I suggest you put the loading value in a state using useState hook like that:

const [loading, setLoading] = useState(generalStore.loading);

then you will add a useEffect with a [generalStore.loading] dependency so it will update the state after the loading value changes, and that will make the component rerender:

useEffect(() => {
    setLoading(generalStore.loading);
}, [generalStore.loading]);

I'm not sure this solution will work for you, but I think it deserves a try.

  • Related