79594232

Date: 2025-04-26 17:51:10
Score: 1
Natty:
Report link

Why does this happen 💡

The React doesn't have a way to know that you have updated some data via fetch. You have to let it know something changed.

I'm not exactly sure what you useAsync hook does, but I suppose it just fetches the data from a backend, while watching out for the value changes in the dependency array ([id] here).

How to solve this 🛠️

First of all, I just want to make clear that this is my subjective recommendation, not anything objectively best.

You've got a good idea about handling this manually via state with comments. It's going to work perfectly fine, you just need to add the state, useEffect for watching changes in book object, and handle the comment adding via passed setComments hook to the CommentForm component.

useOptimistic hook 🪝

You can do some more optimiziation in the solution described above, but what I'd really like to mention is React's new useOptimistic hook. It's meant exactly for use cases like this. Basically what it's meant to do is optimistically update UI with the new data before the actual fetch to the backend completes, providing good UX. And if it fails, it's seamless to rollback.

In your scenario, you would add the useOptimistic hook alongside the comments useState hook:

export function BookItem() {
  const [comments, setComments] = useState<{
    userId: string,
    text: string
  }[]>([]);

  const [optimisticComments, addOptimisticComment] = useOptimistic(
    comments,
    (state, newComment) => [
      ...state,
      {
        ...newComment,
        sending: true
      }
    ]
  );

  const { bookId } = useParams<{ bookId: string }>();
  const id = parseInt(bookId, 10);

  const { data: book, loading } = useAsync(
    () => bookService.loadBookWithComments(id),
    [id]
  );
  
  useEffect(() => {
    setComments(book.comments);
  }, [book.comments]);


  if (loading) return <p>Loading...</p>;
  if (!book) return <p>Book not found</p>;

  return (
    <div>
      <h1>{book.name}</h1>
      <ul>
        {optimisticComments.map((c, index) => (
          <li key={index}>
            <strong>{c.userId}</strong>: {c.text}
            // optionally some loading animation if "c.sending"
          </li>
        ))}
      </ul>

      <CommentForm
        bookId={book.id}
        addOptimisticComment={addOptimisticComment}
        setComments={setComments}
      />
    </div>
  );

and in the CommentForm:

export function CommentForm({ bookId, addOptimisticComment, setComments }: { 
  bookId: number,
  // rest of the types here
  }) {
  const [text, setText] = useState("");
  const { trigger } = useAsyncAction(async () => {
    const newComment = { userId: "Me", text };
    addOptimisticComment(newComment);
    await bookService.createNewBookComment(bookId, text);
    setComments(comments => [...comments, newComment])
  });

  // ... rest of the code
  );

🗒️ And just a quick note, a little downside of this solution is not being to able to use comment ID as an index in .map method. This is obviously because you don't have an ID before your backend responds with a generated one. So keep this in mind.

If you have any questions regarding the usage, of course feel free to ask.

Reasons:
  • Blacklisted phrase (1): How to solve
  • Long answer (-1):
  • Has code block (-0.5):
  • Starts with a question (0.5): Why do
  • Low reputation (1):
Posted by: Trawen