Liki Crus
Liki Crus

Reputation: 2062

Why isn't child component re-rendered even though parent state is updated using hooks

I am a newbie in React and have got a problem using hook. I am going to build a basic form as a child component, but I am wondering why input element on the child form is not rendered when changing its value.

Here is my code.

'use strict';

function SearchForm({form, handleSearchFormChange}) {

    return (
        <div className="card border-0 bg-transparent">
            <div className="card-body p-0 pt-1">
                <div className="form-row">
                    <div className="col-auto">
                        <label className="mr-1" htmlFor="number">Number:</label>
                        <input type="text" className="form-control form-control-sm w-auto d-inline-block"
                               name="number" id="number" value={form.number} onChange={handleSearchFormChange}/>
                    </div>
                </div>
            </div>
        </div>
    );
}

function App() {
    const formDefault = {
        number: 'Initial Value'
    };
    const [form, setForm] = React.useState(formDefault);

    const handleSearchFormChange = (e) => {
        setForm(Object.assign(form, {[e.target.name]: e.target.value}));
        console.log('Handle Search Form Change', e.target.name, "=", e.target.value);
    };

    return (
        <React.Fragment>
            <div>Number : {form.number}</div>
            <SearchForm form={form} handleSearchFormChange={handleSearchFormChange} />
        </React.Fragment>
    );
}

const domContainer = document.querySelector('#root');
ReactDOM.render((
    <React.Fragment>
        <App/>
    </React.Fragment>
), domContainer);

I defined an onChange event handler for 'number' element in parent component and tried to transfer the value to child component using props.

The problem is that when I am going to change 'number', 'number' input element is not changed at all. (Not rendered at all). So that input element has always 'Initial Value'. Could you advise on this?

And I'd like to know if this approach is reasonable or not.

Thanks in advance.

Upvotes: 1

Views: 73

Answers (2)

Nicholas Tower
Nicholas Tower

Reputation: 84902

One of the philosophies of react is that state is immutable. Ie, you don't change properties of the existing state object, but instead you create a new state object. This allows react to tell that the state changed by a simple === check from before and after.

This line of code mutates the existing form object, then passes that into setForm:

setForm(Object.assign(form, {[e.target.name]: e.target.value}));

So react compares the object before setForm and after, and sees that they're the same object. Therefore it concludes that nothing changed, and so the component does not rerender.

Instead, you need to make a copy of the object and make your changes on the copy. If you're used to Object.assign, that can be accomplished by putting an empty object as the first argument to Object.assign:

setForm(Object.assign({}, form, {[e.target.name]: e.target.value}));

Alternatively, the spread syntax is a convenient way to make a shallow copy of an object:

setForm({
  ...form,
  [e.target.name]: e.target.value
})

Upvotes: 2

Ram Angelo Caceres
Ram Angelo Caceres

Reputation: 91

Try replacing setForm(Object.assign(form, {[e.target.name]: e.target.value})); with

setForm(prevState => ({
    ...prevstate,
    [e.target.name]: e.target.value
}));

Upvotes: 1

Related Questions