UseEffect based on props is adding duplicates to state array

Viewed 629

So a short description of the problem

Users can select a radius via the html select tag, this value is send as a prop to this component and if the value changes the useEffect rerenders

The way i am implementing it now, it creates the problem that the filtered events are added to state. So it keeps on adding duplicates to the array.

How can i use useEffect / setState to make sure that previous items will be removed or overwritten and only unique events are stored in the state?

const [data, setData] = useState();
const [userLat, setUserLat] = useState();
const [userLon, setUserLon] = useState();
const [evtsFiltered, setEvtsFiltered] = useState([]);

// Fetch all events and store in state
useEffect(() => {
  axios.get("http://localhost:5000/events").then((res) => {
    setData(res.data.events);
  });
}, []);

// Filter events based on user's location
useEffect(() => {
  data?.forEach((el) => {
    Geocode.fromAddress(`${el.address}`)
      .then((res) => {
        let dis = getPreciseDistance(
          {
            latitude: parseFloat(res.results[0].geometry.location.lat),
            longitude: parseFloat(res.results[0].geometry.location.lng),
          },
          { latitude: parseFloat(userLat), longitude: parseFloat(userLon) }
        );
        if (dis / 1000 <= props.radius) { // props.radius will trigger the useEffect when user selects a new value on the homepage
          setEvtsFiltered((evtsFiltered) => [...evtsFiltered, el]);
        }
      })
      .catch((err) => console.log(err));
  });
}, [data, userLon, userLat, props.radius]);

I know the line setEvtsFiltered((evtsFiltered) => [...evtsFiltered, el]) is wrong since I store the new events together with the previous events by using spread operator. The problem is I don't know how i can fix it.

If someone could point me in the right direction?

Thanks in advance!

1 Answers

I would map the data to an array of promises, and await for all of them to finish using Promise.all(), before replacing the current state with the new array. To remove items that don't match the criteria, you can return an empty array, and then flatten the result with Array.flat() to remove them

Example (not tested):

useEffect(async() => {
  try {
    const evts = await Promise.all(
      data.map(async(el) => {
        const res = await Geocode.fromAddress(`${el.address}`);

        let dis = getPreciseDistance({
          latitude: parseFloat(res.results[0].geometry.location.lat),
          longitude: parseFloat(res.results[0].geometry.location.lng),
        }, {
          latitude: parseFloat(userLat),
          longitude: parseFloat(userLon)
        });

        return dis / 1000 <= props.radius ? el : [];
      })
    );

    setEvtsFiltered(evts.flat());
  } catch (e) {
    console.log(err);
  }
}, [data, userLon, userLat, props.radius]);
Related