Skip to content

React form how to get user data based on a toggle on/off

I have a form in React JS with one toggle/switch. If toggle/switch is on, then two inputs appear in the screen. So i want to get user data if the user types in inputs and the toggle/switch is on and stays on. So if the user types in inputs but he toggles/switches again to off then input values get reset and when he saves the form i must get empty user data(i get the initial values). How can i achieve something like this? I’m checking in submit handler if the switch button is false and im setting the usestate to the initial values, but it doesnt work.

My code:

Form.js

import React, { useRef, useState } from "react";
import Wrapper from "./UI/Wrapper";
import Switch from '@mui/material/Switch';
import "./Form.css";

const Form = () => {

  const [showCertification, setShowCertification] = useState(false);
  
  const [enteredCodecert, setEnteredCodecert] = useState('');
  const codecertRef = useRef();

  const [codesteps, setCodesteps] = useState([{ value: null }]);
  const codestepsRef = useRef();

  const enteredCodecertIsValid = showCertification && enteredCodecert.trim() !== '';
  const codecertInputIsInvalid = !enteredCodecertIsValid;

  const codestepsIsValid = showCertification && codesteps.length >= 1 && codesteps.every(codestep => codestep.value !== null && codestep.value.trim() !== '');
  const codestepInputIsInvalid = !codestepsIsValid;


  const showCertificationHandler = (event) => {
    setShowCertification(prevState => !prevState);
    if (!showCertification) {
      setEnteredCodecert('');
      setCodesteps([{value: null}]);
    }
  }

  const codecertChangeHandler = (event) => {
    setEnteredCodecert(event.target.value);
  }

  const stepChangeHandler = (i, event) => {
    const values = [...codesteps];
    values[i].value = event.target.value;
    setCodesteps(values);
  }

  const addStepHandler = (event) => {
    event.preventDefault();
    const values = [...codesteps];
    values.push({ value: null });
    setCodesteps(values);
  }

  const removeStepHandler = (i, event) => {
    event.preventDefault();
    const values = [...codesteps];
    values.splice(i, 1);
    setCodesteps(values);
  }

  const submitHandler = (event) => {
    event.preventDefault();

    if (!enteredCodecertIsValid && showCertification) {
      codecertRef.current.focus();
      return;
    }

    if (!codestepsIsValid && showCertification) {
      if (codesteps.length >= 1) {
        codestepsRef.current.focus();
        return;
      }
      return;
    }

    if (showCertification === false) {
      setEnteredCodecert('');
      setCodesteps([{value: null}]);
    }

    console.log(enteredCodecert);
    console.log(codesteps);
  }


  return (
    <Wrapper>
      <form onSubmit={submitHandler}>

        <fieldset className={`${(showCertification && codecertInputIsInvalid) || (showCertification && codestepInputIsInvalid)  ? 'govgr-form-group__error' : '' }`}>
          <legend><h3 className="govgr-heading-m">Certifications</h3></legend>

          <Switch id="certification" checked={showCertification} onClick={showCertificationHandler} inputProps={{ 'aria-label': 'controlled' }} />
          <label className="govgr-label govgr-!-font-weight-bold cert-label" htmlFor="certification">Certification</label>
          {showCertification && (
          <div>
            <div className="govgr-form-group">
              <label className="govgr-label govgr-!-font-weight-bold" htmlFor="codecert">Code Certification*</label>
              {codecertInputIsInvalid && <p className="govgr-error-message"><span className="govgr-visually-hidden">Λάθος:</span>Code Certification is required.</p>}
              <input className={`govgr-input govgr-!-width-three-quarter ${codecertInputIsInvalid ? 'govgr-error-input' : ''}`} id="codecert" name="codecert" type="text" value={enteredCodecert} ref={codecertRef} onChange={codecertChangeHandler} />
           </div>

            <div className="govgr-form-group">
              <label className="govgr-label govgr-!-font-weight-bold" htmlFor="codestep">Code STEPS*</label>
              {codestepInputIsInvalid && <p className="govgr-error-message"><span className="govgr-visually-hidden">Λάθος:</span>Code STEPS are required.</p>}
                  {codesteps.map((field, idx) => {
                    return (
                    <div key={`${field}-${idx}`}>
                      <div className="flex-row">
                        <input className={`govgr-input govgr-input--width-10 input-step ${codestepInputIsInvalid ? 'govgr-error-input' : ''}`} id="codestep" type="text" ref={codestepsRef} value={field.value || ""} onChange={e => stepChangeHandler(idx, e)} />
                        <button className="govgr-btn govgr-btn-warning remove-step" onClick={(e) => removeStepHandler(idx, e)}>Χ</button>
                      </div>  
                    </div>
                    );
                  })}
                  <button className="govgr-btn govgr-btn-secondary button-step" onClick={addStepHandler}>Add Code Step</button>       
            </div>
          </div>
          )}
        </fieldset>

        <button className="govgr-btn govgr-btn-primary btn-center" type="submit">Save</button>

      </form>
    </Wrapper>
  );
};

export default Form;

Answer

The issue is that in showCertificationHandler when you toggle the showCertification you are expecting the state update to be immediate.

const showCertificationHandler = (event) => {
  setShowCertification(prevState => !prevState);
  if (!showCertification) {
    setEnteredCodecert('');
    setCodesteps([{value: null}]);
  }
}

This is not the case with React state updates, however. React state updates are enqueued and asynchronously processed.

To resolve, move the “reset” logic into an useEffect hook with a dependency on the showCertification state.

const showCertificationHandler = () => {
  setShowCertification((prevState) => !prevState);
};

useEffect(() => {
  if (!showCertification) {
    setEnteredCodecert("");
    setCodesteps([{ value: null }]);
  }
}, [showCertification]);

For the same reason above, when resetting the states in your submitHandler they are enqueued and asynchronously processed, so console logging the state immediately after will only ever log the state values from the current render cycle, not what they will be on a subsequent render cycle. You can remove the “reset” logic from submitHandler.

const submitHandler = (event) => {
  event.preventDefault();

  if (!enteredCodecertIsValid && showCertification) {
    codecertRef.current.focus();
    return;
  }

  if (!codestepsIsValid && showCertification) {
    if (codesteps.length >= 1) {
      codestepsRef.current.focus();
      return;
    }
    return;
  }

  console.log({enteredCodecert, codesteps});
};

Edit react-form-how-to-get-user-data-based-on-a-toggle-on-off