0% ont trouvé ce document utile (0 vote)
81 vues53 pages

Communication Client-Serveur avec React

Transféré par

madjiguene80
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
81 vues53 pages

Communication Client-Serveur avec React

Transféré par

madjiguene80
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Communication client serveur

Services React
Cache des requetes (ReactQuery)

Cedric Dumoulin
Architecture
 La communication se fait entre
 les services React
 les contrôleurs Spring
 Toujours à l’initiative du client React
 Les composants utilisent les services React
Organiser les Composants, leurs modules …
 Pas de consensus
 Pour commencer : src/
components/
buttons/
textfield/
contexts/
hooks/
pages/
services/
utils/
App.js
index.js
Organiser les paquetages
Bibliographie
 React Arch
 https://www.taniarascia.com/react-architecture-directory-structure/
itecture: How to Structure and Organize a React Application
 Is there a recommended way to structure React projects?
 https://legacy.reactjs.org/docs/faq-structure.html

 ➔ 4 folder structures to organize your React & React Native project


 https://reboot.studio/blog/folder-structures-to-organize-react-project
Le problème
 Le client veut envoyer une requête au
serveur
 Le serveur :
 Fournit des données (ex: Json)
 Reçoit des requêtes
update/create/delete
 Sert des données Json (Controleurs)

 Le client :
 Affiche les données (après mise en
forme)
 Envoie des requêtes
update/create/delete

 ➔ Comment procéder coté client ?


Communiquer avec le serveur
 Un client javascript peut communiquer avec le serveur
 Utilisation de la fonction ‘fetch()’ de javascript
 Il existe aussi des librairies :
 axios

 Les appels au serveur sont asynchrones !


 ➔ il faut utiliser un mécanisme pour attendre la réponse
 ➔ on se retrouve avec plusieurs flots d’exécution
API Fetch
 Fetch() permet
 d’envoyer une requête à un serveur
 GET, POST, PUT, DELETE ….
 Et d’attendre la réponse
 ➔ l’appel est asynchrone
fetch( urlAsString, init ) : Promise

 Paramétres :
 urlAsString : l’url de la ressource (absolue ou relative)
 init (optionel) : permet de passer des paramètres à l’appel
 Retour ;
 une Promise !!
 la Promise est remplie lorsque la réponse est reçue
Important : Fetch() et Erreur 40X 50x
 Fetch() ne lance pas d’exception quand il y a des erreurs de type :
 40x
 Bad Request – Le serveur a reçu la requête, mais celle-ci ne peut pas être traité car elle
comporte un problème (mal formed, authorization …)
 50x
 Erreur coté serveur – Une erreur est survenue du coté serveur

 ➔ Le client doit vérifier le status de la réponse


 Et effectuer l’action appropriée
 En générale envoyer une exception
Fetch et React
 L’appel à fetch() doit se faire après l’initialisation du Component
 ➔ utiliser useEffect()
 La réception de la réponse doit rafraichir le Component
 ➔ sauve la réponse dans un état (useState())
 Les hooks facilitent l’utilisation de fetch()

const [concerts, setConcerts] = useState([]);


const [loading, setLoading] = useState(false);

useEffect(() => {
setLoading(true);

fetch('http://localhost:8080/api/concerts')
.then(response => response.json())
.then(data => {
setConcerts(data);
setLoading(false);
})
}, []);
useEffect()
 https://react.dev/reference/react/useEffect
 useEffect is a React Hook that lets you synchronize a component with an
external system.
useEffect(setup, dependencies?)
useEffect() Setup code

cleanup code

List of dependencies
Plusieurs façon d’utiliser fetch()
 Avec les promises
 Avec await / async
 Avec useFetch()
Requête avec fetch() et Promises
 Fetch renvoie un objet de type Promise
 https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects
/Promise
 Plusieurs méthode sur cette classe Promise :
 then(onFullfillment, onRejection) -
 On peut enchainer les appels à then()
 catch(onRejection)
 onFullfillment – fonction appelée en cas de succès.
 onRejection – fonction appelée en cas d’échec
Exemple fetch() + fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
Promise .catch(error => console.error(error));

fetch('https://api.example.com/data')

 Effectue l’appel asynchrone. Renvoie une Promise


 Si succès, la Promise contient la réponse
.then(response => response.json())

 Traite la réponse, si succès (ne traite pas si echec)


 response : la réponse de l’appel précédent
 response.json() : déserialise la réponse (json->objet JS). Renvoie une Promise
avec le résultat
.then(data => console.log(data))

 Fait un traitement avec l’objet récupéré (ici log dans la console)


.catch(error => console.error(error));

 En cas d’erreur, cette partie est appelée


Fetch + Promise
Exemple plus complet Les états

const [concerts, setConcerts] = useState([]);


const [loading, setLoading] = useState(true); Dans un useEffect()
const [error, setError] = useState(false); Pour que l’appel se fasse après
l’initialisation du composant
useEffect(() => {
setLoading(true); Appel asynchrone
fetch(SERVER_URL + '/api/concerts')
.then(response => {
Attente de la réponse
if(response.ok) { Verification du status OK
return response.json();
}
throw response Deserialisation Json -> objet JS
}) Renvoie d’une promesse avec le résultat
.then(data => {
setConcerts(data);
Traitement de l’objet retourné par json()
setLoading(false);
})
.catch( error => {
Gestion des erreurs
console.error(
'error feetchinf data : ', error);
setError(true)
})
.finally( () => { Dans tout les cas, quand c’est fini :
setLoading(false); loading  false
})
}, []);
On verifie les status.
if (loading) { return <p>Loading...</p>; }
if (error) {return <p>Error !!</p>;} On affiche le status si on n’a pas encore la
réponse
Un état qui recevra la réponse.
Fetch() et await
Crée une méthode async.
Exemple complet Obligatoire
const [joke, setJoke] = useState(‘’);
 On peut utiliser await à la place de la Promise Try / catch
const fetchJoke = async () => {
try { await : indique d’attendre le
const response = await fetch('https://api.example.com/data’, résultat de l’appel asynchrone
{
method: 'GET’, Optionel : on peut indiquer le
type de requête, les headers …
headers: { /* optionel */ }
});
if ( ! response.ok) { Verifie le status de la réponse
throw new Error('Network response was not ok’);
} Deserialise la réponse. Appel
const data = await response.json(); asynchrone.

await : attend le résultat de


setJoke(data[0].joke); l’appel asynchrone

Range le résultat dans un état


} catch (error) {
➔ rafraichissement du comp.
console.error('Error:', error);
}
} Traitement en cas d’erreurs
Utilisation
Dans un useEffect()
useEffect(() => { Pour que l’appel se fasse après
fetchJoke(); l’initialisation du composant
}, []);
Appel de notre méthode asynchrone.
Le résultat sera disponible dans l’état.
En cas d’erreur, pas de résultat
Avec useFetch()
 Necessite la librairie react-fetch-hook
 https://www.npmjs.com/package/react-fetch-hook

Retourne les status et le résultat
import useFetch from "react-fetch-hook";

const { isLoading, data : concerts, error } = Les useStates(), le useEffect() et la


useFetch(SERVER_URL + '/api/concerts'); déserialisation sont encapsulé dans
useFetch()

if (isLoading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error !!</p>;
}
CrossOrigin
 Si le serveur React et le serveur de Spring sont différent :
 ➔ problème de CrossOrigin
 certain navigateur bloquent ce type de requêtes

 Solutions :
 Autoriser le CrossOrigin
 Au niveau du Contrôleur Spring :
 @CrossOrigin(origins = "http://localhost:3000")
@CrossOrigin(origins = "http://localhost:3000")

@RestController( )
@RequestMapping("api/concerts")
@CrossOrigin(origins = "http://localhost:3000")
public class ConcertController {
. . .
Lecture et tutoriaux
 Fetching data with Effects
 https://react.dev/reference/react/useEffect#fetching-data-with-effects
 How to Fetch Data in React: Cheat Sheet + Examples
 https://www.freecodecamp.org/news/fetch-data-react/
Requête pour demander
des données au serveur
 L’appel à fetch() doit se faire après l’initialisation du Component
 ➔ utiliser useEffect()
 La réception de la réponse doit rafraichir le Component
 ➔ sauve la réponse dans un état (useState())
 Les hooks facilitent l’utilisation de fetch()

const [concerts, setConcerts] = useState([]);


const [loading, setLoading] = useState(false);

useEffect(() => {
setLoading(true);

fetch('http://localhost:8080/api/concerts')
.then(response => response.json())
.then(data => {
setConcerts(data);
setLoading(false);
})
}, []);
Requête de type
Update et Create
 Principalement utilisées pour la soumission de formulaires
 Il faut :
 gérer le formulaire
 soumettre la requête quand on soumet le formulaire
 Pour edit :
 On veut commencer par afficher les anciennes valeur
 ➔ faire un read pour charger la valeur initiale
 Pour create :
 il faut initialiser le formulaire avec un objet contenant les valeurs par défaut
(généralement des chaines vides)
Update et Create (1)
Initialisation
import React, { useEffect, useState } from 'react';
import {useNavigate, useParams } from 'react-router-dom';
//import { Button, Container, Form, Form.Group, Form.Control, Form.Label } from 'react-bootstrap';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';

const SERVER_URL = "http://localhost:8080/";

const ConcertEdit = () => {


// Création d'un concert vide
const initialFormState = {
name: '',
price: '',
date: '',
};
// etat
const [concert, setConcert] = useState(initialFormState);
const navigate = useNavigate();
// Recupere l'id du concert a éditer
const { id } = useParams();
// Charge le concert si id existe
useEffect(() => {
if (id !== 'new') {
fetch(SERVER_URL + `/api/concerts/${id}`)
.then(response => response.json())
.then(data => setConcert(data));
}
}, [id, setConcert]);
Update et Create (2)
Handlers changement et submit
// Gere les changement de valeurs dans le formulaire
const handleChange = (event) => {
const { name, value } = event.target

setConcert({ ...concert, [name]: value })


}
// Methode appelée quand le formulaire est soumis
const handleSubmit = async (event) => {
// Disable submit default [email protected]
(html) behavior
event.preventDefault();
// Do fetch , wait the response (await)
// We set method type according to the presence of an id
// Form values are sent as a json object
await fetch( SERVER_URL + '/api/concerts' + (concert.id ? '/' + concert.id : ''), {
method: (concert.id) ? 'PUT' : 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(concert)
});
// Reset form
setConcert(initialFormState);
// Move away
navigate('/concerts');
}
Requetes POST et PUT avec le
SecurityManager Spring
 Si la librairie 'org.springframework.boot:spring-boot-starter-security‘ est
dans le classpath
 il faut desactiver (cross references forgery)
package fil.ipint.resaconcert;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Configure protected area, login and logout pages ...
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// We disable csrf (cross references forgery) when we test without password.
// Otherwise, PUT, POST and DELETE requests don't work (error 403)
// We shoud try to reenabled crsf when passwords are enabled for supervisors
.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403");
} }
Delete
 Ajouter un bouton ‘delete’ pour chaque élément de la liste
<button className='btn btn-primary btn-sm'
type="button" onClick={() => deleteById(concert.id)}>Delete</button>

 Envoyer la requête au serveur


 si la requête est ok, on met a jour la liste locale (en enlevant aussi le concert)
const deleteById = async (id) => {
console.log('delete called');
await fetch(SERVER_URL + '/api/concerts/' + id, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(() => {
let updatedConcert = [...concerts].filter(i => i.id !== id);
setConcerts(updatedConcert);
});
}
Module Service
 Il est préférable de regrouper les requêtes dans un module
 Un service par type d’objet manipulé
 ➔ ex: services/ConcertService, services/ArtisteService
 ➔ ex: services/StageService, services/StudentService
Exemple de service avec une classe
 Pas forcément la meilleur façon de faire
 On peut privilégié un service proposant des hooks …
Ex: ConcertService (1)
const SERVER_URL = "http://localhost:8080";

class ConcertService {

async fetchAllConcerts() {
return fetch(SERVER_URL + '/api/concerts')
.then(response => response.json());
}

async fetchConcert( id ) {
return fetch(SERVER_URL + `/api/concerts/${id}`)
.then(response => response.json());
}

async updateConcert( concert ) {


return fetch( SERVER_URL + '/api/concerts' + (concert.id ? '/' + concert.id : ''), {
method: (concert.id) ? 'PUT' : 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(concert)
});
}
Ex: ConcertService (2)

/**
*
* @param {*} id
*/
async deleteConcert (id) {
return await fetch(SERVER_URL + '/api/concerts/' + id, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
}

export default new ConcertService();


Ex: ConcertService
Utilisation
import ConcertService from '../services/ConcertService';
const ConcertWithFetch = () => {
// . . .
useEffect(() => {
setLoading(true);
ConcertService.fetchAllConcerts()
.then(data => {
setConcerts(data);
setLoading(false);
})
}, []);

if (loading) {
return <p>Loading...</p>;
}

const deleteById = async (id) => {


console.log('delete called');
ConcertService.deleteConcert(id)
.then(() => {
let updatedConcert = [...concerts].filter(i => i.id !== id);
setConcerts(updatedConcert);
});
Atelier
 Vous avez un serveur (back office) acceptant des requêtes pour lister,
créer et détruire une entité (par exemple pour Student et Stage)
 Vous avez des composants React (Front) pour afficher des collections
 Vous allez connecter la partie Front et le serveur afin d’obtenir une
application qui doit :
 afficher la liste des entités (d’abord Student, puis Stage)
 avec les boutons edit et delete pour chaque entité
 permettre la destruction d’un concert
 permettre la création d’un concert (vue plus loin)
 Il manque le formulaire !!
 Votre application doit utiliser, coté React, des services
Le problème
 Pour des raisons de performances
 mettre en cache les données téléchargées
 Permet aussi à plusieurs composants de partager des donnée
 via le cache
 au lieu de partager une variable ou un etat

 Solution
 utiliser un mécanisme de cache
 On peut l’implémenter
 ou utiliser des librairies existantes
 TanStack-Query (React-query)
TanStack-Query (React-query)
 Powerful asynchronous state management
 Installation
 npm i react-query
 v3.39
 import {*} from 'react-query'
 ou npm i @tanstack/react-query
 V5.xx
 import {*} from '@tanstack/react-query'

 Site
 https://github.com/TanStack/query
 https://tanstack.com/query/latest

 Doc :
 https://tanstack.com/query/latest/docs/react/overview
Attention : exemple en V4 !
 Dernière version : V5
Mise en place
 Un composant englobant tout les appels à tanstack-query
 Dans App.js

function App() {

// Create a client
const queryClient = new QueryClient();

return (
// Provide the client to your ConcertsListWithCache
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="component1" element={<Component1 />} />
<Route path="*" element={<NoPage />} />
</Route>
</Routes>
</BrowserRouter>
</QueryClientProvider>

);
}
function Todos() {
// Access the client
const queryClient = useQueryClient()

// Queries
const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })

// Mutations
Utilisation const mutation = useMutation({
mutationFn: postTodo,
onSuccess: () => {
 Read // Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['todos'] })
 useQuery({ …}) },
 return un objet avec : })

 data : le résultat return (


 isLoading : boolean <div>
<ul>
 isError : boolean {query.data?.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
 Update/create </ul>
<button
 utiliser les mutations
onClick={() => {
 On déclare la mutation mutation.mutate({
id: Date.now(),
et son handler en cas de title: 'Do Laundry',
succès })
}}
 On appel la mutation
>
au moment ou l’on veut Add Todo
faire l’update </button>
</div>
)
}
function Todos() {
// Access the client
const queryClient = useQueryClient()

// Queries
const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })
useQuery({ …})
return (
<div>
<ul>
{query.data?.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
)
}

 useQuery({ queryKey: [], queryFn: fetchFct })


 queryKey:
 Un tableau de clés
 Permet de donner un identifiant au résultat
 fetchFct
 La fonction retouornant un résultat
 Peut retourner une Promise
 return un objet avec :
 data : le résultat
 isLoading : boolean
 isError : boolean
function Todos() {
// Access the client
const queryClient = useQueryClient()
// Mutations
const mutation = useMutation({
Update : mutationFn: postTodo,
onSuccess: () => {
// Invalidate and refetch
useMutation(…) },
queryClient.invalidateQueries({ queryKey: ['todos'] })

})

return (
<div>
<button
onClick={() => {
mutation.mutate({
id: Date.now(),
title: 'Do Laundry',
})
}}
>
Add Todo </button></div> )}

 useMutation( { mutationFn: fetchFct, onSuccess: fct })


 mutationFn: la fonction appelé après l’appel à mutation.mutate(data)
 onSuccess: La fonction appelé quand le fetch c’est bien passé
 mutation.mutate(data)
 A appeler lorsque l’on veut effectuer la mutation
 Data : les données à envoyer. Sont passé à la fonction mutationFn
useQuery()
Prise en compte de isPending, isError …
function Todos() {
//
const { isPending, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})

if (isPending) {
return <span>Loading...</span>
}

if (isError) {
return <span>Error: {error.message}</span>
}

// We can assume by this point that `isSuccess === true`


return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
Comportement du cache
 Par défaut :
 quand on recharge le composant :
 useQuery retourne le contenu actuelle
 useQuery recharge le cache
 quand le nouveau contenu est mis a jour, le composant est rafraichi
 ➔ il y a une requête à chaque fois !
 Pour éviter les rechargements (dans App.js) :
// Query client used to interact with the cache
// We specify the staleTime in order to avoid
// reloading each time the page is re-open.
// Note : we can change to use a delay.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
})
Affichage des
concerts function ConcertsListWithCache() {
const navigate = useNavigate();
revisité // Access the client
const queryClient = useQueryClient()
// Queries
 isLoading et const { isLoading, isError, data: concerts, error }
= useQuery(['concerts'], ConcertService.fetchAllConcerts)
isError // update Mutations
 fourni par const updateMutation = useMutation(ConcertService.updateConcert, {
onSuccess: () => {
tanstack-query queryClient.invalidateQueries(['concerts'])
},
})
// delete Mutations
const deleteMutation = useMutation(ConcertService.deleteConcert, {
onSuccess: () => {
queryClient.invalidateQueries(['concerts'])
},
})

if (isLoading) {
return (<div>Loading ...</div>)
}

if (isError) {
return (<div>Error while loading ...</div>)
}
Affichage des
concerts
revisité (2)
 isLoading et isError
 fourni par tanstack-query

 L’appel a mutate() entraine l’appel de la fonction associée (delete ou


update)

<button className='btn btn-primary btn-sm'


type="button"
onClick={() => deleteMutation.mutate(concert.id)}
>
Delete
</button>
Debuguer le cache
 Utiliser ReactQueryDevTool
 https://tanstack.com/query/v5
/docs/react/devtools
 npm i @tanstack/react-query-
import {
devtools QueryClient, QueryClientProvider,
} from '@tanstack/react-query'
 Visible en mode dev import { ReactQueryDevtools }
from "@tanstack/react-query-devtools";
 invisible en mode prod
function App() {
// Create a client
const queryClient = new QueryClient();
return (
// Provide the client to your ConcertsListWithCache
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
</Route>
</Routes>
</BrowserRouter>
<ReactQueryDevtools position="bottom-right" />
</QueryClientProvider>
);
tanstack-query bonne pratiques
 Voir l’article
 Effective React Query Keys
 https://tkdodo.eu/blog/effective-react-query-keys
 Use Query Key factories

const todoKeys = {
all: ['todos'] as const,
lists: () => [...todoKeys.all, 'list'] as const,
list: (filters: string) => [...todoKeys.lists(), { filters }] as const,
details: () => [...todoKeys.all, 'detail'] as const,
detail: (id: number) => [...todoKeys.details(), id] as const,
}

// Get all
useQuery( todoKeys.list('all'), fetchAllTodo )
// Get filtered
useQuery( todoKeys.list(filters), () => fetchTodos(filters) )
// Get by id
useQuery( todoKeys.detail(id), () => fetchTodoById(id) )

// 🕺 remove everything related to the todos feature


queryClient.removeQueries(todoKeys.all)

// 🚀 invalidate all the lists


queryClient.invalidateQueries(todoKeys.lists())
Atelier
 Modifier votre application afin de :
 relocaliser les appels vers le serveur dans des services reacts.
 Utiliser les formulaires Formik et la validation client Yup
 Utiliser les caches tanstack-query

Vous aimerez peut-être aussi