skip to Main Content

This is my first question, so please, bear with me. 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. Any help is much appreciated!

2

Answers


  1. 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>;
    }
    
    Login or Signup to reply.
  2. I saw your code.
    And I am agree with Phil’s answer.
    You can use useMemo hook instead of useEffect.
    And by the way, the reason, you component renders twice, is that
    first your component renders (with initial allFarms data)
    sencod your component renders (with fetched allFarms data)

    And the reason, your component renders blank, is that you mentioned that

    let farm = {};
    

    inside your component.
    So every re-render, farm will initialized as blank {}
    but

    useEffect

    only called when allFarm variable changed.

    Hope this answer helps.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search