297 lines
No EOL
8.1 KiB
Markdown
297 lines
No EOL
8.1 KiB
Markdown
# Notre premier composant React
|
|
|
|
Si vous êtes arrivé ici, c'est que vous avez compris ce qu'est un composant React et qu'il est composant de `props` et d'un `state`.
|
|
|
|
Mettons donc en pratique ce que vous avez vu.
|
|
|
|
Voici le cahier de charge fonctionnel du composant à implémenter :
|
|
|
|
Fonction:
|
|
- On veut ajouter un bouton `Home` dans l'éditeur pour revenir au menu principal
|
|
|
|
Contraintes principales :
|
|
- Le bouton `home` ne doit pas rafraîchir la page
|
|
|
|
Contraites secondaires :
|
|
- Le bouton est positionné en bas sur la barre
|
|
- Le bouton change de couleur à chaque fois que le curseur entre dedans
|
|
- Le bouton possède un icône home
|
|
|
|
## Initialiser un composant
|
|
|
|
Commençont par créer le composant sans le lier forcément au reste du projet.
|
|
|
|
Par convention comme il est dit dans la section [Pour commencer](PourCommencer.md), créons le fichier `Home.tsx` dans un dossier `Home/` dans `src/Components`. Il est recommandé de toujours créer un dossier pour le composant pour plus tard si on veut ajouter des styles propres au composant ou si on veut faire des tests.
|
|
|
|
Maintenant créons le squelette du composant. Pour rappel, on utilise la syntaxe *fonctionnelle* (i.e. une fonction = un composant).
|
|
|
|
```tsx
|
|
// Home.tsx
|
|
export function Home(): JSX.Element {
|
|
return <></>;
|
|
}
|
|
```
|
|
|
|
Note: vous pouvez utiliser le snippet `tsrfc` si vous utilisez l'extension de VSCode.
|
|
|
|
La convention de nommage indique qu'il faut prioriser les fonctions nommées (donc pas de flèches fonctions).
|
|
|
|
Mettons le bouton html et mettons du texte dedans :
|
|
|
|
```tsx
|
|
export function Home(): JSX.Element {
|
|
return (
|
|
<button>
|
|
Home
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
|
|
## Appeler une fonction
|
|
|
|
Pour que le bouton appelle une fonction nous allons utiliser la propriété `onClick`. Pour lui équiper d'une variable/fonction, on la met entre accolades `prop={var}`.
|
|
|
|
```tsx
|
|
function GoHome(): void {
|
|
location.reload();
|
|
}
|
|
|
|
export function Home(): JSX.Element {
|
|
return (
|
|
<button
|
|
onClick={GoHome}
|
|
>
|
|
Home
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
Cela devrait être suffisant pour revenir au menu principal. Appelons-le dans l'editeur.
|
|
|
|
|
|
## Ajouter le composant dans l'editeur
|
|
|
|
Tout ce qui est UI doit se mettre dans `UI.tsx`.
|
|
On sait que l'on doit l'ajouter dans la **barre** à gauche, soit `Bar.tsx`
|
|
|
|
Commençons par importer le composant dans `Bar.tsx`:
|
|
|
|
```tsx
|
|
// Bar.tsx
|
|
import { Home } from '../Home/Home';
|
|
...
|
|
```
|
|
|
|
Puis ajoutons-le dans la vue :
|
|
|
|
```tsx
|
|
// Bar.tsx
|
|
export function Bar(props: IBarProps): JSX.Element {
|
|
return (
|
|
<div className='bar'>
|
|
...
|
|
<Home />
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
Testez avec `pnpm dev`. Et ouvrez la page sur `http://localhost:5173`.
|
|
|
|
Et voilà ! Vous avez fini !
|
|
|
|
...
|
|
|
|
Sauf que vous n'avez respecté aucune des contraintes.
|
|
|
|
Faisons mieux. Utilisons les `props` et le `state` de l'application.
|
|
|
|
|
|
## Props et state
|
|
|
|
Le composant doit restaurer l'état du menu principal.
|
|
|
|
Dans le menu principal, lorsque l'on clique sur `Start from scratch`, le composant doit forcément changer un état pour cacher le menu.
|
|
|
|
Ce que l'on veut faire est donc de renverser cette opération, autrement dit changer l'état qui permet de cacher le menu pour le remontrer.
|
|
|
|
Allons donc dans le composant `MainMenu.tsx` et analysons le contenu du bouton `Start from scratch`.
|
|
|
|
```tsx
|
|
// MainMenu.tsx
|
|
...
|
|
default:
|
|
return (
|
|
<div className='absolute bg-blue-50 p-12 rounded-lg drop-shadow-lg grid grid-cols-1 md:grid-cols-2 gap-8 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
|
<button type="button" className='mainmenu-btn' onClick={() => {
|
|
setWindowState(WindowState.Loading);
|
|
props.newEditor();
|
|
}}>Start from scratch</button>
|
|
<button type="button" className='mainmenu-btn' onClick={() => setWindowState(WindowState.Load)}>Load a configuration file</button>
|
|
</div>
|
|
);
|
|
```
|
|
|
|
On trouve la fonction `onClick` suivante :
|
|
|
|
```tsx
|
|
// MainMenu.tsx
|
|
{
|
|
setWindowState(WindowState.Loading);
|
|
props.newEditor();
|
|
}
|
|
```
|
|
|
|
Aha! On trouve une fonction qui change d'état. Cependant ! On découvre aussi qu'elle appelle une fonction `newEditor`.
|
|
|
|
Instinctivement, vu que l'on cherche une fonction qui permet de changer d'état, on pourrait croire que c'est `setWindowState` qui gère mais on va vite comprendre que ce n'est pas elle.
|
|
|
|
Pour rappel `setWindowState` est un setteur d'état, et qu'elle est définie **dans** le composant. Autrement dit, elle ne gère pas l'état de l'application. De plus, un indice indique que le l'état du menu passe de `Main` à `Loading`. Elle ne fait donc que changer sa propre vue pour passer à celle d'une animation de loading que l'on peut voir plus haut dans le `switch`.
|
|
|
|
`newEditor` étant une fonction donnée par `props`, doit donc correspondre à la fonctionalité que l'on veut renveser.
|
|
|
|
Regardons donc le composant parent de `MainMenu`: `App`.
|
|
|
|
```tsx
|
|
// App.tsx
|
|
...
|
|
<MainMenu
|
|
newEditor={() => NewEditor(
|
|
setEditorState, setLoaded
|
|
)}
|
|
loadEditor={(files: FileList | null) => LoadEditor(
|
|
files,
|
|
setEditorState,
|
|
setLoaded
|
|
)}
|
|
/>
|
|
...
|
|
```
|
|
|
|
Cette fonction `newEditor` appelle donc une autre fonction `NewEditor`.
|
|
|
|
```tsx
|
|
export function NewEditor(
|
|
setEditorState: Dispatch<SetStateAction<IEditorState>>,
|
|
setLoaded: Dispatch<SetStateAction<boolean>>
|
|
): void {
|
|
// Fetch the configuration from the API
|
|
FetchConfiguration()
|
|
.then((configuration: IConfiguration) => {
|
|
// Set the editor from the given properties of the API
|
|
const editorState: IEditorState = GetDefaultEditorState(configuration);
|
|
|
|
setEditorState(editorState);
|
|
setLoaded(true);
|
|
}, (error) => {
|
|
console.debug('[NewEditor] Could not fetch resource from API. Using default.', error);
|
|
setLoaded(true);
|
|
});
|
|
}
|
|
```
|
|
|
|
Cette fonction à l'air assez compliquée, mais on sait qu'elle utilise `setEditorState` et `setLoaded`. Et c'est tout ce qui nous intéresse.
|
|
|
|
Donc pour renverser l'état de `App` il faut appeler soit `setEditorState` ou soit `setLoaded`. En analysant un peu plus près `App.tsx`, on remarque l'évidante condition permettant d'afficher ou non le menu principal :
|
|
|
|
```tsx
|
|
// App.tsx
|
|
...
|
|
if (isLoaded) {
|
|
return (
|
|
<div>
|
|
<Editor
|
|
(...)
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className='mainmenu-bg'>
|
|
<MainMenu
|
|
(...)
|
|
/>
|
|
</div>
|
|
```
|
|
|
|
Ainsi, on sait qu'il faut juste appeler `setLoaded` le setteur d'état de `isLoaded`:
|
|
|
|
```tsx
|
|
// App.tsx
|
|
const [isLoaded, setLoaded] = useState<boolean>(false);
|
|
```
|
|
|
|
Cependant comment appeler cette fonction depuis `Home` ? Et bien nous allons utiliser les `props` pour passer la fonction en dessous.
|
|
|
|
Comme pour `MainMenu`, dans `Home.tsx` nous allons mettre la fonction `GoHome` dans les props :
|
|
|
|
```tsx
|
|
import React from 'react';
|
|
|
|
interface IHomeProps {
|
|
goHome: () => void
|
|
}
|
|
|
|
export function Home({ goHome }: IHomeProps): JSX.Element {
|
|
return (
|
|
<button
|
|
onClick={goHome}
|
|
>
|
|
Home
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
Note: vous pouvez utiliser `Home(props: IHomeProps)` et appeler `props.goHome` comme tout le reste du projet, mais le destructuring d'objet est autorisé.
|
|
|
|
Home est utilisé dans les composants Bar, UI, et Editor avant d'être appelé dans App. Il faut faire la même chose, ajouter la fonction dans les props et l'appeler dans le composant :
|
|
|
|
```tsx
|
|
// Bar.tsx
|
|
interface IBarProps {
|
|
goHome: () => void
|
|
}
|
|
...
|
|
export function Bar(props: IBarProps): JSX.Element {
|
|
return (
|
|
<div className='bar'>
|
|
...
|
|
<Home
|
|
goHome={props.goHome}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
*Faites la même chose pour UI et Editor.*
|
|
|
|
Enfin dans `App.tsx`, créez la fonction permettant de changer `isLoaded` vers `false`:
|
|
|
|
```tsx
|
|
...
|
|
if (isLoaded) {
|
|
return (
|
|
<div>
|
|
<Editor
|
|
root={props.root}
|
|
configuration={editorState.configuration}
|
|
history={editorState.history}
|
|
historyCurrentStep={editorState.historyCurrentStep}
|
|
goHome={() => {
|
|
setLoaded(false);
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
...
|
|
```
|
|
|
|
Félicitation vous avez correctement développé votre premier composant Home !
|
|
|
|
Essayons maintenant de faire le reste dans le prochain chapitre avec [`TailwindCSS`](Tailwind.md) |