React Logoreact-query

React Query (now officially TanStack Query) is a powerful data-fetching, caching, and state management library for React applications. It simplifies the process of fetching, caching, synchronizing, and updating server-state data, making it a robust alternative to global state managers like Redux for server-side data.\n\nKey Features and Benefits:\n* Declarative Data Fetching: Easily fetch data with the `useQuery` hook, defining a unique key and a fetcher function.\n* Automatic Caching: Automatically caches fetched data, reducing redundant network requests and improving performance.\n* Background Refetching: Intelligently refetches stale data in the background, ensuring your UI always displays up-to-date information without blocking user interaction.\n* Query Invalidation: Provides powerful mechanisms to invalidate queries, forcing a refetch of specific data when mutations occur (e.g., after adding, updating, or deleting an item).\n* Optimistic Updates: Supports optimistic updates, allowing you to update the UI immediately after a mutation, assuming the mutation will succeed, and then revert if it fails, enhancing perceived performance.\n* Retry Mechanism: Built-in automatic retries for failed queries, with configurable retry logic.\n* Pagination & Infinite Scrolling: Dedicated hooks and utilities for implementing complex pagination and infinite scroll UIs with ease.\n* Prefetching: Allows you to prefetch data proactively, anticipating user actions and making navigation feel instant.\n* Devtools: Comes with a comprehensive Devtools panel for inspecting query states, caches, and mutations, greatly aiding debugging.\n* Reduces Boilerplate: Significantly reduces the amount of boilerplate code typically associated with manual data fetching, loading states, error handling, and caching.\n\nHow it Works:\nAt its core, React Query uses a `QueryClient` to manage all its operations.\n* `QueryClientProvider`: Wraps your application (or a part of it) to provide the `QueryClient` instance to all components.\n* `useQuery` Hook: Used for fetching data (typically GET requests). It takes a unique "query key" (an array) and an asynchronous "query function" that fetches the data. It returns an object containing `data`, `isLoading`, `isError`, `error`, `isFetching`, etc.\n* `useMutation` Hook: Used for performing data mutations (POST, PUT, DELETE requests). It takes a "mutation function" and provides methods like `mutate` or `mutateAsync` to trigger the mutation. It also allows you to define `onSuccess`, `onError`, `onSettled` callbacks for cache invalidation and optimistic updates.\n* Query Keys: Crucial for identifying and managing cached data. React Query uses these keys to store, retrieve, refetch, and invalidate data.\n\nBy abstracting away the complexities of data synchronization, caching, and state management, React Query allows developers to focus on building user interfaces rather than managing server state.

Example Code

import React from 'react';\nimport {\n  QueryClient,\n  QueryClientProvider,\n  useQuery,\n  useMutation,\n  useQueryClient\n} from '@tanstack/react-query'; // Using @tanstack/react-query as it's the latest official name\n\n// Initialize a new QueryClient\nconst queryClient = new QueryClient();\n\n// --- 1. Main App Component (Providing the QueryClient) ---\nfunction App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <div style={{ fontFamily: 'Arial, sans-serif', padding: '20px' }}>\n        <h1>React Query Example</h1>\n        <AddPostForm />\n        <hr />\n        <PostsList />\n      </div>\n    </QueryClientProvider>\n  );\n}\n\n// --- 2. Component to Display Posts (useQuery) ---\nfunction PostsList() {\n  // Use the useQuery hook to fetch posts\n  const { data: posts, isLoading, isError, error } = useQuery({\n    queryKey: ['posts'], // Unique key for this query\n    queryFn: async () => { // Function to fetch the data\n      const res = await fetch('https://jsonplaceholder.typicode.com/posts');\n      if (!res.ok) {\n        throw new Error('Network response was not ok');\n      }\n      return res.json();\n    }\n  });\n\n  if (isLoading) return <p>Loading posts...</p>;\n  if (isError) return <p>Error: {error.message}</p>;\n\n  return (\n    <div>\n      <h2>Posts</h2>\n      {posts.map(post => (\n        <div key={post.id} style={{ border: '1px solid #ccc', padding: '10px', margin: '10px 0' }}>\n          <h3>{post.title}</h3>\n          <p>{post.body}</p>\n        </div>\n      ))}\n    </div>\n  );\n}\n\n// --- 3. Component to Add a New Post (useMutation) ---\nfunction AddPostForm() {\n  const queryClient = useQueryClient(); // Get the query client instance\n  const [title, setTitle] = React.useState('');\n  const [body, setBody] = React.useState('');\n\n  // Use the useMutation hook for adding a new post\n  const addPostMutation = useMutation({\n    mutationFn: async (newPost) => { // Function to perform the mutation\n      const res = await fetch('https://jsonplaceholder.typicode.com/posts', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify(newPost),\n      });\n      if (!res.ok) {\n        throw new Error('Network response was not ok');\n      }\n      return res.json();\n    },\n    onSuccess: () => {\n      // Invalidate the 'posts' query to refetch the list and show the new post\n      queryClient.invalidateQueries({ queryKey: ['posts'] });\n      setTitle(''); // Clear form fields\n      setBody('');\n      alert('Post added successfully!');\n    },\n    onError: (error) => {\n      alert(`Error adding post: ${error.message}`);\n    },\n  });\n\n  const handleSubmit = (e) => {\n    e.preventDefault();\n    if (!title || !body) {\n      alert('Please enter both title and body.');\n      return;\n    }\n    addPostMutation.mutate({ title, body, userId: 1 }); // Trigger the mutation\n  };\n\n  return (\n    <div>\n      <h2>Add New Post</h2>\n      <form onSubmit={handleSubmit}>\n        <div style={{ marginBottom: '10px' }}>\n          <label htmlFor="title">Title:</label>\n          <input\n            id="title"\n            type="text"\n            value={title}\n            onChange={(e) => setTitle(e.target.value)}\n            disabled={addPostMutation.isLoading}\n            style={{ marginLeft: '5px', padding: '5px' }}\n          />\n        </div>\n        <div style={{ marginBottom: '10px' }}>\n          <label htmlFor="body">Body:</label>\n          <textarea\n            id="body"\n            value={body}\n            onChange={(e) => setBody(e.target.value)}\n            disabled={addPostMutation.isLoading}\n            style={{ marginLeft: '5px', padding: '5px', verticalAlign: 'top' }}\n          />\n        </div>\n        <button type="submit" disabled={addPostMutation.isLoading}>\n          {addPostMutation.isLoading ? 'Adding...' : 'Add Post'}\n        </button>\n        {addPostMutation.isError && <p style={{ color: 'red' }}>Error: {addPostMutation.error.message}</p>}\n      </form>\n    </div>\n  );\n}\n\nexport default App;