Interagir avec le backend
Ce cours fait partiellement suite au cours de Node.js.
Objectifs
- Comprendre comment React Query fonctionne
- Utiliser React Query pour interagir avec notre API
- Intégrer les données dans notre application
Connexion back / front
On a déjà créé notre API. On peut la trouver sur http://localhost:3000/. On va donc pouvoir l'utiliser dans notre front. On pourrait utiliser un fetch classique, mais on va utiliser une librairie qui s'appelle react-query.
React Query
On commence par installer la librairie :
npm i @tanstack/react-query
On va ensuite créer un client React Query, dans notre main.jsx :
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.jsx';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
createRoot(document.getElementById('root')).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>
);
Ici on vient ajouter un nouveau composant, QueryClientProvider, qui va nous permettre de fournir le client React Query à l'ensemble de l'application. En fait, c'est comme si on fournissait un contexte à l'ensemble de l'application. On lui dit qu'on utilise React Query.
Récupérer nos tâches
Pour rappel, nos tâches se situent sur l'API sur la route /tasks. On va donc pouvoir récupérer les tâches avec la fonction useQuery de React Query.
import { useQuery } from '@tanstack/react-query';
export default function App() {
const { data, isLoading, error } = useQuery({
queryKey: ['tasks'],
queryFn: () => fetch('http://localhost:3000/tasks').then((res) => res.json())
});
// Exemple pour afficher les tâches (ne pas copier)
return (
<div>
{data.map((task) => (
<Task key={task.id} task={task} />
))}
</div>
);
}
Vous remarquerez que la fonction useQuery prend deux arguments :
queryKey: un array avec des clés qui permettent d'identifier la requête. C'est ce qui permet à React Query d'ajouter la requête au cache, par exemple.queryFn: la fonction qui va récupérer les données
Mais queryFn utilise fetch ? Oui, mais React Query s'occupe de gérer ce fetch pour ne pas causer de problème avec React, et s'occupe d'autres choses comme le cache, les erreurs, etc.
Erreur CORS
Ah ! Vous avez peut-être eu une erreur CORS ? C'est normal, notre API est sur un autre domaine que notre front, et elle n'est pas configurée pour recevoir des requêtes depuis un autre domaine. On pourrait configurer notre API pour recevoir des requêtes depuis un autre domaine, mais c'est un peu compliqué. On va donc utiliser un proxy avec Vite.
Vite nous permet d'ajouter un proxy dans le fichier vite.config.ts :
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
Ok, pour clarifier, "localhost:5173/api" (notre front) va être redirigé vers "localhost:3000" (notre API). La partie changeOrigin permet de faire croire au back-end que la requête vient du même domaine, et pour finir la partie rewrite permet de retirer le "/api" de la requête pour que le back-end puisse la traiter correctement.
On peut donc changer notre fetch pour utiliser le proxy :
const { data, isLoading, error } = useQuery({
queryKey: ['tasks'],
queryFn: () => fetch('/api/tasks').then((res) => res.json())
});
Pour finir, une convention que l'API suit c'est de retourner un objet avec un status, et un data. Donc, qunad vous accédez à data de la fonction useQuery, vous aurez l'objet avec le status et le data.
console.log(data);
Vous devriez voir un objet avec un status et un data.
Pour récupérer les tâches en particulier, on va donc accéder à data.data.
const tasks = data?.data;
PS: Si vous avez une erreur tasks is not defined, lorsque vous faites le forEach sur les tâches pour les grouper, c'est parce que tasks n'est pas toujours défini. Lors du fetch, tasks est vide jusqu'à ce que les données soient récupérées.
x 1. Utiliser le `?.` pour vérifier si la propriété existe (tasks?.forEach)
2. Utiliser la propriété `placeholderData` de React Query pour donner des données par défaut le temps que les données soient récupérées
3. Ne pas faire le forEach tant que `isLoading` est true
Ajouter une tâche
On va utiliser ce qui est déjà en place pour ajouter une tâche. Avant on a utilisé useQuery pour récupérer les tâches, maintenant on va utiliser useMutation pour ajouter une tâche.
const { mutate } = useMutation({
mutationFn: (task) =>
fetch('/api/tasks', {
method: 'POST',
body: JSON.stringify(task)
})
});
On va donc pouvoir ajouter une tâche avec la fonction mutate.
handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const form = Object.fromEntries(formData.entries());
mutate({ title: form.title, description: form.description, status: form.status });
};
Et voilà, on peut lancer notre application et voir les tâches, ainsi que les ajouter ! Si on en ajoute une, et qu'on rafraîchit la page, on voit que la tâche a bien été ajoutée.
Récupérer les nouvelles tâches automatiquement
Le fait de devoir rafraîchir la page pour voir les nouvelles tâches, c'est pas très pratique. On va donc utiliser onSuccess dans la mutation pour récupérer les nouvelles tâches. Comment ça marche ? Lorsque la reqûete est un succès, on va relancer le useQuery pour récupérer les nouvelles tâches. On va récupérer la fonction refetch dans le useQuery, et on va l'appeler dans le onSuccess de la mutation.
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['tasks'],
queryFn: () => fetch('/api/tasks').then((res) => res.json())
});
const { mutate } = useMutation({
mutationFn: (task) =>
fetch('/api/tasks', {
method: 'POST',
body: JSON.stringify(task)
}),
onSuccess: () => {
refetch();
}
});
Donc pour résumer, lorsque la mutation est un succès, on relance le useQuery pour récupérer les nouvelles tâches.