Pourquoi React avec GraphQL ?
React avec GraphQL est une combinaison puissante qui permet aux développeurs de créer des applications réactives et performantes en utilisant une API GraphQL pour récupérer les données nécessaires. En effet, GraphQL offre une grande flexibilité et un contrôle précis sur les données renvoyées par l'API. Cela signifie que chaque composant React peut demander exactement ce qu'il a besoin de savoir, sans avoir à se soucier des relations complexes ou des sous-ressources.
Un cas d'utilisation concret serait une application de messagerie instantanée où chaque utilisateur peut voir son propre flux de messages. Avec GraphQL, chaque composant ne demande que les messages du destinataire en question, ce qui réduit considérablement le temps de chargement et améliore l'expérience utilisateur.
Prerequis
- Connaissance approfondie de React
- Familiarité avec JavaScript ES6+
- Compréhension des concepts d'état (state) et de props dans React
- Connaissance de la structure de fichiers et du cycle de vie des composants React
- Installation de Node.js v14 ou supérieur
Concepts fondamentaux
1. GraphQL : Un langage de requête pour API
GraphQL est une spécification qui définit un langage de requêtes et de mutations permettant aux clients d'interroger les données qu'ils ont besoin, sans avoir à récupérer toutes les données possibles.
## Schema GraphQL simple pour une application de tâches
type Task {
id: ID!
title: String!
completed: Boolean!
}
type Query {
tasks(filter: String): [Task]
}
2. Apollo Client : Le client GraphQL officiel pour React
Apollo Client est un client GraphQL moderne qui permet d'intégrer facilement la récupération et la mise à jour de données dans des applications React.
## Installation d'Apollo Client et des dépendances nécessaires
npm install @apollo/client graphql react-apollo-hooks
3. Queries : Requêtes pour récupérer des données
Les queries sont utilisées pour récupérer des données depuis l'API GraphQL. Elles sont spécifiées dans le composant React.
// Exemple de Query en utilisant Apollo Client
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';
const TASKS_QUERY = gql`
query Tasks {
tasks(filter: "") {
id
title
completed
}
}
`;
function TaskList() {
const { loading, error, data } = useQuery(TASKS_QUERY);
if (loading) return <p>Chargement...</p>;
if (error) return <p>Erreur : {error.message}</p>;
return (
<ul>
{data.tasks.map(task => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}
4. Mutations : Mises à jour des données
Les mutations sont utilisées pour modifier les données sur le serveur, y compris la création, la mise à jour et la suppression de ressources.
// Exemple de Mutation en utilisant Apollo Client
import { useMutation } from '@apollo/client';
import gql from 'graphql-tag';
const CREATE_TASK_MUTATION = gql`
mutation CreateTask($title: String!) {
createTask(title: $title) {
id
title
completed
}
}
`;
function TaskForm() {
const [createTask, { loading, error }] = useMutation(CREATE_TASK_MUTATION);
if (loading) return <p>Chargement...</p>;
if (error) return <p>Erreur : {error.message}</p>;
const handleSubmit = async event => {
event.preventDefault();
await createTask({ variables: { title: 'Nouvelle tâche' } });
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Créer une tâche</button>
</form>
);
}
5. Subscriptions : Mise à jour en temps réel
Les subscriptions sont utilisées pour écouter les événements de mise à jour en temps réel sur le serveur.
// Exemple de Subscription en utilisant Apollo Client
import { useSubscription } from '@apollo/client';
import gql from 'graphql-tag';
const NEW_TASK_SUBSCRIPTION = gql`
subscription NewTask {
newTask {
id
title
completed
}
}
`;
function TaskList() {
const { loading, error, data } = useSubscription(NEW_TASK_SUBSCRIPTION);
if (loading) return <p>Chargement...</p>;
if (error) return <p>Erreur : {error.message}</p>;
return (
<ul>
{data.newTask ? (
<li key={data.newTask.id}>{data.newTask.title}</li>
) : null}
</ul>
);
}
Mise en pratique : projet fil rouge
1. Configuration du projet
## Création d'un nouveau projet React
npx create-react-app react-graphql-app
cd react-graphql-app
## Installation d'Apollo Client et des dépendances nécessaires
npm install @apollo/client graphql react-apollo-hooks
2. Structure de fichiers
react-graphql-app/
├── src/
│ ├── App.js
│ ├── index.js
│ ├── TaskList.js
│ ├── TaskForm.js
│ └── tasks.graphql
└── package.json
3. Configuration d'Apollo Client
Créez un fichier ApolloClientProvider.js :
// src/ApolloClientProvider.js
import React from 'react';
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({ uri: 'http://localhost:4000/graphql' }),
cache: new InMemoryCache(),
});
const ApolloClientProvider = ({ children }) => (
<ApolloProvider client={client}>
{children}
</ApolloProvider>
);
export default ApolloClientProvider;
4. Composants React
TaskList.js
// src/TaskList.js
import React from 'react';
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';
const TASKS_QUERY = gql`
query Tasks {
tasks(filter: "") {
id
title
completed
}
}
`;
function TaskList() {
const { loading, error, data } = useQuery(TASKS_QUERY);
if (loading) return <p>Chargement...</p>;
if (error) return <p>Erreur : {error.message}</p>;
return (
<ul>
{data.tasks.map(task => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}
export default TaskList;
TaskForm.js
// src/TaskForm.js
import React from 'react';
import { useMutation } from '@apollo/client';
import gql from 'graphql-tag';
const CREATE_TASK_MUTATION = gql`
mutation CreateTask($title: String!) {
createTask(title: $title) {
id
title
completed
}
}
`;
function TaskForm() {
const [createTask, { loading, error }] = useMutation(CREATE_TASK_MUTATION);
if (loading) return <p>Chargement...</p>;
if (error) return <p>Erreur : {error.message}</p>;
const handleSubmit = async event => {
event.preventDefault();
await createTask({ variables: { title: 'Nouvelle tâche' } });
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Créer une tâche</button>
</form>
);
}
export default TaskForm;
App.js
// src/App.js
import React from 'react';
import ApolloClientProvider from './ApolloClientProvider';
import TaskList from './TaskList';
import TaskForm from './TaskForm';
function App() {
return (
<ApolloClientProvider>
<div className="App">
<h1>Gestionnaire de tâches</h1>
<TaskForm />
<TaskList />
</div>
</ApolloClientProvider>
);
}
export default App;
5. Execution du projet
## Démarrage du serveur GraphQL (supposons que votre API soit en cours d'exécution sur http://localhost:4000/graphql)
npm start
Erreurs frequentes et debugging
1. Erreur : Network error while attempting to fetch resource.
## Code incorrect
const TASKS_QUERY = gql`
query Tasks {
tasks(filter: "") {
id
title
completed
}
}
`;
## Correction
const TASKS_QUERY = gql`
query Tasks($filter: String) {
tasks(filter: $filter) {
id
title
completed
}
}
`;
2. Erreur : Invariant Violation: Objects are not valid as a React child.
## Code incorrect
const { data } = useQuery(TASKS_QUERY);
return (
<ul>
{data.tasks.map(task => (
<li key={task.id}>{task}</li> // Problème : Tâche est un objet, pas une chaîne de caractères
))}
</ul>
);
## Correction
const { data } = useQuery(TASKS_QUERY);
return (
<ul>
{data.tasks.map(task => (
<li key={task.id}>{task.title}</li> // Correction : Utilisation du champ title
))}
</ul>
);
3. Erreur : Invariant Violation: Can't perform a React state update on an unmounted component.
## Code incorrect
const [createTask] = useMutation(CREATE_TASK_MUTATION);
useEffect(() => {
const subscription = someSubscription().subscribe({
next(data) {
createTask({ variables: { title: data.newTask.title } });
},
});
return () => subscription.unsubscribe();
}, []);
## Correction
const [createTask] = useMutation(CREATE_TASK_MUTATION);
useEffect(() => {
const subscription = someSubscription().subscribe({
next(data) {
if (isMounted.current) { // Vérification si le composant est toujours monté
createTask({ variables: { title: data.newTask.title } });
}
},
});
return () => subscription.unsubscribe();
}, []);
Pour aller plus loin
1. Optimisation des requêtes avec GraphQL Fragments
Les fragments permettent de réutiliser les parties communes des requêtes.
## tasks.graphql
fragment TaskFields on Task {
id
title
completed
}
query Tasks {
tasks(filter: "") {
...TaskFields
}
}
2. Intégration avec React Hooks personnalisés
Les hooks personnalisés permettent de factoriser la logique réutilisable.
// src/useTasks.js
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';
const TASKS_QUERY = gql`
query Tasks($filter: String) {
tasks(filter: $filter) {
id
title
completed
}
}
`;
export default function useTasks(filter) {
const { loading, error, data } = useQuery(TASKS_QUERY, { variables: { filter } });
return { loading, error, tasks: data?.tasks };
}
3. Utilisation de Apollo Link pour ajouter des middleware
Les middlewares peuvent être utilisés pour intercepter les requêtes et effectuer des opérations avant ou après l'exécution.
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({ uri: 'http://localhost:4000/graphql' }),
cache: new InMemoryCache(),
});
client.use([
({ operation, next }, forward) => {
console.log('Middleware : ', operation);
return forward(operation);
},
]);
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
Défi pratique
Défi : Créer un mini-projet complet qui implémente la gestion des utilisateurs (CRUD) avec GraphQL et Apollo Client. Utilisez des fragments pour optimiser les requêtes et des hooks personnalisés pour factoriser la logique de récupération des données.