1. Introduction: What is TanStack Query?
TanStack Query (formerly React Query) is a framework-agnostic state management library designed specifically to manage Server State—handling data fetching, caching, background updating, and cache invalidation.
2. Server State vs. Client State & The Memory Reality
-
Client State: Owned and controlled entirely by the browser (e.g.,
isModalOpen, selected UI theme). - Server State: Owned by the remote backend database (e.g., user profiles, posts, cart items). The browser only holds a read-only temporary snapshot.
Where is data physically stored?
- Physical Location: By default, cached data lives strictly in the browser tab's JavaScript RAM (In-Memory).
- Backend ("The Server"): Refers to your remote API/database (regardless of whether it runs on Kubernetes, Docker containers, serverless functions, or bare metal).
-
Optional Persistence: You can opt to sync this RAM cache to
localStorage,sessionStorage, orIndexedDBusing TanStack Query Persisters.
import React, { useState } from 'react';
import {
QueryClient,
QueryClientProvider,
useQuery,
useMutation,
useQueryClient,
} from '@tanstack/react-query';
// 1. Initialize QueryClient (manages the RAM cache)
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 10000, // Data stays fresh in RAM for 10 seconds
},
},
});
// Mock API functions
async function fetchPost(postId) {
const res = await fetch(`[https://jsonplaceholder.typicode.com/posts/$](https://jsonplaceholder.typicode.com/posts/$){postId}`);
if (!res.ok) throw new Error('Network error');
return res.json();
}
async function createPost(newPost) {
const res = await fetch('[https://jsonplaceholder.typicode.com/posts](https://jsonplaceholder.typicode.com/posts)', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
});
return res.json();
}
// 2. Query Component (Fetching & Reading Data)
function PostViewer() {
const [postId, setPostId] = useState(1);
const { data, isLoading, isFetching, error } = useQuery({
queryKey: ['post', postId], // Cache key
queryFn: () => fetchPost(postId),
});
return (
<div>
<div>
<button onClick={() => setPostId(1)}>Load Post 1</button>
<button onClick={() => setPostId(2)}>Load Post 2</button>
</div>
{isFetching && <p style={{ color: 'orange' }}>Updating cache in background...</p>}
{isLoading ? (
<p>Loading initial data...</p>
) : error ? (
<p style={{ color: 'red' }}>Error: {error.message}</p>
) : (
<div>
<h3>Post #{data.id}: {data.title}</h3>
<p>{data.body}</p>
</div>
)}
</div>
);
}
// 3. Mutation Component (Updating Data & Invalidating Cache)
function CreatePostForm() {
const [title, setTitle] = useState('');
const client = useQueryClient();
const mutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
// Mark cached post as stale to trigger background refetch
client.invalidateQueries({ queryKey: ['post', 1] });
setTitle('');
alert('Post created! Cache invalidated.');
},
});
const handleSubmit = (e) => {
e.preventDefault();
mutation.mutate({ title, body: 'Sample content', userId: 1 });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Enter post title..."
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Saving...' : 'Add Post'}
</button>
</form>
);
}
// 4. Main Application Wrapper
export default function App() {
return (
<QueryClientProvider client="{queryClient}">
<PostViewer/>
<hr />
<CreatePostForm/>
</QueryClientProvider>
);
}```
Enter fullscreen mode Exit fullscreen mode
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.