Reputation: 22775
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?
Upvotes: 7
Views: 11319
Reputation: 2243
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>
</>
);
}
Upvotes: 1
Reputation: 1540
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.
Upvotes: 2