I'm sure you've seen this React code:
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
The key
prop is necessary so each element is identifiable.
But did you know you can pass key
to a component even though there's no .map(...)
or other array of React elements.
Consider this simplified app:
function Form({ user }: { user: User }) {
const [comment, setComment] = useState("");
return (
<form>
<label htmlFor="comment">User comment for '{user.name}':</label>
<textarea
id="comment"
aria-label="User comment"
value={comment}
onChange={(event) => setComment(event.target.value)}
/>
</form>
);
}
And you use it like this:
<Form user={currentUser} />
The problem is; if that currentUser
changes in consequent renders, it won't reset that const [comment, setComment] = useState("")
state. Perhaps that's not desired, but it's not implausible that you'd want, as per this simple example, a different comment
for different users.
To achieve that, add the magical prop key
:
<Form user={currentUser} key={currentUser.id} />
Now, that const [comment, setComment] = useState("")
state, inside the <Form>
component, will "reset" as the currentUser.id
changes.
An alternative solution
In the above example, the <Form>
component completely "starts over" when the key={...}
prop changes. What if the <Form>
component depends on a bunch of other props such as jokeOfTheDay={joke} writingStyle={style} {...etc}
.
You might not want to reset the const [comment, setComment] = useState("")
state just because the writingStyle={...}
prop changes.
To accomplish this, you can keep the user
prop as a piece of state. Like this:
const [previousUser, setPreviousUser] = useState(user);
And when it changes, you apply whatever resets (state updates) you need. Like this:
const [previousUser, setPreviousUser] = useState(user);
if (user !== previousUser) {
setPreviousUser(user);
setComment("");
}
This pattern also has the advantage that, from within the component, you don't need to tell the caller of it what it should do.
Comments