【问题标题】:In ReactJS How to save Child State in Parent state on destroying child?在 ReactJS 中,如何在销毁子时将子状态保存在父状态中?
【发布时间】:2021-08-16 12:32:06
【问题描述】:

我有一个父组件和两个子组件,如下所示:

父组件:

import React, { useState } from "react";
import { Child1Comp } from "./Child1Comp";
import { Child2Comp } from "./Child2Comp";

export function ParentComp() {
  const [step, setStep] = useState(1);
  const [parentState, setParentState] = useState({});
  const handleNextBtn = () => setStep((step) => step + 1);
  const handlePrevBtn = () => setStep((step) => step - 1);

  return (
    <div>
      <div>{`the parent state is: ${JSON.stringify(parentState)}`}</div>
      {step === 1 && (
        <Child1Comp
          step={step}
          parentState={parentState}
          setParentState={setParentState}
        />
      )}
      {step === 2 && (
        <Child2Comp
          step={step}
          parentState={parentState}
          setParentState={setParentState}
        />
      )}
      <br />
      <button
        id="nextBtn"
        name="nextBtn"
        onClick={handleNextBtn}
        disabled={step === 2}
      >
        Next
      </button>
      <button
        id="nextBtn"
        name="nextBtn"
        onClick={handlePrevBtn}
        disabled={step === 1}
      >
        Prev
      </button>
      <br />
      <br />
      {`current step is : ${step}`}
    </div>
  );
}

Child1Component:

import React, { useEffect, useState } from "react";

export function Child1Comp({ step, parentState, setParentState }) {
  const [inputValue, setInputValue] = useState();
  const handleChange = (e) => setInputValue(e.target.value);

  useEffect(() => setInputValue(parentState.child1), []);
  useEffect(
    () =>
      setParentState((parentState) => ({
        ...parentState,
        child1: inputValue
      })),
    [step]
  );

  return (
    <div>
      <br />
      name:
      <input
        id="child1"
        name="child1"
        value={inputValue}
        onChange={handleChange}
      />
    </div>
  );
}

Child2Component:

import React, { useEffect, useState } from "react";

export function Child2Comp({ step, parentState, setParentState }) {
  const [inputValue, setInputValue] = useState();
  const handleChange = (e) => setInputValue(e.target.value);

  useEffect(() => setInputValue(parentState.child2), []);
  useEffect(
    () =>
      setParentState((parentState) => ({
        ...parentState,
        child2: inputValue
      })),
    [step]
  );

  return (
    <div>
      <br />
      family:
      <input
        id="child2"
        name="child2"
        value={inputValue}
        onChange={handleChange}
      />
    </div>
  );
}
  • 在父组件中,我有一个步骤状态来确定应该显示哪个子组件。
  • 每个孩子都有自己的状态。

现在我希望当用户通过单击下一步按钮更改步长值时,子状态值保存在父状态中,所以我在每个子状态中使用此代码:

孩子1:

useEffect(
        () =>
          setParentState((parentState) => ({
            ...parentState,
            child1: inputValue
          })),
        [step]
      );

孩子2:

useEffect(
        () =>
          setParentState((parentState) => ({
            ...parentState,
            child2: inputValue
          })),
        [step]
      );

但是当父组件中的 step 状态发生变化时,top useEffect 都不会运行。并且子状态不保存在父状态中。:(

你有什么办法解决这个问题吗?

codesandbox link

【问题讨论】:

    标签: reactjs react-state-management


    【解决方案1】:

    将状态保留在父组件中,并将 getter 和 setter 作为 props 传递。 像这样的:

    export function ParentComp() {
        const [step, setStep] = useState(1);
        const [child1Input, setChild1Input] = useState(null);
        const [child2Input, setChild2Input] = useState(null);
        
        return (
          <div>
          
            {step === 1 && (
              <Child1Comp
                input={child1Input}
                setInput={setChild1Input}
              />
            )}
            {step === 2 && (
              <Child1Comp
              input={child2Input}
              setInput={setChild2Input}
            />
            )}
    
            ...
    

    现在子组件不再需要状态变量或 useEffects,这意味着一旦代码增长,出现错误的风险就会降低,并且更容易理解流程。

    export function Child1Comp({ input, setInput }) {
    
     return (
        <div>
          <br />
          name:
          <input
            id="child1"
            name="child1"
            value={inputValue}
            onChange={e => setInput(e.target.value)}
          />
        </div>
      );
    }
    

    当然,如果您愿意,您仍然可以将状态保留在对象中,不过我希望您能大致了解。

    【讨论】:

    • 谢谢,您的解决方案很好,但是当步骤很多时,对于每个子组件,我是否应该在父组件中有一个“状态”,使父组件格式不正确。
    • 没错,但就像我说的,如果你愿意,你仍然可以将状态放在一个对象中,并且仍然只保留在父对象中。
    【解决方案2】:

    解决方案:

    请注意inputValue,而不是useEffect中的step

    孩子1

    useEffect(
        () =>
          setParentState((parentState) => ({
            ...parentState,
            child1: inputValue
          })),
        [inputValue]
      );
    

    孩子2

    useEffect(
        () =>
          setParentState((parentState) => ({
            ...parentState,
            child2: inputValue
          })),
        [inputValue]
      );
    

    问题:

    当你点击next/prev时,step值在parent中分别变为1或2。因此,相应的子组件被隐藏并且不再存在于 DOM 上。所以这就是你的useEffect 代码不执行的原因。

    【讨论】:

    • 感谢您的解决方案和描述,但是当我使用 inputValue 而不是 step 时,对于输入的每次更改,父状态都会受到影响。我现在只有当用户点击下一个/上一个按钮时,子状态才保存在父级中。
    【解决方案3】:

    当 step 改变时子组件会卸载,并且它看不到 step prop 何时改变。相反,您应该注意 inputValue 并在每次更改时将其设置为父级。

      useEffect(
        () =>
          setParentState((parentState) => ({
            ...parentState,
            child2: inputValue
          })),
        [inputValue]
      );
    

    【讨论】:

      【解决方案4】:

      如果你真的要使用useEffect,你的useEffect中缺少依赖(useEffect的第二个参数,依赖在数组中):

        useEffect(
          () => setInputValue(parentState.child2), 
         [parentState.child2] //If parentState.child2 change, useEffect function will be called
        );
        
        useEffect(
          () =>
            setParentState((parentState) => ({
              ...parentState,
              child2: inputValue
            })),
          [step, inputValue, setParentState] //If one of these variables change, useEffect function will be called
        );
      

      工作示例here

      但更好的方法是处理这样的更改:

      import React from "react";
      
      export function Child1Comp({ parentState, setParentState }) {
        const handleChange = (e) =>
          setParentState((oldParentValues) => ({
            ...oldParentValues, //Spread old parent states
            [e.target.name]: e.target.value // override child1 input value here
          }));
      
        return (
          <div>
            <br />
            name:
            <input
              id="child1"
              name="child1"
              value={parentState.child1}
              onChange={handleChange}
            />
          </div>
        );
      }
      

      沃金例子here

      注意: 标签(姓名、家庭、...)和姓名(child1、child2、...)是多余的,可以作为道具传递,以便拥有一个 ChildComponent .赞这个demo

      【讨论】:

        【解决方案5】:

        我终于用这个解决方案了:

        我将 saveStep 状态添加到作为 'step,req,conf,action' 对象的父组件:

        • step=> 当前步骤编号。
        • req=>显示更改步骤请求的布尔值。
        • conf=> bool 值,显示子状态保存在父级或否。
        • action=>点击了下一个或上一个按钮。

        仅当更改了 req 和 conf 两个值都为 true 时才执行步骤。

        父组件:

        import React, { useEffect, useState } from "react";
        import { Child1Comp } from "./Child1Comp";
        import { Child2Comp } from "./Child2Comp";
        
        export function ParentComp() {
          const [parentState, setParentState] = useState({});
          const [saveStep, setSaveStep] = useState({
            step: 1,
            req: false,
            conf: false,
            action: ""
          });
        
          useEffect(() => {
            if (saveStep.req && saveStep.conf) {
              setSaveStep((saveStep) => ({
                step:
                  saveStep.action === "next"
                    ? saveStep.step + 1
                    : saveStep.action === "prev"
                    ? saveStep.step - 1
                    : saveStep.step,
                req: false,
                conf: false
              }));
            }
          }, [saveStep]);
        
          const handleNextBtn = () =>
            setSaveStep((saveStep) => ({
              ...saveStep,
              req: true,
              action: "next"
            }));
        
          const handlePrevBtn = () =>
            setSaveStep((saveStep) => ({
              ...saveStep,
              req: true,
              action: "prev"
            }));
        
          return (
            <div>
              <div>{`the parent state is: ${JSON.stringify(parentState)}`}</div>
              {saveStep.step === 1 && (
                <Child1Comp
                  saveStep={saveStep}
                  setSaveStep={setSaveStep}
                  parentState={parentState}
                  setParentState={setParentState}
                />
              )}
              {saveStep.step === 2 && (
                <Child2Comp
                  saveStep={saveStep}
                  setSaveStep={setSaveStep}
                  parentState={parentState}
                  setParentState={setParentState}
                />
              )}
              <br />
              <button
                id="nextBtn"
                name="nextBtn"
                onClick={handleNextBtn}
                disabled={saveStep.step === 2}
              >
                Next
              </button>
              <button
                id="nextBtn"
                name="nextBtn"
                onClick={handlePrevBtn}
                disabled={saveStep.step === 1}
              >
                Prev
              </button>
              <br />
              <br />
              {`current step is : ${saveStep.step}`}
            </div>
          );
        }
        

        Child1Component:

        import React, { useEffect, useState } from "react";
        
        export function Child1Comp({
          saveStep,
          setSaveStep,
          parentState,
          setParentState
        }) {
          const [inputValue, setInputValue] = useState();
          const handleChange = (e) => setInputValue(e.target.value);
        
          useEffect(() => setInputValue(parentState.child1), []);
          useEffect(() => {
            if (saveStep.req) {
              setParentState((parentState) => ({
                ...parentState,
                child1: inputValue
              }));
              setSaveStep((saveStep) => ({ ...saveStep, conf: true }));
            }
          }, [saveStep.req]);
        
          return (
            <div>
              <br />
              child1:
              <input
                id="child1"
                name="child1"
                value={inputValue}
                onChange={handleChange}
              />
            </div>
          );
        }
        

        Child2Component:

        import React, { useEffect, useState } from "react";
        
        export function Child2Comp({
          saveStep,
          setSaveStep,
          parentState,
          setParentState
        }) {
          const [inputValue, setInputValue] = useState();
          const handleChange = (e) => setInputValue(e.target.value);
        
          useEffect(() => setInputValue(parentState.child2), []);
          useEffect(() => {
            if (saveStep.req) {
              setParentState((parentState) => ({
                ...parentState,
                child2: inputValue
              }));
              setSaveStep((saveStep) => ({ ...saveStep, conf: true }));
            }
          }, [saveStep.req]);
        
          return (
            <div>
              <br />
              child2:
              <input
                id="child2"
                name="child2"
                value={inputValue}
                onChange={handleChange}
              />
            </div>
          );
        }
        

        codesandbox Link

        【讨论】:

          猜你喜欢
          • 2019-01-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-10-02
          • 2019-05-09
          • 1970-01-01
          • 1970-01-01
          • 2018-10-05
          相关资源
          最近更新 更多