1
votes

I have a parent component with array of children that displays the next children every time the next button in clicked.

const SqlSchema = () => {
  const [activeStep, setActiveStep] = useState(0);
  const [stepResponse, setStepResponse] = useState();
  const steps = ['Connect', 'Define tables', 'Define hierarchy', 'Finish']; 

  const getStepContent = (step) => {
    switch (step) {
      case 0:
        return <ConnectToDB onSubmit={setStepResponse} />;
      case 1:
        return <DefineTables data={stepResponse.data} onSubmit={setStepResponse} />;
      case 2:
        return <DefineHierarchy data={stepResponse.data} onSubmit={setStepResponse} />;
      case 3:
          return <Finish data={stepResponse.data} onSubmit={setStepResponse} />
    }
  };

  const handleNext = () => {
    setActiveStep((prevActiveStep) => prevActiveStep + 1);
  };

  const handleBack = () => {
    setActiveStep((prevActiveStep) => prevActiveStep - 1);
  };

  return (
     <Stepper activeStep={activeStep}>
        {steps.map(label => (
            <Step key={label}>
              <StepLabel>{label}</StepLabel>
            </Step>
        ))}
      </Stepper>

      <div>
        {activeStep === steps.length ? (
          <Typography className={classes.instructions}>
            All steps completed - you&apos;re finished
          </Typography>
        ) : (
          <div>
            <Typography>{getStepContent(activeStep)}</Typography>
            <div>
              <Button disabled={activeStep === 0} onClick={handleBack} className={classes.button}>
                Back
              </Button>
              <Button onClick={handleNext} className={classes.button}>
                {activeStep === steps.length - 1 ? 'Finish' : 'Next'}
              </Button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

every child gets data and onSubmit props, and manage own state based on the data prop. I want to execute the onSubmit prop with the child state when next button is clicked, and update the parent data with the child state and send it to the next child. I tried to do something like this in every child:

const [value, setValue] = useState(props.data);
useEffect(() => {
  return () => props.onSubmit(value);
}, []);

but I it sends the initial state instead of the updated state. if I add the "value" to the dependencies array in useEffect the onSubmit would be called on every setValue, and I don't want that. is there a way to get the updated state from the child only when the "next" button is clicked in the parent?

1
Is there anything wrong with keeping the parent state in sync with the current child component? It is really why you lift state in the first place, and also wanting to have a single source of truth (to basically help avoid state synchronicity issues like you have and are trying to solve for). - Drew Reese
@DrewReese does it make sense to change the parent state whenever the child is changed? It would trigger rerender of the parent on every small change in one of its descendants. - fullstack
Yes, completely. It is normal to store "common" state in a common, accessible location, i.e. a parent component and passed to each child, in a React context and consumed by children, app-wide state management (redux, mobx, etc) and subscribed to. The element in common here is having a single source of truth, the app only needs to update "state" in a single location. If you don't want other children to rerender when the parent does, well, react has tools/patterns for that. Anyway, use @thedude's solution if you want to hold on and update parent state on child component unmount - Drew Reese

1 Answers

1
votes

I have to say this seems like a strange design choice, but you could use a ref to keep track of the current value:

const [value, setValue] = useState(props.data);
const valueRef = useRef(value)

useEffect(() => {
    valueRef.current = value
}, [value])

useEffect(() => {
  return () => props.onSubmit(valueRef.current);
}, []);