svg-layout-designer-react/docs/DevDocs/Pages/PremierComposantReact.md
2022-09-23 18:10:30 +02:00

8.1 KiB

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

// 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 :

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}.

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:

// Bar.tsx
import { Home } from '../Home/Home';
...

Puis ajoutons-le dans la vue :

// 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.

// 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 :

// 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.

// App.tsx
...
      <MainMenu
        newEditor={() => NewEditor(
          setEditorState, setLoaded
        )}
        loadEditor={(files: FileList | null) => LoadEditor(
          files,
          setEditorState,
          setLoaded
        )}
      />
...

Cette fonction newEditor appelle donc une autre fonction NewEditor.

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 :

// 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:

// 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 :

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 :

// 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:

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