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

[React 19] useOptimistic is not updating its optimistic collection immediately #31020

Open
jsoneaday opened this issue Sep 22, 2024 · 2 comments

Comments

@jsoneaday
Copy link

jsoneaday commented Sep 22, 2024

I have this component that I created where I'm testing useOptimistic and don't have a real backend to call to. So my data source is a variable in another module called messagesOnApi. I have a local state variable messagesRetrievedFromApi that I use to load the data from my fake api, messagesOnApi, and then when I call useOptimistic I pass the messagesRetrievedFromApi state variable as the first parameter.
When I make my update to the api data I add a delay but instead of my optimistic data updating immediately it only updates after the delay. Why?
This is the github with entire source, https://github.com/PacktPublishing/Full-Stack-React-TypeScript-and-Node-2nd-Edition/blob/main/Chap5/components/src/OptimisticMessages.tsx. This is the main component.

import {
  useOptimistic,
  useState,
  MouseEvent,
  ChangeEvent,
  useTransition,
  useEffect,
} from "react";
import { Message, messagesOnApi } from "./MessagesData";

export function OptimisticMessages() {
  const [messagesRetrievedFromApi, setMessagesRetrievedFromApi] = useState<
    Message[]
  >([]);
  const [message, setMessage] = useState("");
  const [optimisticMessages, addOptimisticMessage] = useOptimistic<
    Message[],
    string
  >(messagesRetrievedFromApi, (currentMessages, message) => {
    const finalMessages = [
      ...currentMessages,
      { id: getNextId(currentMessages), message },
    ];
    return finalMessages;
  });
  const [_isPending, startTransition] = useTransition();

  useEffect(() => {
    const data = JSON.parse(JSON.stringify(messagesOnApi));
    setMessagesRetrievedFromApi(data);
  }, []);

  const addMessage = async (message: string) => {
    addOptimisticMessage(message);
    await new Promise((res) =>
      setTimeout(() => {
        messagesOnApi.push({
          id: getNextId(messagesOnApi),
          message,
        });
        res(null);
      }, 2000)
    );

    const data = JSON.parse(JSON.stringify(messagesOnApi));
    setMessagesRetrievedFromApi(data);
    setMessage("");
  };

  const onChange = (e: ChangeEvent<HTMLInputElement>) => {
    e.preventDefault();
    setMessage(e.target.value);
  };

  const onClick = async (e: MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();

    startTransition(() => {
      addMessage(message);
    });
  };

  return (
    <div>
      <input value={message} onChange={onChange} />
      <button onClick={onClick}>add</button>
      <ul style={{ listStyleType: "none" }}>
        {optimisticMessages.map((m, i) => (
          <li key={i}>{m.message}</li>
        ))}
      </ul>
    </div>
  );
}

function getNextId(currentMessages: Message[]) {
  let id = 0;
  for (let i = 0; i < currentMessages.length; i++) {
    if (currentMessages[i].id > id) id = currentMessages[i].id;
  }
  id += 1;
  return id;
}
@eps1lon
Copy link
Collaborator

eps1lon commented Sep 25, 2024

Can you distill this into a minimal repro with as little files as possible? You can use https://react.new for that.

@jsoneaday
Copy link
Author

Can you distill this into a minimal repro with as little files as possible? You can use https://react.new for that.

Here's a new project with just the affected component OptimisticMessages, https://github.com/jsoneaday/optimistic. Note I am using useTransition and not forms in this example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants