// ✅ REACT QUERY - THE ASYNC STATE SOLUTION
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// 1. User profile with caching and background updates
function UserProfile({ userId }) {
const {
data: user,
isLoading,
error,
refetch,
isStale
} = useQuery({
queryKey: ['user', userId],
queryFn: ({ signal }) => fetchUser(userId, { signal }),
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
});
if (isLoading) return <div>Loading user...</div>;
if (error) return (
<div>
Error: {error.message}
<button onClick={() => refetch()}>Retry</button>
</div>
);
return (
<div>
<h2>{user.name} {isStale && '(updating...)'}</h2>
<p>{user.email}</p>
<EditUserForm user={user} />
</div>
);
}
// 2. Optimistic updates with mutations
function EditUserForm({ user }) {
const queryClient = useQueryClient();
const [formData, setFormData] = useState({
name: user.name,
email: user.email
});
const updateUserMutation = useMutation({
mutationFn: (userData) => updateUser(user.id, userData),
// Optimistic update
onMutate: async (newUserData) => {
// Cancel outgoing refetches
await queryClient.cancelQueries(['user', user.id]);
// Snapshot previous value
const previousUser = queryClient.getQueryData(['user', user.id]);
// Optimistically update
queryClient.setQueryData(['user', user.id], old => ({
...old,
...newUserData
}));
return { previousUser };
},
// On error, rollback
onError: (err, newUserData, context) => {
queryClient.setQueryData(['user', user.id], context.previousUser);
},
// Always refetch after error or success
onSettled: () => {
queryClient.invalidateQueries(['user', user.id]);
},
});
const handleSubmit = (e) => {
e.preventDefault();
updateUserMutation.mutate(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
disabled={updateUserMutation.isLoading}
/>
<input
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
disabled={updateUserMutation.isLoading}
/>
<button type="submit" disabled={updateUserMutation.isLoading}>
{updateUserMutation.isLoading ? 'Saving...' : 'Save'}
</button>
{updateUserMutation.error && (
<div className="error">
Error: {updateUserMutation.error.message}
</div>
)}
</form>
);
}
// 3. Infinite scroll with React Query
function PostList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
error
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 0 }) => fetchPosts({ page: pageParam }),
getNextPageParam: (lastPage, pages) => lastPage.nextPage ?? undefined,
});
const posts = data?.pages.flatMap(page => page.posts) ?? [];
if (isLoading) return <div>Loading posts...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{posts.map(post => (
<PostItem key={post.id} post={post} />
))}
{hasNextPage && (
<button
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
>
{isFetchingNextPage ? 'Loading more...' : 'Load More'}
</button>
)}
</div>
);
}