22

I have a prop being passed from a parent component to a child component which changes based on the user's input.

I want to trigger a data fetch in the child component when that prop changes before the child component is rendered. How can I do it?

I tried in the following manner by using useEffects(()=>{},[props.a, props.b]) but that is always called after the render. Please help!

import React, { useEffect, useState } from "react";
import "./styles.css";

export default function parentComponent() {
  const [inputs, setInputs] = useState({ a: "", b: "" });
  return (
    <>
      <input
        value={inputs.a}
        onChange={(event) => {
          const value = event.target.value;
          setInputs((prevState) => {
            return { ...prevState, a: value };
          });
        }}
      />
      <input
        value={inputs.b}
        onChange={(event) => {
          const value = event.target.value;
          setInputs((prevState) => {
            return { ...prevState, b: value };
          });
        }}
      />
      <ChildComponent a={inputs.a} b={inputs.b} />
    </>
  );
}

function ChildComponent(props) {
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState({});

  useEffect(() => {
    console.log("updating new data based on props.a: " + props.a);
    setData({ name: "john " + props.a });
    return () => {};
  }, [props.a, props.b]);

  useEffect(() => {
    console.log("data successfully changed");
    console.log(data);
    if (Object.keys(data).length !== 0) {
      setIsLoading(false);
    }
    return () => {};
  }, [data]);

  function renderPartOfComponent() {
    console.log("rendering POC with props.a: " + props.a);
    return <div>data is: {data.name}</div>;
  }
  return (
    <div className="App">{isLoading ? null : renderPartOfComponent()}</div>
  );
}

In the console what I get is:

rendering POC with props.a: fe 
rendering POC with props.a: fe 
updating new data based on props.a: fe 
rendering POC with props.a: fe 
rendering POC with props.a: fe 
data successfully changed 
Object {name: "john fe"}
rendering POC with props.a: fe 
rendering POC with props.a: fe 

If you know how I can make the code more efficient, that would be a great help as well!

Here's the codesandbox link for the code: https://codesandbox.io/s/determined-northcutt-6z9f8?file=/src/App.js:0-1466

Gass
  • 7,536
  • 3
  • 37
  • 41
NamanD
  • 473
  • 1
  • 4
  • 12
  • do you think this answer would help? https://stackoverflow.com/a/56818036 – Abhilash Sep 02 '20 at 17:54
  • @Abhilash Thanks for the reply, but the approach that's been talked about in that answer is more of how to run a piece of code once when the component mounts, but in my case everything works fine the first time the component mounts, however when the props changes the return ( ) block renders before the useEffect is called, which is an issue because I want to fetch appropriate data for the new props – NamanD Sep 02 '20 at 18:06
  • I know this is an old question, but have you considered using cleanUp function in useEffect? I believe the steps goes as follows: cleanUp function in useEffect is called -> render is called-> then non-cleanUp useEffect function is called. – constantlyFlagged Jun 03 '22 at 13:56

5 Answers5

21

Solution

You can use useMemo, which doesn't wait for a re-render. It will execute as long as the dependencies are changed.

useMemo(()=>{
    doSomething() //Doesn't want until render is completed 
}, [dep1, dep2])
Abraham
  • 12,140
  • 4
  • 56
  • 92
7

You can use function below:

// utils.js
const useBeforeRender = (callback, deps) => {
    const [isRun, setIsRun] = useState(false);

    if (!isRun) {
        callback();
        setIsRun(true);
    }

    useEffect(() => () => setIsRun(false), deps);
};

// yourComponent.js
useBeforeRender(() => someFunc(), []);
Johny Martin
  • 424
  • 3
  • 4
5

useEffect is always called after the render phase of the component. This is to avoid any side-effects from happening during the render commit phase (as it'd cause the component to become highly inconsistent and keep trying to render itself).

  • Your ParentComponent consists of Input, Input & ChildComponent.
  • As you type in textbox, ParentComponent: inputs state is modified.
  • This state change causes ChildComponent to re-render, hence renderPartOfComponent is called (as isLoading remains false from previous render).
  • After re-render, useEffect will be invoked (Parent's state propagates to Child).
  • Since isLoading state is modified from the effects, another rendering happens.
Abhilash
  • 2,026
  • 17
  • 21
5

Nowadays you can use useLayoutEffect which is a version of useEffect that fires before the browser repaints the screen.

Docs: https://beta.reactjs.org/reference/react/useLayoutEffect

enriquejr99
  • 530
  • 6
  • 6
3

I found the solution by creating and maintaining state within the ChildComponent

So, the order of processes was this: props modified -> render takes place -> useEffect block is executed.

I found the workaround by simply instantiating a state within the childComponent and making sure that the props state is the same as the one in the child component before rendering, else it would just show loading... This works perfectly.

NamanD
  • 473
  • 1
  • 4
  • 12