Swix
Swix

Reputation: 2113

Unify input value with React useState

I have this three Quantity inputs in a form. (Actually, it can be added more dynamically, approx 15 or more. But for the sake of simplification, I said three.)

| - | - | Quantity |
|---|---|----------|
| - | - |    3     |
| - | - |    4     |
| - | - |    5     |

I want to add another input above them to add a single value and set them all same.

| - | - | Quantity |
|---|---|----------|
| - | - |    3     | <--- This input will set the same value for inputs below
|---|---|----------|
| - | - |    3     |
| - | - |    3     |
| - | - |    3     |

But the original three Quantity inputs will be still able to change each value.

Here's my attempt:

// form.tsx

...

  const [value, setValue] = useState(0 || undefined)
  const handleValue = (e: any) => setValue(e.target.value)

  ...

  {/* Unify input to set the same value in inputs below */}
  <input value={value} onChange={handleValue} />

  {/* Without onChange, input will be set readOnly automatically, so I tried adding something */}
  <input value={value} onChange={() => undefined} />
  <input value={value} onChange={(e: any) => e.target.value} />
  <input value={value} onChange={null} />
  <input value={value} onChange={false} />

...

Unify input works, it sets values. But I can't edit inputs below. What am I doing wrong?


Stack - React:v16.13.1, TypeScript:v3.9.5

Upvotes: 1

Views: 185

Answers (3)

Son Nguyen
Son Nguyen

Reputation: 1482

Solution with customizable number of inputs:

    const NUM_INPUTS = 3;
    const range: string[] = (Array.from(Array(NUM_INPUTS).keys()).map(i => ''));
    const [value, setValue] = useState<string | undefined>(undefined);
    const [values, setValues] = useState(range);

    // Handle unified value
    const handleValue = (e: any) => {
        setValue(e.target.value);
        setValues(range.fill(e.target.value));
    }
    // Handle individual inputs
    const handleOtherValue = (e: any) => {
        values[+e.target.name] = e.target.value;
        setValues(values);
    }

    // Create array of input elements
    const inputs = range.map((_, i) =>
        (<input key={i} name={i.toString()}
            defaultValue={values[i]} onChange={handleOtherValue}/>));
            // Important to use defaultValue so inputs are mutable

    return (
       <div>
            <input onChange={handleValue}/>
            <Fragment key={value}> {/* important for React to auto update these inputs */}
                {inputs}
            </Fragment>
       </div>
     );

Upvotes: 1

iamhuynq
iamhuynq

Reputation: 5529

You need to store unify value and rest values in 2 separate state

const [valueAll, setValueAll] = useState(0)

const [value, setValue] = useState({
    input1: 0,
    input2: 0,
    input3: 0
})

const handleValue = (e: any) => {
    setValueAll(e.target.value)
    let newValue = value;
    Object.keys(newValue).forEach(key => {
      newValue[key] = e.target.value
    });
    setValue(newValue)
}

<input value={valueAll} onChange={handleValue} />
<input value={value.input1} name="input1" onChange={handleEachValue} />
<input value={value.input2} name="input2" onChange={handleEachValue} />
<input value={value.input3} name="input3" onChange={handleEachValue} />

You can check here codesandbox

Upvotes: 1

Mike Rivet
Mike Rivet

Reputation: 106

The issue seems to come from all of the inputs having the same value (the value state). This provides an easy way for you to update all values at once but as you can see doesn't allow updating them individually. The only way they can change is if setValue() is called which is in the onChange event in the unify value.

To correct this, you'd want each value to have individual state. This could be done by storing an array of values in state, but for simplicity I just implemented additional useState hooks.

export const Form = () => {
    const [unifyValue, setUnifyValue] = useState(0);
    const [firstValue, setFirstValue] = useState(0);
    const [secondValue, setSecondValue] = useState(0);
    const [thirdValue, setThirdValue] = useState(0);

    const handleUnifyValue = (e: any) => {
        setUnifyValue(e.target.value);
        setFirstValue(e.target.value);
        setSecondValue(e.target.value);
        setThirdValue(e.target.value);
    }
    const handleFirstValue = (e: any) => {
        setFirstValue(e.target.value);
    }
    const handleSecondValue = (e: any) => {
        setSecondValue(e.target.value);
    }
    const handleThirdValue = (e: any) => {
        setThirdValue(e.target.value);
    }

    return (
        <form>
            {/* Unify input to set the same value in inputs below */}
            <input value={unifyValue} onChange={handleUnifyValue} />

            {/* Without onChange, input will be set readOnly automatically, so I tried adding something */}
            <input value={firstValue} onChange={handleFirstValue} />
            <input value={secondValue} onChange={handleSecondValue} />
            <input value={thirdValue} onChange={handleThirdValue} />
        </form>
    )
}

If I can get the state array working nicely I'll update this with that example. That would allow more than just 3 inputs and also reduce the need for so many useState and handleXValue lines.

Upvotes: 1

Related Questions