Home > Software engineering >  React JS update one or more values in an array of form fields stored in state
React JS update one or more values in an array of form fields stored in state

Time:10-21

I've a form created in React JSX with some input and select fields. I'm trying to place some "Clear" buttons on the form that would clear the values of these fields. I should be able to clear one, few or all fields on the form based on the placing of this "Clear" button.

I've tried several options listed below but the main issue that I'm facing is that it's not able to clear both - const variable value as well as the textbox/drop-down value. From debugging, it's able to clear the value of the textbox/drop-down and even the temp array/variable that I create inside the clear function, in most of the cases that I tried, but the value of the const variable for that element in formData doesn't change, which then shows up on the variable when I pass the value to an API on form submission.

Ques: How to clear the value in textbox/drop-down as well as the corresponding state variable of one of the two fields in a form?

Any help is greatly appreciated.

The code for the fields look as following:

<div id="input-1" className="form-group" >
    <label id="input-1-label" className="new-label" htmlFor="form-label">Text One</label>
    <input
        type="text"
        className="form-control"
        id="input-1-id"
        placeholder=""
        name="input1"
        value={input1}
        onChange={(event) => onChange(event)}
    />
</div>

<div id="select-1" className="form-group">
    <label id="select-1-label" className="new-label" htmlFor="form-label">Text One Color</label>
    <select
        className="form-control form-control-lg"
        id="select-1-id"
        placeholder=""
        name="select1"
        value={select1}
        onChange={(event) => onChange(event)}
    >
        <option value="0"></option>
        <option value="1">Black</option>
        <option value="1">Blue</option>
    </select>
</div>

<div id="input-2" className="form-group" >
    <label id="input-2-label" className="new-label" htmlFor="form-label">Text Two</label>
    <input
        type="text"
        className="form-control"
        id="input-2-id"
        placeholder=""
        name="input2"
        value={input2}
        onChange={(event) => onChange(event)}
    />
</div>

<div id="select-2" className="form-group">
    <label id="select-2-label" className="new-label" htmlFor="form-label">Text Two Color</label>
    <select
        className="form-control form-control-lg"
        id="select-2-id"
        placeholder=""
        name="select2"
        value={select2}
        onChange={(event) => onChange(event)}
    >
        <option value="0"></option>
        <option value="1">Black</option>
        <option value="1">Blue</option>
    </select>
</div>

I'm storing the values of these fields in formData constant using state as following:

    const [formData, setFormData] = useState({
        input1: "",
        select1: "",
        input2: "",
        select2: ""
    });

    const {
        input1,
        select1,
        input2,
        select2
    } = formData;

The event handler for on change event is as following:

    const onChange = (event) => {

        setFormData({ ...formData, [event.target.name]: event.target.value });
    };

I've tried several options to be able to clear one or all fields but the values for the input1, select1, input2 and select2 fields in formData state constant doesn't change. For example,

I tried to call following function and passed all the field variables:

    function onClear() {

        var fields = {};
        for (var i = 0; i < arguments.length; i  ) {

            fields[arguments[i]] = "";
        }

        setFormData(fields);
    };

Since, the above was replacing all fields in the formData array, I tried the following, in order to be able to clear only one of the two fields:

    function onClear() {

        var fields = formData;
        for (var i = 0; i < arguments.length; i  ) {

            fields[arguments[i]] = "";
        }

        setFormData(fields);
    };

I've tried to pass the element Ids as following:

    function onClear() {

        for (var i = 0; i < arguments.length; i  ) {
            if ((window.document.getElementById(arguments[i]) != null) && (window.document.getElementById(arguments[i]).value != "")) {
                window.document.getElementById(arguments[i]).value = "";
            }
        }
    };

I've tried to use the spread operator as following:

    function onClear() {

        for (var i = 0; i < arguments.length; i  ) {
            setFormData({ ...formData, [arguments[i]]: "" }); // please ignore the syntax error in this case, I was able to remove them when I tried
        }
    };

I also tried to fire onChange event, like the following:

    function onClear() {

        var fields = formData;

        for (var i = 0; i < arguments.length; i  ) {

            window.document.getElementById('new-general-product-name-input-id').value = "";
            var event = new Event('input', {'input-1-id': ""}); // for testing only i hardcoded the element id
            const input = document.getElementById('input-1-id');
            input.dispatchEvent(event);
        }
    };

I also tried to use array index, but in this case it added an element in the array instead of replacing the field on the array index:

    function onClear() {

        var fields = formData;
        for (var i = 0; i < arguments.length; i  ) {

            fields[i] = "";
        }

        setFormData(fields);
    };

I also tried the option to iterate through the formData array and only update value of the matching fields, but I want to avoid iteration as it may impact performance if there are large number of fiels in a form. I also want to avoid creating separate state variables for each form element.

Is there a better way or suggestion?

CodePudding user response:

The following code worked after much experimentation. I passed all the variable names for the respective fields in a group in the arguments.

function onClear() {

        var fields = formData;

        for (var i = 0; i < arguments.length; i  ) {
            
            fields[arguments[i]] = "";
        }

        // setFormData(fields);
        setFormData({...formData, fields});
    };

CodePudding user response:

So, 1) use Formik - it will save you a lot of pain, and if you use it with 2) Yup for form validation even less pain.

But here's a solution that might work for you.

In short, separate the form into fieldsets (which uses a dumb component, FieldSet), and have the form component manage the state of each fieldset's inputs by their ids. Types of input are managed by a configuration array.

const { useEffect, useState } = React;

function capitalise(str) {
  return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
}

function FieldSet(props) {

  // All of the state management is handled by the
  // parent component
  const {
    fieldSetId,
    legend,
    inputs,
    handleInput,
    handleClearSet,
    values
  } = props;

  // Call the parent handler to deal with the
  // change in input value
  function handleChange(e) {
    const { id, value } = e.target;
    handleInput(fieldSetId, id, value);
  }

  // Call the parent handler to clear the fieldset
  function clearSet(e) {
    handleClearSet(fieldSetId);
  }

  return (
    <fieldset onChange={handleChange}>
      <legend>{legend}</legend>
      {inputs.map(({ value, name, type }) => {
        return (
          <div>
            <label htmlFor={name}>{capitalise(name)}</label>
            <input
              type={type}
              id={name}
              value={values[fieldSetId] && values[fieldSetId][name] || ''}
            />
          </div>
        );
       })}
      <button type="button" onClick={clearSet}>Clear set</button>
    </fieldset>
  );

}

function Example() {

  const [ form, setForm ] = useState({});

  // Clear the data only from the fieldset with
  // the corresponding id
  function handleClearSet(fieldSetId) {
    const emptySet = Object.keys(form[fieldSetId]).reduce((acc, key) => {
      return { ...acc, [key]: '' };
    }, {});
    const refreshSet = { ...form, [fieldSetId]: emptySet };
    setForm(refreshSet);
  }

  // Or clear everything out
  function clearAll() {
    const refreshAll = Object.keys(form).reduce((acc, key) => {
      return {...acc, [key]: {} };
    }, {});
    setForm(refreshAll);
  }

  function handleInput(fieldSetId, id, value) {
    setForm(prev => {
      return {
        ...prev,
        [fieldSetId]: {
          ...prev[fieldSetId],
          [id]: value
        }
      }
    });
  }

  return (
    <form>
      <FieldSet
        fieldSetId="fs1"
        handleInput={handleInput}
        handleClearSet={handleClearSet}
        legend="Simple fieldset"
        inputs={[
          { name: 'name', type: 'text' },
          { name: 'age', type: 'text' }
        ]}
        values={form}
      />
      <FieldSet
        fieldSetId="fs2"
        handleInput={handleInput}
        handleClearSet={handleClearSet}
        legend="Another fieldset"
        inputs={[
          { name: 'location', type: 'text' }
        ]}
        values={form}
      />
      <button type="button" onClick={clearAll}>Clear all</button>
    </form>
  );
}

ReactDOM.render(
  <Example />,
  document.getElementById('react')
);
input { display: block; }
button { margin-top: 0.5em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

For fixing this issue you need to just reset the state like following As you are assigning the state values to constants it'll create a copy of the actual elements in the variables you defined. So to properly reset the state you need to set the stage for all the values to the initial state and also use the formData. instead of created variables.

function onClear() {
      setFormData({
        input1: "",
        select1: "",
        input2: "",
        select2: ""
      });
  }

For reference, you can refer to the following link with the whole working example. https://codesandbox.io/s/react-hooks-playground-forked-ng0xp?file=/src/index.tsx

  • Related