Search code examples
javascriptreactjsinputreact-hooksonchange

Change an input and reflact change on several other inputs with React


I have 19 inputs and when one of them is changed, all the others reflect the change with a calculation.

<input value={mph} onChange={(e) => cmph(e.target.value, setKph, setMps)}
<input value={kph} onChange={(e) => ckph(e.target.value, setMph, setMps)}
<input value={mps} onChange={(e) => cmps(e.target.value, setKph, setMph)}

For each input I have one state. So I used 19 useState variables. And all input have an onChange.

  const [mph, setMph] = useState() // miles per hour
  const [kph, setKph] = useState() // kiliometers per hour
  const [mps, setMps] = useState() // miles per seconds

So when any input is changed, it's onChange call its unique function do the calculation and set the state 18 times (for each of the other input).

Each input calls it's own function that calculates the value of the others. Its simple math. No DB or endpoint calling.

function cmph(mph, setKph, setMps) {
  const kph = mph * 1.60934
  setKph(Math.round((kph + Number.EPSILON) * 100) / 100)

  const mps = mph / 2.237
  setMps(Math.round((mph + Number.EPSILON) * 100) / 100)
}

function ckph(kph, setMph, setMps) {
  const mph = kph / 1.60934
  setMph(Math.round((mph + Number.EPSILON) * 100) / 100)

  const mps = mph / 2.237
  setMps(Math.round((mps + Number.EPSILON) * 100) / 100)
 }

function cmps(mps, setKph, setMph) {
  const mph = mps * 2.237
  setMph(mph)

  const kph = mph * 1.60934
  setKph(kph)
}

All I want is change any of the inputs and all others reflect the new value automatically.

PS: I reduced for three inputs so the example will be smaller. But I have 19 of these.

How can I solve this?

Stack Snippet:

const { useState } = React;

const Example = () => {
    const [mph, setMph] = useState(); // miles per hour
    const [kph, setKph] = useState(); // kiliometers per hour
    const [mps, setMps] = useState(); // miles per seconds

    function cmph(mph, setKph, setMps) {
        const kph = mph * 1.60934;
        setKph(Math.round((kph + Number.EPSILON) * 100) / 100);

        const mps = mph / 2.237;
        setMps(Math.round((mph + Number.EPSILON) * 100) / 100);
    }

    function ckph(kph, setMph, setMps) {
        const mph = kph / 1.60934;
        setMph(Math.round((mph + Number.EPSILON) * 100) / 100);

        const mps = mph / 2.237;
        setMps(Math.round((mps + Number.EPSILON) * 100) / 100);
    }

    function cmps(mps, setKph, setMph) {
        const mph = mps * 2.237;
        setMph(mph);

        const kph = mph * 1.60934;
        setKph(kph);
    }

    return (
        <div>
            <input
                value={mph}
                size="5"
                onChange={(e) => cmph(e.target.value, setKph, setMps)}
            />
            <input
                value={kph}
                size="5"
                onChange={(e) => ckph(e.target.value, setMph, setMps)}
            />
            <input
                value={mps}
                size="5"
                onChange={(e) => cmps(e.target.value, setKph, setMph)}
            />
        </div>
    );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>


Solution

  • The problem is that your inputs are controlled, which means their values don't automatically update unless you set the state member you're using to set their value. The reason it works initially is that by not giving an initial value to useState, you're defaulting to undefined, and value={undefined} makes React think the input is uncontrolled. As soon as you change a value, the inputs go from uncontrolled to controlled, and it stops working. (If you use the development version of the React libs uring development, you'll get a big warning about that.) More about controlled vs. uncontrolled inputs in the documentation.

    Instead:

    • Use initial values with useState so that they don't default to undefined
    • When processing the change event of each input, update the state member that controls it

    For instance, for mph:

    const mphOnChange = ({ currentTarget: { value } }) => {
        setMph(value); // <======================= This is what was missing
        cmph(value, setKph, setMps);
    };
    

    Working example (those default values may need tweaking):

    const { useState } = React;
    
    function cmph(mph, setKph, setMps) {
        const kph = mph * 1.60934;
        setKph(Math.round((kph + Number.EPSILON) * 100) / 100);
    
        const mps = mph / 2.237;
        setMps(Math.round((mph + Number.EPSILON) * 100) / 100);
    }
    
    function ckph(kph, setMph, setMps) {
        const mph = kph / 1.60934;
        setMph(Math.round((mph + Number.EPSILON) * 100) / 100);
    
        const mps = mph / 2.237;
        setMps(Math.round((mps + Number.EPSILON) * 100) / 100);
    }
    
    function cmps(mps, setKph, setMph) {
        const mph = mps * 2.237;
        setMph(mph);
    
        const kph = mph * 1.60934;
        setKph(kph);
    }
    
    const Example = () => {
        const [mph, setMph] = useState(0); // miles per hour
        const [kph, setKph] = useState(0); // kiliometers per hour
        const [mps, setMps] = useState(0); // miles per seconds
    
        const mphOnChange = ({ currentTarget: { value } }) => {
            setMph(value);
            // Recommend explicit conversion to number here
            cmph(value, setKph, setMps);
        };
    
        const kphOnChange = ({ currentTarget: { value } }) => {
            setKph(value);
            // Recommend explicit conversion to number here
            ckph(value, setMph, setMps);
        };
    
        const mpsOnChange = ({ currentTarget: { value } }) => {
            setMps(value);
            // Recommend explicit conversion to number here
            cmps(value, setKph, setMph);
        };
    
        return (
            <div>
                <input value={mph} size="5" onChange={mphOnChange} />
                <input value={kph} size="5" onChange={kphOnChange} />
                <input value={mps} size="5" onChange={mpsOnChange} />
            </div>
        );
    };
    
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<Example />);
    <div id="root"></div>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>