Home > Enterprise >  State not updating while being set in the same function as another setState
State not updating while being set in the same function as another setState

Time:10-19

Whenever I try to set the state of "activeSubStage" in the same function as setUserData (A state from the Signup component) it refuses to actually update, the useEffect DOES get trigged but the actual value never changes. If the setUserData part of the handleSaveData function the activeSubStage does actually update correctly.

Just an FYI this code has been shortened to remove unrelated parts.

export default function Signup() {
  const [userData, setUserData] = useState([]);

  function PersonalDetailsStage() {
    const [activeSubStage, setActiveSubStage] = useState(0);

    function ContactSubStage() {
      const [firstName, setFirstName] = useState('');
      const [lastName, setLastName] = useState('');
      const [address, setAddress] = useState('');
      const [address2, setAddress2] = useState('');
      const [suburb, setSuburb] = useState('');
      const [postCode, setPostCode] = useState('');
      const [state, setState] = useState < any > '';
      const [mobile, setMobile] = useState('');

      useEffect(() => {
        console.log('Active Sub Stage: '   activeSubStage);
      }, [activeSubStage]);

      const handleSaveData = () => {
        setActiveSubStage(activeSubStage   1);
        setUserData([
          ...userData,
          {
            contactDetails: {
              firstName: firstName,
              lastName: lastName,
              address: address,
              addressLine2: address2,
              suburb: suburb,
              postCode: postCode,
              state: state,
              mobile: mobile,
            },
          },
        ]);
      };

      return (
        <form>
          <Stack spacing={2}>
            <TextField
              value={firstName}
              onChange={(e) => setFirstName(e.target.value)}
              label="First name"
              required
            />
            /*^ Repeated For Each Input ^*/
            <Box sx={{ mb: 2 }}>
              <div>
                <Button
                  variant="contained"
                  onClick={handleSaveData}
                  type="button"
                  sx={{ mt: 1, mr: 1 }}
                  disabled={
                    !firstName || !lastName || !address || !suburb || !postCode || !state || !mobile
                  }
                >
                  Continue
                </Button>
              </div>
            </Box>
          </Stack>
        </form>
      );
    }
  }
}

CodePudding user response:

Hooks and closures aren't working as you expect them to work. In order to get component being updated(thus - re-running your hooks) - you need to pass it through "default" mechanism - via props/context/custom hook.

Also, defining component within other component might end up with bugs - so I recommend you to simply define bunch of components separately from each other (they might be in the same file, if you want to keep them in same logical scope, that's not a problem). So, your components structure should rather be looking like this:

function ContactSubStage({ 
  activeSubStage, 
  setActiveSubStage, 
  setUserData, 
  userData 
}) {
      const [firstName, setFirstName] = useState('');
      const [lastName, setLastName] = useState('');
      const [address, setAddress] = useState('');
      const [address2, setAddress2] = useState('');
      const [suburb, setSuburb] = useState('');
      const [postCode, setPostCode] = useState('');
      const [state, setState] = useState <any>('');
      const [mobile, setMobile] = useState('');

      useEffect(() => {
        console.log('Active Sub Stage: '   activeSubStage);
      }, [activeSubStage]);

      const handleSaveData = () => {
        setActiveSubStage(activeSubStage   1);
        setUserData([
          ...userData,
          {
            contactDetails: {
              firstName: firstName,
              lastName: lastName,
              address: address,
              addressLine2: address2,
              suburb: suburb,
              postCode: postCode,
              state: state,
              mobile: mobile,
            },
          },
        ]);
      };

      return (
        <form>
          <Stack spacing={2}>
            <TextField
              value={firstName}
              onChange={(e) => setFirstName(e.target.value)}
              label="First name"
              required
            />
            /*^ Repeated For Each Input ^*/
            <Box sx={{ mb: 2 }}>
              <div>
                <Button
                  variant="contained"
                  onClick={handleSaveData}
                  type="button"
                  sx={{ mt: 1, mr: 1 }}
                  disabled={
                    !firstName || !lastName || !address || !suburb || !postCode || !state || !mobile
                  }
                >
                  Continue
                </Button>
              </div>
            </Box>
          </Stack>
        </form>
      );
}

function PersonalDetailsStage({ userData, setUserData }) {
  const [activeSubStage, setActiveSubStage] = useState(0);

  return (<ContactSubStage 
            activeSubStage={activeSubStage} 
            setActiveSubStage={setActiveSubStage} 
            userData={userData} 
            setUserData={setUserData}
          />)
}

export default function Signup() {
  const [userData, setUserData] = useState([]);
  return <PersonalDetailsStage userData={userData} setUserData={setUserData} />
}

I also assume you have other Stage and SubStage components, if so - this components structure will make sense. Better practice also might be to create handle functions within parent component and not directly passing userData and setUserData and etc.

CodePudding user response:

The nested components should be extracted to the top level.

What's happening here is that every time <Signup /> re-renders, it's redeclaring the nested components, and each time that happens, activeSubStage is initialised to the default state of 0.

Just as a quick example, you can look at this:

function ContactSubStage({
  activeSubStage,
  setActiveSubStage,
  userData,
  setUserData,
}) {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [address, setAddress] = useState("");
  const [address2, setAddress2] = useState("");
  const [suburb, setSuburb] = useState("");
  const [postCode, setPostCode] = useState("");
  const [state, setState] = useState("");
  const [mobile, setMobile] = useState("");

  useEffect(() => {
    console.log("Active Sub Stage: "   activeSubStage);
  }, [activeSubStage]);

  const handleSaveData = () => {
    setActiveSubStage(activeSubStage   1);
    setUserData([
      ...userData,
      {
        contactDetails: {
          firstName: firstName,
          lastName: lastName,
          address: address,
          addressLine2: address2,
          suburb: suburb,
          postCode: postCode,
          state: state,
          mobile: mobile,
        },
      },
    ]);
  };

  return (
    <form>
      <Stack spacing={2}>
        <TextField
          value={firstName}
          onChange={(e) => setFirstName(e.target.value)}
          label="First name"
          required
        />
        {/*^ Repeated For Each Input ^*/}
        <Box sx={{ mb: 2 }}>
          <div>
            <Button
              variant="contained"
              onClick={handleSaveData}
              type="button"
              sx={{ mt: 1, mr: 1 }}
              disabled={
                !firstName ||
                !lastName ||
                !address ||
                !suburb ||
                !postCode ||
                !state ||
                !mobile
              }
            >
              Continue
            </Button>
          </div>
        </Box>
      </Stack>
    </form>
  );
}

function PersonalDetailsStage({ userData, setUserData }) {
  const [activeSubStage, setActiveSubStage] = useState(0);

  return (
    <ContactSubStage
      userData={userData}
      setUserData={setUserData}
      activeSubStage={activeSubStage}
      setActiveSubStage={setActiveSubStage}
    />
  );
}

function Signup() {
  const [userData, setUserData] = useState([]);

  return <PersonalDetailsStage userData={userData} setUserData={setUserData} />;
}
  • Related