I'm Samuel FajreldinesI am a specialist in the entire JavaScript and TypeScript ecosystem (including Node.js, React, Angular and Vue.js) I am expert in AI and in creating AI integrated solutions I am expert in DevOps and Serverless Architecture (AWS, Google Cloud and Azure) I am expert in PHP and its frameworks (such as Codeigniter and Laravel). |
Samuel FajreldinesI am a specialist in the entire JavaScript and TypeScript ecosystem. I am expert in AI and in creating AI integrated solutions. I am expert in DevOps and Serverless Architecture I am expert in PHP and its frameworks.
|
Introduction
In the modern web development landscape, efficient data fetching and state management are crucial for building responsive and user-friendly applications. React Query, often described as "the missing data-fetching library for React," simplifies these aspects by providing powerful hooks for fetching, caching, and updating asynchronous data in React applications.
In this comprehensive guide, we'll explore why React Query has become an indispensable tool for developers, how to integrate it into your projects, and walk through a complete use case to solidify your understanding.
What is React Query?
React Query is a library for fetching, caching, and updating data in React applications without the need for global state management solutions like Redux. It leverages React's hooks system to provide an intuitive and declarative API for handling server state.
By abstracting away the complexities of data fetching, synchronization, and caching, React Query allows developers to focus on building features rather than boilerplate code. It handles under-the-hood tasks such as caching, background updates, and stale data management, providing a smoother and more efficient user experience.
Why Use React Query?
Improved Data Fetching
Fetching data from APIs is a common requirement in web applications. Traditionally, developers handled this using useEffect
, managing loading states, errors, and updates manually. React Query automates these aspects, reducing code complexity and potential bugs.
Automatic Caching and Updating
React Query caches fetched data and intelligently updates it when necessary. This means that if multiple components request the same data, React Query serves it from the cache, reducing unnecessary network requests. It also keeps the data fresh by refetching it in the background when it becomes stale.
Simplifies State Management
By handling server state separately from client state, React Query eliminates the need for complex state management libraries for many applications. It provides a straightforward API for managing asynchronous data, which can significantly simplify your codebase.
Better User Experience
With features like background refetching, pagination, and optimistic updates, React Query enhances the user experience by making applications faster and more responsive.
Getting Started with React Query
Installation
To start using React Query in your project, you need to install it along with its required peer dependency:
npm install @tanstack/react-query
Basic Usage
Before using React Query hooks, you need to set up a QueryClient
and wrap your application with a QueryClientProvider
:
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* ... your app components */}
</QueryClientProvider>
);
}
export default App;
Understanding Queries and Mutations
React Query provides two primary hooks: useQuery
for fetching data (queries) and useMutation
for updating data (mutations).
useQuery Hook
The useQuery
hook is used to fetch data asynchronously:
import { useQuery } from '@tanstack/react-query';
function Todos() {
const { isLoading, error, data } = useQuery(['todos'], fetchTodos);
if (isLoading) return 'Loading...';
if (error) return 'An error occurred';
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
useMutation Hook
The useMutation
hook is used for creating, updating, or deleting data:
import { useMutation, useQueryClient } from '@tanstack/react-query';
function AddTodo() {
const queryClient = useQueryClient();
const mutation = useMutation(addTodo, {
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries(['todos']);
},
});
return (
<button
onClick={() => {
mutation.mutate({ title: 'New Todo' });
}}
>
Add Todo
</button>
);
}
Setting Up a Complete Use Case
Let's build a simple application to demonstrate how to use React Query in a real-world scenario. We'll create a basic CRUD (Create, Read, Update, Delete) app for managing a list of users.
Overview of the Application
Our application will have the following features:
Setting Up the Project
First, create a new React project using Create React App:
npx create-react-app react-query-demo
cd react-query-demo
npm install @tanstack/react-query axios
We'll use Axios for making HTTP requests.
Creating the API
For simplicity, we'll use the JSONPlaceholder API, which provides fake online REST APIs for testing.
Implementing React Query in the Application
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
// App.js
import React from 'react';
import axios from 'axios';
import { useQuery } from '@tanstack/react-query';
function App() {
const { isLoading, error, data } = useQuery(['users'], () =>
axios.get('https://jsonplaceholder.typicode.com/users').then(res => res.data)
);
if (isLoading) return <p>Loading users...</p>;
if (error) return <p>An error occurred: {error.message}</p>;
return (
<div>
<h1>User List</h1>
<ul>
{data.map(user => (
<li key={user.id}>
{user.name}
{/* Update and Delete buttons will go here */}
</li>
))}
</ul>
{/* Components for adding a user will go here */}
</div>
);
}
export default App;
Since JSONPlaceholder doesn't actually change data, we'll simulate adding a user.
// AddUser.js
import React, { useState } from 'react';
import axios from 'axios';
import { useMutation, useQueryClient } from '@tanstack/react-query';
function AddUser() {
const queryClient = useQueryClient();
const [name, setName] = useState('');
const addUserMutation = useMutation(
newUser => axios.post('https://jsonplaceholder.typicode.com/users', newUser).then(res => res.data),
{
onSuccess: data => {
// Update the users list
queryClient.setQueryData(['users'], oldData => [...oldData, data]);
},
}
);
const handleSubmit = e => {
e.preventDefault();
addUserMutation.mutate({ name });
setName('');
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} placeholder="Name" required />
<button type="submit">Add User</button>
</form>
);
}
export default AddUser;
Include <AddUser />
in your App
component.
// App.js
// ...
import AddUser from './AddUser';
// ...
function App() {
// existing code
return (
<div>
{/* existing code */}
<AddUser />
</div>
);
}
// ...
// UpdateUser.js
import React, { useState } from 'react';
import axios from 'axios';
import { useMutation, useQueryClient } from '@tanstack/react-query';
function UpdateUser({ user }) {
const queryClient = useQueryClient();
const [name, setName] = useState(user.name);
const updateUserMutation = useMutation(
updatedUser => axios.put(`https://jsonplaceholder.typicode.com/users/${user.id}`, updatedUser).then(res => res.data),
{
onSuccess: data => {
queryClient.setQueryData(['users'], oldData =>
oldData.map(u => (u.id === user.id ? data : u))
);
},
}
);
const handleSubmit = e => {
e.preventDefault();
updateUserMutation.mutate({ name });
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} required />
<button type="submit">Update</button>
</form>
);
}
export default UpdateUser;
Include <UpdateUser user={user} />
in your user list rendering.
// App.js
// ...
<li key={user.id}>
{user.name}
<UpdateUser user={user} />
{/* Delete button */}
</li>
// ...
// DeleteUser.js
import React from 'react';
import axios from 'axios';
import { useMutation, useQueryClient } from '@tanstack/react-query';
function DeleteUser({ userId }) {
const queryClient = useQueryClient();
const deleteUserMutation = useMutation(
() => axios.delete(`https://jsonplaceholder.typicode.com/users/${userId}`),
{
onSuccess: () => {
queryClient.setQueryData(['users'], oldData =>
oldData.filter(user => user.id !== userId)
);
},
}
);
return <button onClick={() => deleteUserMutation.mutate()}>Delete</button>;
}
export default DeleteUser;
Include <DeleteUser userId={user.id} />
in your user list rendering.
// App.js
// ...
<li key={user.id}>
{user.name}
<UpdateUser user={user} />
<DeleteUser userId={user.id} />
</li>
// ...
Advanced Features
Handling Query States
React Query provides various states to manage loading, error, and success scenarios effectively.
const {
isLoading,
isError,
data,
error,
isFetching,
refetch,
} = useQuery(['todos'], fetchTodos);
isLoading
: Indicates if the query is currently loading.isError
: Becomes true if the query encounters an error.data
: The data fetched by the query.error
: The error object if the query fails.isFetching
: Remains true during background refetches.refetch
: Function to manually refetch data.Caching Strategies
Adjusting cache settings can optimize performance:
useQuery(['users'], fetchUsers, {
cacheTime: 1000 * 60 * 10, // Cache data for 10 minutes
staleTime: 1000 * 60 * 5, // Data is fresh for 5 minutes
});
Polling or Interval Fetching
Keep data updated at regular intervals using refetchInterval
:
useQuery(['notifications'], fetchNotifications, {
refetchInterval: 1000 * 60, // Refetch every 60 seconds
});
Retrying Failed Queries
Automatically retry failed queries:
useQuery(['users'], fetchUsers, {
retry: 3, // Retry up to 3 times
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
});
React Query Devtools
For debugging and inspecting queries:
npm install @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
function App() {
return (
// ...
<ReactQueryDevtools initialIsOpen={false} />
);
}
Server-Side Rendering
React Query supports SSR with Next.js:
// pages/_app.js
// Implement getServerSideProps or getStaticProps
Custom Query Functions
Standardize API calls with custom fetchers:
const fetcher = url => axios.get(url).then(res => res.data);
useQuery(['users'], () => fetcher('/api/users'));
Transforming Data
Use the select
option to modify data:
useQuery(['users'], fetchUsers, {
select: data => data.map(user => user.name),
});
Best Practices
onError
and onSuccess
callbacks.staleTime
and cacheTime
appropriately.Comparison with Other Libraries
Compared to Redux or MobX, React Query focuses on server state. For applications heavily reliant on server data, React Query can simplify the codebase by handling data fetching and caching effectively.
Alternatives to React Query
Conclusion
React Query simplifies data fetching and state management in React applications, leading to cleaner code and a better user experience. By handling caching, background updates, and synchronization, it allows developers to focus on building features rather than the intricacies of asynchronous data handling.
In this guide, we've explored the reasons to use React Query, how to integrate it into your application, and walked through a complete use case. Whether you're building a small app or a large-scale application, React Query can significantly enhance your development workflow.
Further Reading
About Me
Since I was a child, I've always wanted to be an inventor. As I grew up, I specialized in information systems, an area which I fell in love with and live around it. I am a full-stack developer and work a lot with devops, i.e., I'm a kind of "jack-of-all-trades" in IT. Wherever there is something cool or new, you'll find me exploring and learning... I am passionate about life, family, and sports. I believe that true happiness can only be achieved by balancing these pillars. I am always looking for new challenges and learning opportunities, and would love to connect with other technology professionals to explore possibilities for collaboration. If you are looking for a dedicated and committed full-stack developer with a passion for excellence, please feel free to contact me. It would be a pleasure to talk with you! |
SecurityScoreCard
Nov. 2023 - Present
New York, United States
Senior Software Engineer
I joined SecurityScorecard, a leading organization with over 400 employees, as a Senior Full Stack Software Engineer. My role spans across developing new systems, maintaining and refactoring legacy solutions, and ensuring they meet the company's high standards of performance, scalability, and reliability.
I work across the entire stack, contributing to both frontend and backend development while also collaborating directly on infrastructure-related tasks, leveraging cloud computing technologies to optimize and scale our systems. This broad scope of responsibilities allows me to ensure seamless integration between user-facing applications and underlying systems architecture.
Additionally, I collaborate closely with diverse teams across the organization, aligning technical implementation with strategic business objectives. Through my work, I aim to deliver innovative and robust solutions that enhance SecurityScorecard's offerings and support its mission to provide world-class cybersecurity insights.
Technologies Used:
Node.js Terraform React Typescript AWS Playwright and Cypress