react
react copied to clipboard
Bug: Stale values returned from useOptimistic when state changes
The optimisticState returned from useOptimistic is stale when the state param passed to useOptimistic is changed. The change could come from an RSC reponse or from a setState.
React version: 18.2.0
Steps To Reproduce
- Call
useOptimisticwith astatevariable - Update the value of
stateeither viasetStateor any other method that would cause a rerender - The initial
stateis returned as theoptimisticState
Link to code example: https://github.com/dorshinar/next-optimistic-bug
I've recreated the bug in a Next project, but the important component is this:
export function Form() {
const [arrFromServer, setArrFromServer] = useState([1, 2, 3]);
useEffect(() => {
const interval = setInterval(() => {
setArrFromServer((a) => [...a, a.length + 1]);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
const [optimisticArr] = useOptimistic(arrFromServer, (s) => s);
console.log("🚀 ~ arrFromServer:", arrFromServer);
console.log("🚀 ~ optimisticArr:", optimisticArr);
return <div>{JSON.stringify(optimisticArr)}</div>;
}
The current behavior
When there is no form submission in progress, the optimisticArr always returns the initial state provided to it.
The expected behavior
I'd exepct that optimisticState returned from useOptimistic to be in sync with the state param passed to it.
I'm experiencing the same issue.
Me too. Downgrading to next 13 solves it for now
I'm experiencing the same issue.
same issue here.
same here, for me it sometimes flashes the correct value before changing back to the original value
Same here. It's not more experimental version, why is this happening?
same here, for me it sometimes flashes the correct value before changing back to the original value
For me it does every time
https://github.com/facebook/react/assets/62719173/25ea9058-27fa-4367-bde8-3686f7d89c26
` 'use client'
import { addTodo } from "@/actions/todo"; import { Todo } from "@prisma/client"; import { ElementRef, useOptimistic, useRef } from "react"; import { v4 as uuid } from 'uuid' import AddButton from "./AddButton";
type Props = { todos: Todo[] }
const Form = ({ todos }: Props) => {
const formRef = useRef<ElementRef<'form'>>(null)
const [optimisticTodos, addOptimisticTodo] = useOptimistic<Todo[], string>(
todos,
(currTodos, newTodo: string) => {
return [
{
text: newTodo,
id: uuid(),
createdAt: new Date(Date.now()),
updatedAt: new Date(Date.now())
},
...currTodos
]
}
)
return(
<>
<form
ref={formRef}
action={async formData => {
formRef.current?.reset();
addOptimisticTodo(formData.get("text") as string)
await addTodo(formData)
}}
className="flex-row flex"
>
<label>Input text</label>
<input
type="text"
name="text"
className="bg-blue-950"
/>
<AddButton />
</form>
<ul className="mt-5">
{
optimisticTodos.map(todo => (
<li key={todo.id} className="mb-3">
<p>{todo.text}</p>
</li>
))
}
</ul>
</>
);
}
export default Form `
When there is no form submission in progress, the optimisticArr always returns the initial state provided to it.
I think this is an expected behaviour of useOptimistic. Or rather, the optimistic update is replaced with the new state as soon as the transition is over. When there is no async work in the transition, the optimistic update is replaced instantly.
Edit: In fact it says exactly that in the first line of the docs (emphasis my own):
useOptimistic is a React Hook that lets you show a different state while an async action is underway. It accepts some state as an argument and returns a copy of that state that can be different during the duration of an async action such as a network request.
It's expected for useOptimistic to essentially do nothing outside of async transitions.
This is, indeed, a bug — I apologize for not looking into this until now (thanks @tom-sherman for the ping on ~Twitter~ X). I remember getting the ping from @sophiebits but forgot to follow up.
Should be fixed by https://github.com/facebook/react/pull/27936
The fix has now landed in the most recent React and Next.js canaries. Thanks for the bug report!