0

I am a React beginner but I did a very basic form component that does not work! It is this. What is the trouble? Maybe the value={prop.value}?

ReactDOM
    .createRoot(document.getElementById('root'))
    .render(<App />);

function App() {
    const data = [
        {
            type: 'text',
            name: 'name',
            label: 'Name',
            value: 'Alfred'
        },
        {
            type: 'number',
            name: 'amount',
            label: 'Amount',
            value: 23
        },
        {
            type: 'date',
            name: 'birthday',
            label: 'Date',
            value: '1987-02-01'
        }
    ];

    return <Form data={data} />;
}

function Form(props) {
    let [data, setData] = React.useState(props.data);
    const handleChange = (e) => {
        let elem = e.target;
        setData((data) => {
            const prop = data[elem.dataset.index];
            console.log('Input \'' + prop.name + '\' becomes from \'' + prop.value + '\' to \'' + elem.value + '\'');
            data[elem.dataset.index].value = elem.value;
            return data;
        });
    };

    return (
        <form>
            {data.map((prop, i) => {
                const id = "input-" + prop.name;
                return (
                    <div key={prop.name}>
                        <label htmlFor={id}>{prop.label}</label>
                        <span>: </span>
                        <input
                            type={prop.type}
                            id={id}
                            value={prop.value}
                            onChange={handleChange}
                            data-index={i}
                        />
                    </div>
                );
            })}
        </form>
    );
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Nick Vu
  • 14,512
  • 4
  • 21
  • 31

1 Answers1

1

React's state is immutable, so you cannot modify the array data directly.

You have to change return data to return [...data] which is to clone your array to a new value in setData of handleChange

ReactDOM
    .createRoot(document.getElementById('root'))
    .render(<App />);

function App() {
    const data = [
        {
            type: 'text',
            name: 'name',
            label: 'Name',
            value: 'Alfred'
        },
        {
            type: 'number',
            name: 'amount',
            label: 'Amount',
            value: 23
        },
        {
            type: 'date',
            name: 'birthday',
            label: 'Date',
            value: '1987-02-01'
        }
    ];

    return <Form data={data} />;
}

function Form(props) {
    let [data, setData] = React.useState(props.data);
    const handleChange = (e) => {
        let elem = e.target;
        setData((data) => {
            const prop = data[elem.dataset.index];
            console.log('Input \'' + prop.name + '\' becomes from \'' + prop.value + '\' to \'' + elem.value + '\'');
            data[elem.dataset.index].value = elem.value;
            return [...data]; //the change is here
        });
    };

    return (
        <form>
            {data.map((prop, i) => {
                const id = "input-" + prop.name;
                return (
                    <div key={prop.name}>
                        <label htmlFor={id}>{prop.label}</label>
                        <span>: </span>
                        <input
                            type={prop.type}
                            id={id}
                            value={prop.value}
                            onChange={handleChange}
                            data-index={i}
                        />
                    </div>
                );
            })}
        </form>
    );
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Nick Vu
  • 14,512
  • 4
  • 21
  • 31
  • I didn't modify the array data directly: I try to change using `useState`. I don't understand the difference. If I return an array by callback, React must to know that it is the new one. :'( – Miquel Al. Vicens Oct 18 '22 at 08:44
  • You can read [this article](https://blog.logrocket.com/immutability-in-react-ebe55253a1cc/) for understanding your situation better :D – Nick Vu Oct 18 '22 at 09:10
  • I know the JavaScript behavior. To re-render the view, React needs a different reference of the returned array in `useState`, right? – Miquel Al. Vicens Oct 18 '22 at 09:33
  • yeah that's true! @MiquelAl.Vicens – Nick Vu Oct 18 '22 at 09:40
  • Then, for now, I don't like React! xD – Miquel Al. Vicens Oct 18 '22 at 09:44
  • With the usual value-to-value comparison approach, you can imagine that you have 1000 value changes, but React has to go through all of them to compare values one by one. That will cause a huge performance issue in re-renderings, so that's why React's team wants to check reference values which will cause fewer problems. You can check this answer which is why immutability is essential https://stackoverflow.com/a/34385684/9201587 @MiquelAl.Vicens – Nick Vu Oct 18 '22 at 09:50
  • And also you can pass a exactly same values with different reference (in our case, commenting the `data[elem.dataset.index].value = elem.value;` line) and React go through all and re-render too. – Miquel Al. Vicens Oct 18 '22 at 10:43