简体   繁体   中英

Is initial state based on props always bad in React?

It's a common React knowledge that having state initialized by props is bad if we don't make them in sync. This is considered fine:

import { useState, useEffect } from 'react';

export default function MyInput({ initialValue }) {
    const [value, setValue] = useState(initialValue);

    useEffect(
        () => setValue(initialValue),
        [initialValue]
    );

    return (
        <>
            <h1>The value is {value}</h1>
            <input
                type="text"
                value={value}
                onChange={event => setValue(event.target.value)}
            />
        </>
    );
}

But what if I actually don't want to update the value when initialValue changes and want to remove the useEffect() here? Is it strongly against React philosophy? It makes sense in my case, as I actually don't want to update this input value when something else changes the value passed as initialValue . I don't want users to lose their input when that happens.

How bad is it?

In essence, there's nothing wrong with using a prop as the initial value of a state variable, AFAIK.

However, in your example you're doing something that is kind of nonsensical: You are defining a state variable which is initialized with the value of a prop, and then every time the prop updates you update your state with the same value. Regardless of whether it's an anti-pattern or not, it makes no sense - just use the prop directly, you're doing extra work for no profit. If you remove the useEffect you'll get a very valid use for a prop as an initial value of a state variable.

The question of using a derived state in React.js is often misunderstood, which this StackOverflow question proves.

In the provided code example from the question, it is unclear why a derived state is being used when the initialValue prop could be used directly. For the sake of clarity, using useEffect for this purpose would be considered an antipattern. Instead, you should check for changes yourself, as demonstrated in the React documentation

However, if the EmailInput component does some modification on the initialValue , such logic will unnecessarily pollute the parent component, if we followed the"rule of lifting state up" (Which I believe the author attempts to explain in this comment ).

In this case, I would argue that the antipattern may be an acceptable choice if used sparingly. Robin Wieruch blog post where said antipattern is used.

An alternative solution is to use the key attribute ( useful in this case ), but this is only effective if the key and initialValue are based off different states. Otherwise, it may lead to duplicate renderings.

Example with the key attribute

// EmailInput.jsx
export default function EmailInput({ initialValue, onChange }) {
    const [value, setValue] = useState(initialValue);

    const handleChange = (event) => {
        const newValue = event.target.value;
        // do some modification on the newValue
        setValue(newValue);
        onChange(newValue); // pass value to parent
    };

    return (
        <>
            <h1>The Email is {value}</h1>
            <input type="text" value={value} onChange={handleChange} />
        </>
    );
}
// Checkout.jsx
export function Checkout() {
    const [user, setUser] = useState({
        id: 1,
        email: "example@example.com",
    });
    return (
        <>
            <EmailInput
                initialValue={user.email}
                key={user.id}
                onChange={(value) => setUser({ id: user.id, email: value })}
            />
            <button
                onClick={() => setUser({id: 2, email: "foo@bar.com"})}
            >
                Update user
            </button>
        </>
    );
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM