0% ont trouvé ce document utile (0 vote)
100 vues65 pages

Communication Client-Serveur avec React

client serveur Communication client serveur Services React Cache de donnée Formulaires avec validations Cedric Dumoulin

Transféré par

salohivinta
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)
100 vues65 pages

Communication Client-Serveur avec React

client serveur Communication client serveur Services React Cache de donnée Formulaires avec validations Cedric Dumoulin

Transféré par

salohivinta
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 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]

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


 [Link]
Le problème
 Communiquer entre le serveur et le
client
 Le serveur :
 Sert 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 : Erreur 40X 50x
 Fetch() ne lance pas d’exception quand il y a des erreurs de type :
 40x
 50x

 ➔ Il faut 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('[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]

 Effectue l’appel asynchrone. Renvoie une Promise


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

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


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

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


.catch(error => [Link](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([Link]) { Verification du status OK
return [Link]();
}
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
[Link](
'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('[Link] résultat de l’appel asynchrone
{
method: 'GET’, Optionel : on peut indiquer le
type de requête, les headers …
headers: { /* optionel */ }
});
if ( ! [Link]) { Verifie le status de la réponse
throw new Error('Network response was not ok’);
} Deserialise la réponse. Appel
const data = await [Link](); 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.
[Link]('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()
 Demande une librairie
 [Link]

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 = "[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()

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


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

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]

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


}
// Methode appelée quand le formulaire est soumis
const handleSubmit = async (event) => {
// Disable submit default [Link]@[Link]
(html) behavior
[Link]();
// 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' + ([Link] ? '/' + [Link] : ''), {
method: ([Link]) ? 'PUT' : 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: [Link](concert)
});
// Reset form
setConcert(initialFormState);
// Move away
navigate('/concerts');
}
Requetes POST et PUT avec le
SecurityManager Spring
 Si la librairie '[Link]:spring-boot-starter-security‘ est
dans le classpath
 il faut desactiver (cross references forgery)
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@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 element de la liste
<button className='btn btn-primary btn-sm'
type="button" onClick={() => deleteById([Link])}>Delete</button>

 Envoyer la requête au serveur


 si la requete est ok, on met a jour la liste locale (en enlevant aussi le concert)
const deleteById = async (id) => {
[Link]('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 => [Link] !== id);
setConcerts(updatedConcert);
});
}
Atelier
 Modifier votre application Gestion de tickets de Concert afin de la
connecter au serveur.
 Votre application doit :
 afficher la liste des concerts
 avec les boutons edit et delete pour chaque concert
 permettre la destruction d’un concert
 permettre la création d’un concert (vue plus loin)
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
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 = "[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]());
}

async updateConcert( concert ) {


return fetch( SERVER_URL + '/api/concerts' + ([Link] ? '/' + [Link] : ''), {
method: ([Link]) ? 'PUT' : 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: [Link](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);
[Link]()
.then(data => {
setConcerts(data);
setLoading(false);
})
}, []);

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

const deleteById = async (id) => {


[Link]('delete called');
[Link](id)
.then(() => {
let updatedConcert = [...concerts].filter(i => [Link] !== id);
setConcerts(updatedConcert);
});
Formik
 [Link]
 npm install formik –save

 Librairie javascript pour simplifier l’écriture de formulaires


 permet la validation
 notamment avec Yup
 Supporte les formulaires avec collections
 ajout/suppressions d’items, modification des items …

 Hautement customisable ou adaptable à votre façon de faire


 Pour bien comprendre Formik :
 [Link]
Yup
 [Link]
 npm install -S yup

 “schema builder for runtime value parsing and validation”


 Permet de specifier la validation attendue
 Le schema ressemble à une declaration similaire à l’objet à valider
 propertyName : expected validation

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]

 Il faut gérer la saisie de


l’utilisateur :
const MyForm = () => {
 Sauvegarder le texte saisie const [name, setName] = useState('');
 ➔ plusieurs états const [email, setEmail] = useState('');

 ➔plusieurs onClick() ou const onSubmit = () => {


alert(`Submitted ${name} ${email}`);
handleChange() };

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

 BS fournit des styles pour les formulaires


 Présentation
 Alignement
 Coloration en cas d’erreur de saisie ..

 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);

let inputStyle = "form-control"


if ([Link]) {
inputStyle = inputStyle + ([Link] ? " is-invalid" : " is-valid");
}

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 : })

 data : le résultat return (


 isLoading : boolean <div>
<ul>
 isError : boolean {[Link]?.map((todo) => (
<li key={[Link]}>{[Link]}</li>
))}
 Update/create </ul>
<button
 utiliser les mutations
onClick={() => {
 On déclare la mutation [Link]({
id: [Link](),
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>
{[Link]?.map((todo) => (
<li key={[Link]}>{[Link]}</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(…) },
[Link]({ queryKey: ['todos'] })

})

return (
<div>
<button
onClick={() => {
[Link]({
id: [Link](),
title: 'Do Laundry',
})
}}
>
Add Todo </button></div> )}

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


 mutationFn: la fonction appelé après l’appel à [Link](data)
 onSuccess: La fonction appelé quand le fetch c’est bien passé
 [Link](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: {[Link]}</span>
}

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


return (
<ul>
{[Link]((todo) => (
<li key={[Link]}>{[Link]}</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 [Link]) :
// 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'], [Link])
isError // update Mutations
 fourni par const updateMutation = useMutation([Link], {
onSuccess: () => {
tanstack-query [Link](['concerts'])
},
})
// delete Mutations
const deleteMutation = useMutation([Link], {
onSuccess: () => {
[Link](['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={() => [Link]([Link])}
>
Delete
</button>
Debuguer le cache
 Utiliser ReactQueryDevTool
 [Link]
/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
 [Link]
 Use Query Key factories

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) )

// 🕺 remove everything related to the todos feature


[Link]([Link])

// 🚀 invalidate all the lists


[Link]([Link]())
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
Storybook
 [Link]

 Storybook is a frontend workshop for building UI components and pages


in isolation.

Vous aimerez peut-être aussi