Home > Software engineering >  React useEffect not running after state change
React useEffect not running after state change

Time:03-07

I am querying Firebase real time database, saving the result into state and then rendering the results.

My data is not displaying because the page is rendering before the data is had. What I don't understand is why

useEffect(() => {
    for (var key in projects) {
      var projectData = {
        title: projects[key].title,
        description: projects[key].description,
      };
      result.push(<Project props={projectData} />);
    }
   }, [projects]); 

My use effect is not running once the projects state change is triggered, populating the array and triggering the conditional render line.

What am I missing here?

  const [projects, setProjects] = useState();
  const { user } = useUserAuth();
  const result = [];
  const dbRef = ref(db, `/${user.uid}/projects/`);

  useEffect(() => {
    onValue(dbRef, (snapshot) => {
      setProjects(snapshot.val());
    });
  }, []);

  useEffect(() => {
    for (var key in projects) {
      var projectData = {
        title: projects[key].title,
        description: projects[key].description,
      };
      result.push(<Project props={projectData} />);
    }
  }, [projects]);

  return (
    <>
      {result.length > 0 && result}
    </>
  );
};

CodePudding user response:

result should also be a state!

Right now at every rerender result is being set to []. So when the useEffect does kick in, the subsequent rerender would set result to [] again.

CodePudding user response:

This should not be a useEffect. Effects run after rendering, but you're trying to put <Project> elements on the page, which must happen during rendering. Simply do it in the body of your component:

  const [projects, setProjects] = useState();
  const { user } = useUserAuth();
  const dbRef = ref(db, `/${user.uid}/projects/`);

  useEffect(() => {
    onValue(dbRef, (snapshot) => {
      setProjects(snapshot.val());
    });
  }, []);

  const result = [];
  for (var key in projects) {
    var projectData = {
      title: projects[key].title,
      description: projects[key].description,
    };
    result.push(<Project props={projectData} />);
  }

  return (
    <>
      {result.length > 0 && result}
    </>
  );

CodePudding user response:

result.push does not mutate result in place. It instead creates a copy of the array with the new value.

As a solution, you could get your current code working by hoisting result into a state variable like so:

const [result, setResult] useState([])
...
 useEffect(() => {
    for (var key in projects) {
      ...
      setResult([...result, <Project props={projectData} />])
    }
  }, [result, projects]);

however, this solution would result in an infinite loop...

My suggestion would be to rework some of the logic and use projects to render your Project components, instead of creating a variable to encapsulate your render Components. Something like this:

  const [projects, setProjects] = useState();
  const { user } = useUserAuth();
  const dbRef = ref(db, `/${user.uid}/projects/`);

  useEffect(() => {
    onValue(dbRef, (snapshot) => {
      setProjects(snapshot.val());
    });
  }, []);

  return (
    <>
      {projects.length > 0 && projects.map(project=>{
        var projectData = {
          title: projects[key].title,
          description: projects[key].description,
        };
        return <Project props={projectData} />
       })}
    </>
  );
};

CodePudding user response:

You're component is not re-rendering since react doesn't care about your result variable being filled.

Set it up as a state like this: const [result, setResult] = useState([]);

Then use map to return each item of the array as the desire component:

{result.length > 0 && result.map((data, index) => <Project key={index} props={data} />)}
  • Related