Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Stale values returned from useOptimistic when state changes #27617

Closed
dorshinar opened this issue Oct 28, 2023 · 11 comments
Closed

Bug: Stale values returned from useOptimistic when state changes #27617

dorshinar opened this issue Oct 28, 2023 · 11 comments
Assignees
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug

Comments

@dorshinar
Copy link

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

  1. Call useOptimistic with a state variable
  2. Update the value of state either via setState or any other method that would cause a rerender
  3. The initial state is returned as the optimisticState
    image

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.

@magoz
Copy link

magoz commented Nov 23, 2023

I'm experiencing the same issue.

@Feel-ix-343
Copy link

Me too. Downgrading to next 13 solves it for now

@katerynarieznik
Copy link

I'm experiencing the same issue.

@ipv6sq
Copy link

ipv6sq commented Dec 9, 2023

same issue here.

@Elee-Lawleit
Copy link

same here, for me it sometimes flashes the correct value before changing back to the original value

@JovanJevtic
Copy link

Same here. It's not more experimental version, why is this happening?

@JovanJevtic
Copy link

same here, for me it sometimes flashes the correct value before changing back to the original value

For me it does every time

@JovanJevtic
Copy link

2024-01-11.00-42-19.mp4

`
'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
`

@tom-sherman
Copy link

tom-sherman commented Jan 13, 2024

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.

@acdlite
Copy link
Collaborator

acdlite commented Jan 14, 2024

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 #27936

@acdlite
Copy link
Collaborator

acdlite commented Jan 14, 2024

The fix has now landed in the most recent React and Next.js canaries. Thanks for the bug report!

@acdlite acdlite closed this as completed Jan 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug
Projects
None yet
Development

No branches or pull requests

9 participants