Is initial state based on props always bad in React?
Asked Answered
P

2

6

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?

Pavyer answered 17/2, 2022 at 18:25 Comment(5)
It's totally fine to initialize state with anything, props if need be. The anti-pattern is syncing it.Irascible
@EmileBergeron I've never heard calling having state and props in sync an anti-pattern.Pavyer
It's usually seen as an anti-pattern because it makes the state useless, as the prop should be used as-is. In your case, you don't have to sync the state with the prop, so that the user can update the value with an input.Irascible
Does this answer your question? React Hooks: handle multiple inputsIrascible
Based on your comments, I see an XY problem, where you're asking about some potential bad practice but the question should describe your situation, with a minimal reproducible example that provides more context.Irascible
E
1

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.

Echoechoic answered 17/2, 2022 at 18:35 Comment(5)
It actually does make sense in my real-life example, which perhaps I should have provided. The source of data is updated when button is clicked and this source of information is also the initial value. But typing in the input doesn't update it instantly.Pavyer
@RoboRobok in your case, it sounds like the state should be lifted up.Irascible
I agree with @EmileBergeron, you might need to control your state in the parent. If you need a component that's an input with some styles or some other stateless elements surrounding it, make the MyInput component accept props of value and onChange and pass it to the inner input it rendersEchoechoic
Well, yeah, but in real life having input's value in parent component gets ugly pretty quick if you have complex UI.Pavyer
It shouldn't be too bad, it's hard for me to tell without proper context, perhaps there's a better solution for your situation, but anyway what you're doing in this example makes little sense to meEchoechoic
B
1

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: "[email protected]",
    });
    return (
        <>
            <EmailInput
                initialValue={user.email}
                key={user.id}
                onChange={(value) => setUser({ id: user.id, email: value })}
            />
            <button
                onClick={() => setUser({id: 2, email: "[email protected]"})}
            >
                Update user
            </button>
        </>
    );
}
Basilio answered 13/1, 2023 at 8:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.