import { useOptimistic } from 'react';
function CommentSection({ initialComments }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
initialComments,
(state, newComment) => [...state, { text: newComment, sending: true }]
);
async function handleAction(formData: FormData) {
const comment = formData.get("comment") as string;
// 1. Update UI instant
addOptimisticComment(comment);
// 2. Trimite la server
try {
await saveCommentToDB(comment);
} catch (e) {
// React va face rollback automat la initialComments aici
console.error("Eroare la salvare:", e);
}
}
return (
<form action={handleAction}>
{optimisticComments.map((c, i) => (
<p key={i} style={{ opacity: c.sending ? 0.5 : 1 }}>{c.text}</p>
))}
<input name="comment" placeholder="Scrie ceva..." />
<button type="submit">Trimite</button>
</form>
);
}Să vorbim pe șleau despre experiența utilizatorului
Am lucrat acum vreo două luni la un dashboard pentru un client din zona de e-commerce care avea o problemă mare: serverul de API era situat undeva prin SUA, iar utilizatorii din Europa simțeau fiecare milisecundă de latență. Când cineva lăsa un comentariu la un produs, dura cam 2 secunde până apărea pe ecran. Senzația aia de „interfață blocată” e cel mai mare inamic al retenției.
Până la React 19, trebuia să facem tot felul de jonglerii cu useState, să adăugăm manual elementul în listă, să avem un flag isPending și, cel mai enervant, să facem rollback manual dacă API-ul crăpa. Era mult boilerplate pentru o chestie care ar trebui să fie implicită în 2024. Aici intervine useOptimistic.
Ce este, de fapt, useOptimistic?
Nu e vreun algoritm magic, ci un hook care îți permite să afișezi o stare „temporară” cât timp o acțiune asincronă este în desfășurare. Ideea e simplă: utilizatorul dă click, noi îi arătăm rezultatul instantaneu (ca și cum serverul ar fi răspuns deja), iar în spate React așteaptă confirmarea reală. Dacă serverul zice „OK”, starea temporară e înlocuită de cea reală. Dacă serverul dă eroare, React face rollback automat la starea anterioară. Fără useEffect, fără bătăi de cap.
Implementarea într-un form de comentarii
Hai să luăm exemplul clasic. Avem o listă de comentarii și un formular. În loc să așteptăm ca fetch-ul să se termine, actualizăm UI-ul imediat.
Pe proiectul de care vă ziceam, trecerea la acest pattern a redus codul responsabil de UI state cu vreo 40 de linii per componentă. Secretul stă în modul în care useOptimistic colaborează cu noile Server Actions (sau orice funcție asincronă apelată într-un formAction).
De ce e mai bun decât ce aveam înainte?
Înainte, dacă trimiteai un comentariu și netul pica la jumătate, rămâneai cu un element „fantomă” în listă dacă nu erai atent să scrii logica de catch perfectă. Cu useOptimistic, React se ocupă de sincronizare. Tu îi dai o funcție de „update” (un fel de reducer mic) care decide cum arată starea ta intermediară.
Un aspect important pe care mulți îl omit: ordinea operațiilor. Trebuie să apelezi addOptimisticComment înainte de a face await pe apelul de rețea. Dacă o faci după, ai pierdut tot avantajul de viteză percepută.
Trade-off-uri și realitate
Să nu ne mințim, nu e totul roz. Dacă ai logici de validare complexe pe server care pot schimba drastic datele (de exemplu, serverul reformatează textul sau adaugă metadate esențiale), utilizatorul ar putea vedea un „jump” vizual când starea optimistă e înlocuită de cea finală. Sfatul meu? Folosește-l pentru chestii simple: like-uri, comentarii, adăugare în coș, schimbări de status. Nu-l folosi la procesări de plăți sau chestii unde integritatea datelor afișate trebuie să fie 100% precisă din prima secundă.
Un alt mic detaliu: useOptimistic funcționează cel mai bine în interiorul componentelor care sunt marcate cu 'use client', dar care primesc datele inițiale ca prop-uri de la o componentă de server. Așa ai și SEO, și viteză.