Home > Software design >  Filtering react component state causes infinite render loop
Filtering react component state causes infinite render loop

Time:01-01

I'm retrieving some data from an API using useEffect, and I want to be able to filter that returned data using a prop being fed into the component from its parent.

I'm trying to filter the state after it is set by useEffect, however it looks like the component is going into an infinite render loop.

What do I need to do to prevent this?

export default function HomeJobList(props: Props): ReactElement {
    const [listings, setListings] = React.useState(null);
  
    useEffect(() => {
        const func = async () => {
          let res = await service.getListings();
          setListings(res);
        };
        func();
      }, []);
  
    if (props.searchTerm && listings) {
      let filtered = listings.filter((x) => x.positionTitle.includes(props.searchTerm));
      setListings(filtered);
    }

  
    return (
      <>
        <div>do stuff</div>
      </>
    );
  }
  

I understand that the use of the setListing function is then causing a rerender after the filtering, which then causes another setListing call. But what's the best way to break this loop?

Should I just have another state value that maintains the last searchTerm used to filter and check against that before filtering?

Or is there a better way?

CodePudding user response:

It's an indinite loop because every time you filter, you set it as a state variable, which causes re-rendering and filtering & setting the variable again - thus a loop.

I suggest you do it all in one place (your useEffect is a good place for that, because it only executes once.

    useEffect(() => {
        (async () => {
          const res = await service.getListings();
          const filtered = res.filter((x) => x.positionTitle.includes(props.searchTerm));
          setListings(filtered);
        })();
      }, []);

CodePudding user response:

When the state changes that trigger a re-render, that's why you have an infinite loop; what you have to do is to wrap your filtering in a useEffectthat that depends on the searchTerm prop, like this:

import React, { useEffect } from 'react';

export default function HomeJobList() {
  const [listings, setListings] = React.useState(null);

  useEffect(() => {
    const func = async () => {
      let res = await service.getListings();
      setListings(res);
    };
    func();
  }, []);

  useEffect(() => {
    if (listings) {
      let filtered = listings.filter(x =>
        x.positionTitle.includes(props.searchTerm)
      );
      setListings(filtered);
    }
  }, [props.searchTerm]);

  return (
    <>
      <div>do stuff</div>
    </>
  );
}

CodePudding user response:

You need to create a function that's called inside the JSX you're returning.

Actually, you'll need to render the component every time the Props objects changes. That's achieved by calling the function in the JSX code.

Example:

Function:

const filteredListings = () => {
    if (props.searchTerm && listings) {
        let filtered = listings.filter((x) => {
            x.positionTitle.includes(props.searchTerm));
        }
        return filtered;
    }
}

Return Statement:

return (
    <ul>
        {
            filteredListings().map((listing) =>
                <li>{listing.title}</li> 
            );
        }
    </ul>
);

CodePudding user response:

What you need is a useEffect which does the filtering when props changes.

Replace this

 if (props.searchTerm && listings) {
      let filtered = listings.filter((x) => x.positionTitle.includes(props.searchTerm));
      setListings(filtered);
    }

with

useEffect(()=>{
   if (props.searchTerm) {
      setListings(prevListing => {
          return prevListing.filter((x) => x.positionTitle.includes(props.searchTerm))
      });            
    }
}, [props.searchTerm] )
  • Related