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 :

src/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.

src/App.jsx
		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.

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.