# Optimistic Updates

**SHOULD** · **ID:** `interactions-optimistic-updates` · **Category:** interactions
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-optimistic-updates

> SHOULD: Optimistic UI; reconcile on response; on failure show error and rollback or offer Undo

Update UI immediately when success is likely; reconcile on response

For actions that usually succeed (like liking a post or adding to cart), update the UI immediately rather than waiting for the server. This makes the interface feel instant. If the request fails, show an error and revert the change or offer an undo option.

## Bad — do not do this

`interactions-optimistic-updates-bad`

```tsx
import { useState } from 'react';
import { Heart } from 'lucide-react';

export function OptimisticUpdatesBad() {
  const [liked, setLiked] = useState(false);
  const [loading, setLoading] = useState(false);

  const handleLike = async () => {
    setLoading(true);
    await new Promise(resolve => setTimeout(resolve, 1000));
    setLiked(!liked);
    setLoading(false);
  };

  return (
    <div className="w-full max-w-sm">
      <div className="p-4 bg-muted rounded-lg">
        <p className="text-sm mb-3">Great post about web design!</p>
        <button
          onClick={handleLike}
          disabled={loading}
          className="flex items-center gap-2 px-3 py-2 bg-card rounded-lg hover:bg-muted transition-colors disabled:opacity-50"
        >
          <Heart className={`w-4 h-4 ${liked ? 'fill-red-500 text-red-500' : 'text-muted-foreground'}`} />
          <span className="text-sm">{loading ? 'Loading...' : liked ? 'Liked' : 'Like'}</span>
        </button>
      </div>
      <p className="text-xs text-error mt-4">
        User waits 1s for server response before seeing feedback
      </p>
    </div>
  );
}
```

## Good — do this

`interactions-optimistic-updates-good`

```tsx
import { useState } from 'react';
import { Heart } from 'lucide-react';

export function OptimisticUpdatesGood() {
  const [liked, setLiked] = useState(false);

  const handleLike = async () => {
    const previousState = liked;
    setLiked(!liked);

    try {
      await new Promise((resolve, reject) =>
        setTimeout(() => Math.random() > 0.2 ? resolve(true) : reject(), 1000)
      );
    } catch {
      setLiked(previousState);
      alert('Failed to update. Please try again.');
    }
  };

  return (
    <div className="w-full max-w-sm">
      <div className="p-4 bg-muted rounded-lg">
        <p className="text-sm mb-3">Great post about web design!</p>
        <button
          onClick={handleLike}
          className="flex items-center gap-2 px-3 py-2 bg-card rounded-lg hover:bg-muted transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
        >
          <Heart className={`w-4 h-4 transition-colors ${liked ? 'fill-red-500 text-red-500' : 'text-muted-foreground'}`} />
          <span className="text-sm">{liked ? 'Liked' : 'Like'}</span>
        </button>
      </div>
      <p className="text-xs text-success mt-4">
        UI updates instantly, rolls back on error
      </p>
    </div>
  );
}
```

## References

- [Optimistic UI](https://www.apollographql.com/docs/react/performance/optimistic-ui)
