Pourquoi React Query avec React : guide complet ?
Contexte réel : pourquoi un dev a besoin de ca au quotidien
React Query est une bibliothèque populaire pour faciliter les requêtes réseau dans des applications React. Elle offre une interface simple et efficace pour récupérer, mettre à jour, supprimer et gérer les données asynchrones en utilisant des hooks personnalisés. En tant que développeur senior React avec 10+ ans d'expérience, vous avez probablement déjà affronté des problèmes comme les requêtes réseau imbriquées, la gestion des états de chargement et des erreurs, ainsi que le maintien des données synchronisées entre les composants. React Query simplifie ces tâches en offrant des outils puissants et fiables pour gérer les interactions avec les API.
Un cas d'utilisation concret en 2-3 phrases
Imaginez une application de gestion de notes où vous devez afficher les notes d'un utilisateur spécifique. Avec React Query, vous pouvez facilement récupérer les données de l'API, gérer le chargement et les erreurs, et même mettre à jour les notes en temps réel sans avoir à refaire une requête complète.
Prerequis
- Connaissances préalables en React (Hooks, State Management)
- Node.js v14 ou supérieur
- npm v7 ou supérieur
- Un environnement de développement (VSCode recommandé)
Concepts fondamentaux
1. Utilisation des hooks de base avec React Query
React Query utilise principalement les hooks useQuery et useMutation. Voici comment ils fonctionnent :
// Importer useQuery et useMutation depuis react-query
import { useQuery, useMutation } from 'react-query';
// Exemple de hook useQuery pour récupérer des données
function fetchUsers() {
return fetch('https://api.example.com/users').then(res => res.json());
}
function UsersComponent() {
const { isLoading, error, data: users } = useQuery('users', fetchUsers);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
2. Mutation et mise à jour des données
La mutation est utilisée pour effectuer des opérations CRUD (Create, Read, Update, Delete) sur les données.
// Importer useMutation depuis react-query
import { useQuery, useMutation } from 'react-query';
function fetchUsers() {
return fetch('https://api.example.com/users').then(res => res.json());
}
function addUser(userId, userName) {
return fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: userId, name: userName })
});
}
function UsersComponent() {
const { isLoading, error, data: users } = useQuery('users', fetchUsers);
const [mutate] = useMutation(addUser, {
onSuccess: () => queryClient.invalidateQueries('users')
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
<button onClick={() => mutate(10, 'New User')}>Add New User</button>
</ul>
);
}
3. Gestion des erreurs et retry
React Query offre une gestion intégrée des erreurs et de la reprise automatique des requêtes.
// Importer useQuery depuis react-query
import { useQuery } from 'react-query';
function fetchUsers() {
return fetch('https://api.example.com/users').then(res => res.json());
}
function UsersComponent() {
const { isLoading, error, data: users } = useQuery('users', fetchUsers, {
retry: 3,
retryDelay: (retryCount) => Math.min(1000 * (2 ** retryCount), 60000)
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
4. Gestion des états de chargement et d'erreur
React Query gère automatiquement les états de chargement (isLoading), de succès (isSuccess) et d'erreur (isError). Vous pouvez utiliser ces états pour afficher du contenu différent en fonction de l'état actuel.
// Importer useQuery depuis react-query
import { useQuery } from 'react-query';
function fetchUsers() {
return fetch('https://api.example.com/users').then(res => res.json());
}
function UsersComponent() {
const { isLoading, error, data: users } = useQuery('users', fetchUsers);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Mise en pratique : projet fil rouge
Construisons UN mini-projet complet et réeliste (ex: un gestionnaire de tâches, une API de blog, un scraper, un CLI tool)
Nous allons construire un simple gestionnaire de tâches avec React Query. Le projet comprendra les fonctionnalités suivantes :
- Afficher la liste des tâches
- Ajouter une nouvelle tâche
- Supprimer une tâche
Étape 1 : Initialisation du projet
Créez un nouveau projet React et installez React Query.
npx create-react-app task-manager
cd task-manager
npm install react-query
Étape 2 : Création des composants
Créez deux fichiers : TaskList.js et AddTask.js.
TaskList.js
// Importer useQuery et useEffect depuis react-query
import { useQuery, useEffect } from 'react-query';
import { fetchTasks, deleteTask } from './api';
function TaskList() {
const { isLoading, error, data: tasks, refetch } = useQuery('tasks', fetchTasks);
useEffect(() => {
refetch();
}, [refetch]);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.name}
<button onClick={() => deleteTask(task.id)}>Delete</button>
</li>
))}
</ul>
);
}
export default TaskList;
AddTask.js
// Importer useMutation depuis react-query
import { useMutation } from 'react-query';
import { addTask } from './api';
function AddTask({ onAdd }) {
const [name, setName] = useState('');
const [mutate] = useMutation(addTask, {
onSuccess: (newTask) => {
onAdd(newTask);
setName('');
}
});
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Add a new task"
/>
<button onClick={() => mutate(name)}>Add</button>
</div>
);
}
export default AddTask;
Étape 3 : Création des API
Créez un fichier api.js pour gérer les requêtes vers l'API.
// Importer fetch
import { fetch, deleteRequest } from 'react-query';
function fetchTasks() {
return fetch('https://api.example.com/tasks').then(res => res.json());
}
function addTask(taskName) {
return fetch('https://api.example.com/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: taskName })
}).then(res => res.json());
}
function deleteTask(taskId) {
return deleteRequest(`https://api.example.com/tasks/${taskId}`);
}
export { fetchTasks, addTask, deleteTask };
Étape 4 : Intégration dans le composant principal
Modifiez App.js pour intégrer les composants TaskList et AddTask.
import React from 'react';
import TaskList from './TaskList';
import AddTask from './AddTask';
function App() {
const [tasks, setTasks] = React.useState([]);
const handleAddTask = (newTask) => {
setTasks([...tasks, newTask]);
};
return (
<div className="App">
<h1>Task Manager</h1>
<AddTask onAdd={handleAddTask} />
<TaskList />
</div>
);
}
export default App;
Étape 5 : Ajout des styles (optionnel)
Vous pouvez ajouter quelques styles CSS pour rendre l'application plus attrayante.
/* Dans le fichier App.css */
.App {
text-align: center;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin: 10px 0;
}
button {
margin-left: 10px;
}
Erreurs fréquentes et debugging
1. useQuery retourne une erreur Network Error
## ❌ Mauvais
const { isLoading, error } = useQuery('users', fetchUsers);
## ✅ Correct
import axios from 'axios';
function fetchUsers() {
return axios.get('https://api.example.com/users').then(res => res.data);
}
2. useMutation ne fonctionne pas
## ❌ Mauvais
const [mutate] = useMutation(addUser);
## ✅ Correct
import { useMutation } from 'react-query';
function AddTask({ onAdd }) {
const [name, setName] = useState('');
const [mutate] = useMutation(addTask, {
onSuccess: (newTask) => {
onAdd(newTask);
setName('');
}
});
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Add a new task"
/>
<button onClick={() => mutate(name)}>Add</button>
</div>
);
}
3. useQuery ne met pas à jour les données après la mutation
## ❌ Mauvais
const { data: tasks, refetch } = useQuery('tasks', fetchTasks);
<AddTask onAdd={() => refetch()} />
## ✅ Correct
import { useMutation, useQueryClient } from 'react-query';
function AddTask({ onAdd }) {
const [name, setName] = useState('');
const queryClient = useQueryClient();
const [mutate] = useMutation(addTask, {
onSuccess: (newTask) => {
onAdd(newTask);
setName('');
queryClient.invalidateQueries('tasks');
}
});
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Add a new task"
/>
<button onClick={() => mutate(name)}>Add</button>
</div>
);
}
Pour aller plus loin
1. Utilisation de useQuery avec des paramètres dynamiques
function fetchPosts(userId) {
return fetch(`https://api.example.com/users/${userId}/posts`).then(res => res.json());
}
function UserPosts({ userId }) {
const { isLoading, error, data: posts } = useQuery(['posts', userId], () => fetchPosts(userId));
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
2. Utilisation de useMutation avec des hooks personnalisés
function useAddPost(userId) {
const [mutate, { isLoading, error }] = useMutation(post => fetchPosts(userId, post), {
onSuccess: (newPost, variables) => {
queryClient.invalidateQueries(['posts', variables.userId]);
}
});
return { addPost: mutate, isLoading, error };
}
3. Utilisation de useQuery avec des hooks personnalisés
function usePosts(userId) {
const [query, { data, loading, error }] = useQuery(['posts', userId], () => fetchPosts(userId), {
onSuccess: (newData) => {
console.log('Data updated:', newData);
}
});
return { posts: data, isLoading: loading, isError: !!error };
}
Défi pratique
Créez un projet de gestion simple d'utilisateurs avec les fonctionnalités suivantes :
- Afficher la liste des utilisateurs
- Ajouter un nouvel utilisateur
- Supprimer un utilisateur
- Mettre à jour les informations d'un utilisateur
Utilisez React Query pour gérer les requêtes réseau et affichez le contenu en fonction de l'état actuel (loading, error, success).