简体   繁体   中英

Why lifting an updated state from a child to a parent component does not work?

I managed to reach the result I wanted when trying to update the UI of a parent component after toggling the state of a child, however, as a React beginner I guess I haven't done it quite right.

As you will see in the code below, I updated the state of my radio button components based on event.target.value and then I lifted it to the component using SitePageToggle ( WebsitePicker ) via the onToggle prop. The problem is that, in my understanding, I should have used selectedRadioBtn with its updated state. However, when I pass selectedRadioBtn so that I can base the state of my QAObject variable according to it, the toggling order between 'Website' and 'Page' is messed up and inverted. If I use the updated state in the child component itself this isn't a problem.

I know that [state updates may be asynchronous][1], but using the useState hook second version by accepting a callback hasn't solved the problem either.

Here's the code in the parent component :

import styles from "./WebsitePicker.module.css";
import { useState } from "react";
import SitePageToggle from "./SitePageToggle";
import SubmitButton from "./Buttons/SubmitButton";

function WebsitePicker() {
    const [QAObject, setQAObject] = useState("Website");
    const { label, input__url } = styles;

    const toggleWebsitePageHandler = toggleData => {
        if (toggleData === 'Website') {
            setQAObject("Website");
        } else {
            setQAObject("Page");
        }
    };

    return (
        <>
            <SitePageToggle onToggle={toggleWebsitePageHandler} />
            <form onSubmit={formSubmissionHandler}>
                <label className={label} htmlFor="picker">
                    Enter the URL of the {QAObject} you wish to QA.
                </label>
                <br />
                <input
                    id="picker"
                    type="url"
                    className={input__url}
                />
                <SubmitButton>QA my page</SubmitButton>
            </form>
        </>
    );
}

export default WebsitePicker;

And here's the code in the child component :

import RadioButton from "./Buttons/RadioButton";

function SitePageToggle({ onToggle }) {
    const [selectedRadioBtn, setSelectedRadioBtn] = useState("Website");

    const isRadioSelected = (btnValue) => selectedRadioBtn === btnValue;

    const handleToggle = event => {
        setSelectedRadioBtn(event.target.value);
        onToggle(event.target.value);
    };

return (
    <ul>
        <li>
            <RadioButton
                label="Website"
                name="react-radio-btn"
                value="Website"
                checked={isRadioSelected("Website")}
                onChange={handleToggle}
            />
        </li>
        <li>
            <RadioButton
                label="Page"
                name="react-radio-btn"
                value="Page"
                checked={isRadioSelected("Page")}
                onChange={handleToggle}
            />
        </li>
    </ul>
);
}

export default SitePageToggle;


  [1]: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

Not sure if I fully understand the use case, but if the goal is to lift the selectedRadioBtn state to the parent (instead of syncing two states), then perhaps SitePageToggle does not need to keep a separate copy of the state, rather receive it from the parent.

Quick simplified demo on: stackblitz

In WebsitePicker pass QAObject as selectedRadioBtn :

<SitePageToggle
  selectedRadioBtn={QAObject}
  onToggle={toggleWebsitePageHandler}
/>

In SitePageToggle use selectedRadioBtn from props as the state:

import RadioButton from "./Buttons/RadioButton";

function SitePageToggle({ selectedRadioBtn, onToggle }) {
  const isRadioSelected = (btnValue) => selectedRadioBtn === btnValue;
  const handleToggle = (event) => {
    onToggle(event.target.value);
  };

  return (
    <ul>
      <li>
        <RadioButton
          label="Website"
          name="react-radio-btn"
          value="Website"
          checked={isRadioSelected("Website")}
          onChange={handleToggle}
        />
      </li>
      <li>
        <RadioButton
          label="Page"
          name="react-radio-btn"
          value="Page"
          checked={isRadioSelected("Page")}
          onChange={handleToggle}
        />
      </li>
    </ul>
  );
}

export default SitePageToggle;

To properly pass up the child state selectedRadioBtn you should use a useEffect(). The second argument of the useEffect is the dependency array, so whenever selectedRadioBtn changes, it will fire.

import RadioButton from "./Buttons/RadioButton";

function SitePageToggle({ onToggle }) {
    const [selectedRadioBtn, setSelectedRadioBtn] = useState("Website");

    const isRadioSelected = (btnValue) => selectedRadioBtn === btnValue;

    const handleToggle = event => {
        setSelectedRadioBtn(event.target.value);
        // onToggle(event.target.value); <-- delete this here
    };

    useEffect(() => {
        onToggle(selectedRadioBtn);
    }, [selectedRadioBtn]);

    return (
        <>...</>
    );
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM