Search code examples
javascriptreactjsreact-router-dom

How can I useParams() to filter over initial state in React, instead of making another get request?


I'm working on a SPA React project which tracks farms and their associated beds and could use some help. I'd like to render some data on a FarmDetail page by filtering over initial state with useParams() rather than making more fetch requests to "farms/:id". I've made 1 fetch get request and saved initial state in an allFarms variable. I've rendered some Links using react-router-dom which navigate to a path using the id of the clicked farm.

So far, the component renders twice, and on the second pass my console.log(farm) in useEffect does output the correct farm object. Trouble is, at that point it doesn't rerender as I'm not setting state and the component has already rendered so the return value is blank since farm = {}.

import { useParams } from "react-router-dom";

function FarmDetail({ allFarms, setAllFarms }) {
  console.log("render");

  let { id } = useParams();
  id = parseInt(id);

  let farm = {};

  useEffect(() => {
    if (allFarms) {
      farm = [...allFarms].find((f) => f.id === id);
    }
    console.log(farm);
  }, [allFarms]);

  console.log(farm);
  console.log(allFarms);

  return (
    <div>
      <p>{farm.name}</p>
    </div>
  );
}

export default FarmDetail;

I've tried to useState for the farm variable and setState in useEffect in order to trigger a rerender, but that doesn't seem to work as farms is then set to undefined and I never trigger the condition in useEffect. allFarms also just logs an empty array multiple times which I don't quite understand why. I end up with a TypeError: cannot read properties of undefined (reading "name") which makes sense to me since farm remains undefined that way. I feel like I'm close, but I'm missing something here.


Solution

  • There's no need for an effect hook here because there are no side-effects.

    What you can do instead is memo-ise the id / allFarms combination

    const farm = useMemo(() => allFarms?.find((f) => f.id === id), [id, allFarms]);
    

    This will re-calculate farm any time id or allFarms changes and re-render the component.


    Even this may not be necessary if changes to allFarms causes the FarmDetail component to re-render. If so, you can even more simply use the following

    const farm = allFarms?.find((f) => f.id === id);
    

    Given that this may return undefined if allFarms is not an array or the id not present, you should use some conditional rendering

    if (!farm) {
      return <p className="alert warning">Not found</p>;
    }