Setting a parent's state to a child's state setter

Viewed 39

I have the following React example, where I'm setting a state to the value of another state setter.

import React, {
  Dispatch,
  SetStateAction,
  useEffect,
  useState,
} from "react";
import ReactDOM from "react-dom";

const A = ({}) => {
  const [setStage, setStageSetter] = useState<Dispatch<SetStateAction<number>>();

  useEffect(() => {
    console.log('A.setStage', setStage);

    if (setStage) {
      setStage(2);
    }
  }, [setStage, setStageSetter]);

  return <B setStageSetter={setStageSetter} />;
};

const B = ({ setStageSetter }) => {
  const [stage, setStage] = useState<number>(1);

  useEffect(() => {
    console.log('B.setStage', setStage);

    setStageSetter(setStage);
  }, [setStage, setStageSetter]);

  return <p>{stage}</p>;
};

ReactDOM.render(<A />, mountNode);

However, when I run the above, it does not output what I'd expect, which is for the above to read 2 in the DOM. It seems to set the value to 1, and then to undefined Why is this?


A solution to the above is the following answer:

const { useEffect, useState } = React;

const B = ({ setStageSetter }) => {
  const [stage, setStage] = useState(1);
  
  useEffect(() => {
    setStageSetter(() => setStage);
  }, [setStage, setStageSetter]);

  return <p>{stage}</p>;
};

const A = () => {
  const [setStage, setStageSetter] = useState();
  
  useEffect(() => {
    if (setStage) {
      setStage(2);
    }
  }, [setStage]);

  return <B setStageSetter={setStageSetter} />;
};

ReactDOM.render(<A />, mountNode);

The functional difference is from setStageSetter(setStage); to setStageSetter(() => setStage);

Whilst this does set the parent's state to the setter of the child, and I can call a method in the parent and set the child's state, I don't understand why the callback is required. Why is this? Is this "bad practice"?

1 Answers

When the setStageSetter call was invoked with a different parameter, it seems to have achieved the desired objective. Please see the code snippet below for a working demo.

Code Snippet

const {
  Dispatch,
  SetStateAction,
  useEffect,
  useState
} = React;

const B = ({ setStageSetter }) => {
  const [stage, setStage] = useState(1);
// <Number>
  useEffect(() => {
    console.log('B stage: ', stage, ' setStage', setStage);
    // calling the props method with a "function" and not the setter as-is
    setStageSetter(() => setStage);
  }, [setStage, setStageSetter]);

  return <p>{stage}</p>;
};

const A = ({}) => {
  const [setStageA, setStageSetter] = useState(()=>{});
// <Dispatch<SetStateAction<Number>>
  useEffect(() => {
    console.log('A setStageA', setStageA);

    if (setStageA) {      // to realize the change on screen, delay by 1.5 seconds
      setTimeout(() => setStageA(2), 1500);
      // setStageA(2); // this works too, but change is not immediately observable
    }
  }, [setStageA, setStageSetter]);

  return <B setStageSetter={setStageSetter} />;
};

ReactDOM.render(
  <div>
    DEMO
    <A />
  </div>,
  document.getElementById("rd")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="rd" />

Explanation

Inline comments added to the snippet above.

Related