'How to trigger useEffects before render in React?

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



Solution 1:[1]

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.

Solution 2:[2]

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.

Solution 3:[3]

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(), []);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 NamanD
Solution 2 Abhilash
Solution 3