Communication Client-Serveur avec React
Communication Client-Serveur avec React
Services React
Cache de donnée
Formulaires avec validations
Cedric Dumoulin
Architecture
La communication se fait entre
les services React
les controleurs Spring
Toujours à l’initiative du client React
Les composants utilisent les services React
Organiser les fichiers
Pas de consensus
Pour commencer : src/
components/
buttons/
textfield/
contexts/
hooks/
pages/
services/
utils/
[Link]
[Link]
Organiser les paquetages
Bibliographie
React Arch
[Link]
itecture: How to Structure and Organize a React Application
Is there a recommended way to structure React projects?
[Link]
Le client :
Affiche les données (après mise en
forme)
Envoie des requêtes
update/create/delete
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 : Erreur 40X 50x
Fetch() ne lance pas d’exception quand il y a des erreurs de type :
40x
50x
useEffect(() => {
setLoading(true);
fetch('[Link]
.then(response => [Link]())
.then(data => {
setConcerts(data);
setLoading(false);
})
}, []);
useEffect()
[Link]
useEffect is a React Hook that lets you synchronize a component with an
external system.
useEffect()
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
[Link]
/Promise
Plusieurs méthode sur cette classe Promise :
then(onFullfillment, onRejection) -
On peut enchainer les appels à then()
catch(onRejection)
onFullfillment – fonction appelé en cas de succès.
onRejection – fonction appelé en cas d’echec
Exemple fetch() + fetch('[Link]
.then(response => [Link]())
.then(data => [Link](data))
Promise .catch(error => [Link](error));
fetch('[Link]
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 = "[Link]
@CrossOrigin(origins = "[Link]
@RestController( )
@RequestMapping("api/concerts")
@CrossOrigin(origins = "[Link]
public class ConcertController {
. . .
Lecture et tutoriaux
Fetching data with Effects
[Link]
How to Fetch Data in React: Cheat Sheet + Examples
[Link]
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()
useEffect(() => {
setLoading(true);
fetch('[Link]
.then(response => [Link]())
.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 créer un objet vide
Update et Create (2)
Handlers changement et submit
// Gere les changement de valeurs dans le formulaire
const handleChange = (event) => {
const { name, value } = [Link]
class ConcertService {
async fetchAllConcerts() {
return fetch(SERVER_URL + '/api/concerts')
.then(response => [Link]());
}
async fetchConcert( id ) {
return fetch(SERVER_URL + `/api/concerts/${id}`)
.then(response => [Link]());
}
/**
*
* @param {*} id
*/
async deleteConcert (id) {
return await fetch(SERVER_URL + '/api/concerts/' + id, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
}
if (loading) {
return <p>Loading...</p>;
}
validationSchema={[Link]({
name: [Link]()
.max(15, 'Must be 15 characters or less')
.required('Required'),
price: [Link]()
.min(5, 'Must be greater than 5')
.required('Required'),
date: [Link](),
})}
Formulaire sans Formik
[Link]
return (
<form>
L’envoyer lors du submit <input value={name}
➔méthode submit onChange={(e) => setName([Link])} />
<input value={email}
onChange={(e) => setEmail([Link])} />
<button onClick={onSubmit}>Submit</button>
</form>
);
};
. . .
// Render the form using bootstrap-react
return (
<div>
Formik <Formik
initialValues={initialFormState}
validationSchema={[Link]({
name: [Link]()
.max(15, 'Must be 15 characters or less')
2 parties : .required('Required'),
price: [Link]()
Entete <Formik ..> .min(5, 'Must be greater than 5')
.required('Required'),
initValue date: [Link](),
})}
validationSchema onSubmit={(values, { setSubmitting }) => {
doUpdate(values);
onSubmit }}
>
<Form className="needs-validation" noValidate >
<div className="form-group">
formulaire <Form ..> <label htmlFor="name">Name</label>
<Field className="form-control" name="name" type="text" />
déclare chaque <ErrorMessage className="invalid-feedback" name="name" />
champs </div>
<div className="form-group">
<label …> <label htmlFor="price">Prix</label>
<Field …> <Field className="form-control" name="price" type="text" />
<ErrorMessage className="invalid-feedback" name="price" />
<ErrorMessage ..> </div>
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
)
};
const ConcertEditFormikBS = () => {
// Création d'un concert vide
const initialFormState = {
name: '',
price: '',
date: '',
};
Formik // etat
const [concert, setConcert] = useState(initialFormState);
const [loading, setLoading] = useState(false);
La gestion du formulaire est const navigate = useNavigate();
// Recupere l'id du concert a éditer
prise en charge par Formik const { id } = useParams();
// Charge le concert si id existe
➔ plus de handleChange() useEffect(() => {
if (id !== 'new') {
setLoading(true);
[Link](id)
Mais il faut quand même : .then(data => {
setConcert(data);
Charger les valeurs initiales setLoading(false);
state loading });
}
Soumettre les valeurs }, [id, setConcert]);
// Methode appelée quand le formulaire est soumis
const doUpdate = async (values) => {
await [Link](values);
// Reset form
setConcert(initialFormState);
// Move away
navigate('/concerts');
}
if (loading) {
return <p>Loading...</p>;
}
Resultat Formik
Formik + Bootstrap
Ma Librairie custom
Comment procéder ?
➔ Créer sa bibliothèque de composants
Formik + Bootstrap
Ma Librairie custom
On définit des composants <MyInput …>
Dans un module séparé : utils/[Link]
utilisent BS
const MyTextInput = ({ label, ...props }) => {
// useField() returns [[Link](), [Link]()]
// which we can spread on <input>. We can use field meta to show an error
// message if the field is invalid and it has been touched (i.e. visited)
const [field, meta] = useField(props);
return (
<div className='form-group'>
<label htmlFor={[Link] || [Link]}>{label}</label>
<input className={inputStyle} {...field} {...props} id={[Link] || [Link]} />
<div className="valid-feedback">Ok !</div>
<div className="invalid-feedback">{[Link]}</div>
</div>
);
};
Formik + Bootstrap
Utilisation <Form className="needs-validation" noValidate>
<MyTextInput
label="Name"
Seul la déclaration des champs name="name"
type="text"
change placeholder="Entrez le nom"
label />
<MyTextInput
name label="Prix"
name="price"
nom de la propriété à éditer type="text"
placeholder="Entrez le prix"
type />
le type d’input html: text, email … <MyTextInput
label="Date"
placeholder name="date"
type="text"
une indication
placeholder="Entrez la date"
/>
<button type="submit">Submit</button>
</Form>
Résultat
Formik + Bootstrap
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
[Link]
import {*} from '@tanstack/react-query'
Site
[Link]
[Link]
Doc :
[Link]
Attention : exemple en V4 !
Dernière version : V5
Mise en place
Un composant englobant tout les appels à tanstack-query
Dans [Link]
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
[Link]({ queryKey: ['todos'] })
useQuery({ …}) },
return un objet avec : })
// Queries
const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })
useQuery({ …})
return (
<div>
<ul>
{[Link]?.map((todo) => (
<li key={[Link]}>{[Link]}</li>
))}
</ul>
</div>
)
}
})
return (
<div>
<button
onClick={() => {
[Link]({
id: [Link](),
title: 'Do Laundry',
})
}}
>
Add Todo </button></div> )}
if (isPending) {
return <span>Loading...</span>
}
if (isError) {
return <span>Error: {[Link]}</span>
}
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
const todoKeys = {
all: ['todos'] as const,
lists: () => [...[Link], 'list'] as const,
list: (filters: string) => [...[Link](), { filters }] as const,
details: () => [...[Link], 'detail'] as const,
detail: (id: number) => [...[Link](), id] as const,
}
// Get all
useQuery( [Link]('all'), fetchAllTodo )
// Get filtered
useQuery( [Link](filters), () => fetchTodos(filters) )
// Get by id
useQuery( [Link](id), () => fetchTodoById(id) )