logoWebatoire

Mémos React

Bienvenue dans la section Mémos React.Js !
vous trouverez ici toutes mes notes qui me sont utilisées pour des projets sous ce Framework (de la création d'un projet à l'utilisation d'APIs).

Ces notes sont issues dans la grande majortié de mon auto-formation en suivant le tutoriel de Grafikart, tutoriel que vous pouvez retrouver ici.

Création d'un projet React

Première étape, créer un dossier pour votre projet. Ensuite, placez vous dedans et installez vite.JS qui va nous permettre d'écrire en JSX. npm create vite@latest ./ Sélectionnez React avec les flèches directionnelles puis validez avec la touche Entrer.
Sélectionnez Javascript avec les flèches directionnelles puis validez avec la touche Entrer. npm i
npm run dev
Et voilà, votre serveur est lancé sur l'adresse 127.0.0.1:5173.

Par défaut, ila généré un petit projet de compteur donc on peut supprimer les dossiers et fichiers suivants :

  • src/assets
  • src/App.css
  • src/index.css

(on va avoir des erreurs dans le terminal mais c'est normal, on essaie de faire appel à des assets qu'on aura supprimé.)

On peut modifier App.jsx pour avoir seulement dans le fichier :


function App() {
    return 'coucou';
}

export default App


    

Enfin, dans Main.jsx, on peut supprimer l'import du css.

Bonus : installer le plugin chrome React Developer Tools ici pour avoir deux nouveaux onglets dans l'inspection de votre navigateur : Components et profiler (qui s'ajoutent au bout de la ligne avec Elements, Source, Network...)

Comprendre la structure de React

React est basé sur des composants. Ce sont des fonctions JSX qui vont avoir pour rôle de retourner un string, un nombre, de l'HTML ou une valeur null.
Par exemple on peut avoir un composant pour afficher un formulaire, pour afficher la page home...

Le principe est de structurer ces pages en plusieurs composants afin de pouvoir les réutiliser à différents moments.

Zoom sur les return

Quand on retourne de l'HTML il faut savoir deux trois choses :

  1. Les noms des attributs peuvent changer par rapport à une écrire HTMl classique :
    • Par exemple, class devient className
    • pour tout autre attributs en plusieurs mots (sauf les data-... ou les aria-...) sont en un seul mot mot1Mot2 (ex: autoComplete)
  2. Un composant (= une fonction) ne peut retourner qu'un élément racine :
    • return <h1> coucou <h2> <div>toto</div> Ne fontionne pas car h1 et div ne sont pas contenu dans un même élément
    • return <div> <h1> coucou </h1> <p> blabla </p> </div> Fonctionne car h1 et p sont contenu dans un élément parent (div)

Si on ne veut pas de cette div parente. On n'utilise pas div mais <Fragment> </Fragment> qui s'importe de react. (on peut aussi simplement l'écrire <> </>). Avec cette écriture, si on inspect le code depuis le navigateur, on verra afficher directement <h1> coucou </h1> <p> blabla </p> sans l'élément parent.

Si html tolère les balises non fermées, ce n'est pas le cas de react. Il faut absolument ne pas oublier de les fermer (input, img, br...).

Maintenant, qu'on a évoqué les différents types pouvant être retourné, il est temps d'évoquer le fait qu'on peut combiner ces types dans le sens où on peut utiliser des variables JavaScript dans un return qui retourne de l'HTML. Voici un exemple :

 
const title = 'coucou';
const texte = 1+5;
function App(){
    return <h1> {title} </h1><p>{texte}</p>
}

OUTPUT =>
Coucou
6

    

là aussi, la constante JS peut être un string, null, undefined, integer. MAIS pas un objet (genre tableau)

dans cette variable, il vaut mieux ne pas retourner de nouveaux éléments comme exemple : const title = "Bonjour Les gens " Car il va afficher sur l'écran :
Bonjour <strong>Les gens </strong>     Au lieu d'afficher     Bonjour Les gens .

La balise style doit TOUJOURS contenir un tableau objet. Par exemple : <h1 id="title" style={{color: "white", backgroundColor: "black"}}> {title} </h1>

Maintenant qu'on a vu l'utilisation des variables dans le return, on va voir qu'on peut utiliser ces variables dans le return pour jouer avec des booleans :


const bool = true;

function App() {
  return {bool && <h1> coucou </h1>}
}

OUTPUT =>
coucou


    

const bool = false;

function App() {
  return {bool && <h1> coucou </h1>}
}

OUTPUT =>

(oui, il ne retourne rien)


    

const bool = true;

function App() {
  return return {bool ? <h1> coucou </h1> : <h1> poulpe </h1>}
}

OUTPUT =>
coucou


    

const bool = false;

function App() {
  return return {bool ? <h1> coucou </h1> : <h1> poulpe </h1>}
}

OUTPUT =>
poulpe


    

Si on peut utiliser des variables JS dans le return, on peut également utiliser des fonctions JS...


    const todos = [
    'apprendre React',
    'faire mon portfolio',
    'finir elemion'
];


function App() {
  return <Fragment>
    <h1>{title}</h1>
    <ul>
      {todos.map(todo => (<key={todo}>{todo}</li>))}
    </ul>
  </Fragment>;
}

    

Quand on fait des map, il faut que chaque éléments créés aient un attribut "key" différent. Si on ne le défini pas nous même, il ne sera pas différent et donc on aura une erreur dans la console.

Zoom sur les évènements

Les évènements sont généralements gérés par des fonctions. Par exemple, pour un onclick on aura :


function App() {
  const handleClick = () => {
    alert("On a cliqué sur le titre");
  }
  return <Fragment>
            <h1 id="title" onClick={handleClick}>{title}</h1>
            <p> Lorem </p>
         </Fragment>;
}


    

Quand on récupère un évènement, par exemple dans le code :


handleClick = (e) => {
    alert("On a cliqué sur le titre");
}

    

e ne va pas être un évènement DOM classique comme on le retrouve en js pure. C'est un SyntheticBaseEvent. Marche tout pareil que le DOM sauf qu'on a des petites fonctions en plus et que tous les évents ont une écriture généralisée.
Si on a besoin l'event originale (DOM), on peut y accéder avec e.nativeEvent

Zoom sur les multicomposants

Comme dit dans la présentation, le principe de React est de séparer notre code en plusieurs composants pour les utiliser à plusieurs endroit. Par conséquent, un composant peut faire appel à d'autres composants.

Par exemple, on peut faire ceci :


function App() {
  return <Fragment>
    <Title color="white" bgColor="black"/>
    <Paragraphe color="blue"> Je suis le contenu </Paragraphe>
    <TodoList/>
  </Fragment>;
}

function Title({color,bgColor}) {
  return <h1 style={{color: color, backgroundColor: bgColor}}>{title}</h1>;
}

function Paragraphe({color, children}){
  return <p style={{color: color}}>{children}</p> // dans notre cas children = "Je suis le contenu"
}

function TodoList() {
  return<ul>
    {todos.map(todo => (<li key={todo}>{todo}</li>))}
  </ul>;
}

export default App


    

On peut y déclarer des attributs pour juste récupérer un boolean :


function App() {
  return <Fragment>
    <Title color="white" bgColor="black" hidden/>
  </Fragment>;
}

function Title({color,bgColor, hidden}) {
    console.log(hidden); // OUTPUT : true
  return ...;
}


    

function App() {
  return <Fragment>
    <Title color="white" bgColor="black"/>
  </Fragment>;
}

function Title({color,bgColor, hidden}) {
    console.log(hidden); // OUTPUT : undefined
  return ...;
}


    

function Paragraphe({color, children, hidden}){
    if (hidden){
        return null;
    }
    else{
        return <p style={{color: color}}>{children}</p> // dans notre cas children = "Je suis le contenu"
    }
}

    

Là on a vu qu'on pouvait ajouter des attributs à notre composant qu'on donne comme attributs dans la balise de l'élément et qui sont récupérés dans notre composants par les paramètres de la fonction qui le défini. Mais il existe des moyens pour optimiser automatiser les attributs :

1ère automatisation : Au lieu de devoir écrire les attributs un par un dans un élément, on peut utiliser l'écriture ...props de la manière suivant :


function Paragraphe({color, children, hidden}){
    if (hidden){
        return null;
    }
    const props = {
        id: 'monId',
        className: 'maClass',
        style: {color: color}
    }
    return <p {...props}>{children}</p>
}


    

Ici notre élément p va recevoir les attributs définis dans notre constant props

2ème automatisation : On peut aussi récupérer automatiquement les attributs données lors d'un appel d'un composant :



function App(){
    return <Title color="white" bgColor="black" data-truc="truc" className="truc"/>
}

function Title({color,bgColor, ...props}) { // cf (1)
  return <h1 style={{color: color, backgroundColor: bgColor}} {...props}> //cf (2)
            {title}
        </h1>;
}


    

(1) Mettre ...props ici permet de dire "tu récupères tous les autres attributs".
(2) Mettre ...props ici permet de dire "tu lui redonne tous les attributs que tu as récupéré dans le (1).

Cette automatisation fonctionne aussi sur l'attribut children (qui correspond au contenu de ce que l'on met dans notre élément) :


function App(){
    return <Paragraphe id="para" data-demo="demo"> Je suis le contenu </Paragraphe>
}

function Paragraphe({hidden, ...props}){
    if (hidden){
        return null;
    }
    return <p {...props}></p>
}

// va retourner l'élément HTML : 
<p id="para" data-demo="demo"> Je suis le contenu </p>

Comment choisir le composant à afficher ?

Quand l'utilisateur va aller sur votre site, le premier composant qui va être afficher est le composant App. Autrement dit, la fonction App() que l'on voit depuis le début dans ces notes. Ce choix de composant se fait dans le fichier main.jsx de votre projet.

Pour les redirections, tout se fait par des actions sur des liens/boutons qui vont demander de render de nouveaux composants. On peut notamment utiliser une bibliothèque de routing, bien pratique pour séparer votre projet dans un semblant de multi-page (Oui, malgré le routing, on reste sur du single-page)
On verra plus tard des exemple pour tous ces changements de render.

Déploiement de votre projet React

Une fois votre projet prêt, il est temps de le mettre sur votre serveur !
La première étape est de créer un build de production de votre projet.

npm run build

À la racine ne votre projet, il sera généré un dossier dist. qui contiendra le code compilé (src et public).
Créez sur votre serveur un dossier pour contenir votre projet/var/www/html/mon_damaine. sudo mkdir /var/www/html/mon_damaine
# si vous voulez gérer le contenu du dossier via SFTP :
sudo chown -R ubuntu:ubuntu /var/www/html/mon_damaine
Puis, dans ce dossier, placez y le contenu du dossier dist, le dossier backend (si vous en avez) et votre fichier package.json. Puis, installez les dépendances avec : sudo npm install Si vous avez un dossier backend qui contient des fichiers .js pour un serveur Node.js/Express que vous lancez sur votre local avec node ficher_backend.js, vous devez également les exécuter sur votre serveur. Pour qu'ils tournent encore même après avoir fermé le terminal, on va utiliser pm2 : sudo npm install -g pm2
sudo pm2 ficher_backend.js

Une fois le projet en place, on va s'occuper du fichier de configuration nginx (voir la section déploiement du mémo symfony pour plus de détails).


server {
    server_name mon_damaine;

    root /var/www/html/mon_damaine;
    index index.html index.htm index.nginx-debian.html;

    location / {
         try_files $uri $uri/ /index.html;
    }

}


    

Enfin, on fait les démarches pour avoir la certification ssl (voir la section déploiement du mémo symfony pour plus de détails). Et hop, on est tout bon !

Les Hooks

Le Hook useState

Tous les hooks ont une nomenclature qui commence par use.

Chaque hook est importable depuis React.

Le hook useState permet de demander un changement d'état du composant qui est render. C'est-à-dire quand lorsque ce useState est appelé, tout le composant va être ré-exécuté en changeant la valeur de variable qui est modifiée par l'appel du useState, valeur allouée dans l'espace mémoire du composant. Même si tout le composant va être ré-exécuté, seul les élement concernés par le useState vont être re-render.

Prenons l'exemple d'un compteur :


const [count, setCount] = useState(0); // valeur initiale de count = 0
const handleClick = ()=>{
    setCount(count + 1)
}

return <Fragment>
    <Title color="white" bgColor="black">{title}</Title>
    <button onClick={handleClick}> Incrémenter </button>
    <p> valeur du compteur : {count}</p>
</Fragment>


    

ici, seul, la valeur de count va être re-render car c'est sa valeur qui est associé au useState

Si on aurais juste fait une variable count qu'on aurait incrémenté dans handleClick n'aurait rien changé sur la page.

quand on fait un console.log, on le voit deux fois dans la console. C'est que React exécute les composantes deux fois pour s'assurer qu'il n'y a pas d'erreurs.

Attention à la portée des variables gérées par hook. Tant que la page est pas render (se fait une fois atteint la fin du composant), la variable ne change pas autrement dit :


const [count, setCount] = useState(0);
const handleClick = () => {
        setCount(count + 1);
	setCount(count + 1);
	setCount(count + 1);
    }


    

ne va que incrémenter de 1 car pour la première utilisation, cela revient à faire :


const handleClick = ()=>{
        setCount(0 + 1);
	setCount(0 + 1);
	setCount(0 + 1);
    }


    

pour incrémenter la valeur de l'état de count (et donc incrémenter de trois en trois avec des +1, il faut passer par des fonnctions. Par exemple :


const handleClick = ()=>{
        setCount((count) => count + 1);
        setCount((c) => c + 1);
        setCount(increment);
    }

    const increment = (count) =>{
        return count + 1;
    }


    

Cette méthode fonctionne aussi avec des objets sauf qu'il ne faut pas faire de mutations (par exemple personne.age ++; setPersonne(personne)). Il faut obligatoirement passer par un nouvel objet :


const handleClick = ()=>{
        setPersonne({...personne, age: personne.age +1 })
    }


    

autrement dit, on demande "récupère toutes les informations de personne et change l'âge"

Par conséquence,
Pour retirer un élément dans une liste, on utilisera la fonction filter de JS.
Et pour ajouter/pousser un nouvel élement, on utilisera la syntaxe suivante :


const handleClick = ()=>{
        [...maListe, "nouvelle tache"]
    }

    

le nombre de hooks doit toujours être égal à chaque render. C'est-à-dire qu'on doit retrouver les mêmes hooks à chaque render.
C'est pourquoi, on les déclare (const [a, setA] = useHook(i)) toujours hors boucle et avant n'importe quel return. (genre toujours tout au début)
Par exemple, on va planter la page si on met les hooks dans des if. if (hook1.age < 19){hook2}

Le Hook useEffect

Le hook useEffect permet de faire des effets de bords. C'est à dire que changer un élément entrainera la modification d'un autre.
Par exemple, changer l'option sélectionnée dans un select pourra entraîner la modification du nom de page.

On structure un hook useEffect de la manière suivante : useEffect( () => {
//TODO : fonction exécuté quand il y a un changement }, [tableau des éléments où on regarde si leur valeur change entre deux rendus]);

Si on prend notre exemple de changement de nom de page, on aurait :


function EditTitle(){
	const [title, setTitle] = useState('');
	const [oeuvre, setOeuvre] = useState('');

	useEffect(() => {
		document.title = title
	},[title]);

	return mon_input
}


    

En utilisant le useEffect, la partie useEffect va être exécutée en fonction de ce que contient le tableau fourni en paramètre. Par exemple si le tableau est vide, alors le code du useEffect va être trigger uniquement lors du premier render. On les utilisera donc pour nos addEventListener. Si le tableau n'est pas vide, le useEffect sera trigger a chaque changement d'une des variables listées dans le tableau.

Si pour une raison vous arrêtez de render un bouton qui a un addEventListener associé, ce dernier sera toujours actif. Il faudra donc mettre fin au useEffect pour nettoyer les listeners qu'il contient. Voici un exemple qui ajoute un listener et qui prévoie sa suppression :


useEffect(() => {
    const handler = () => {
        setY(window.scrollY)
    }
    window.addEventListener('scroll', handler)
    return () => {
        window.removeEventListener('scroll', handler) //delete l'event quand on affiche pas la composante
    }
}, []);


    

Le Hook useMemo

Le hook useMemo permet de mémoriser une valeur et de la modifier uniquement quand une de ses dépenences change. Son trigger fonctionne extactement comme un useEffect.

Il ne sert a rien d'utiliser un useMemo sur une opération qui prend peu de temps car on prendre de la place de stockage en mémoire ne gagner que quelques millisecondes...

Par exemple, on peut utiliser useMemo pour vérifier la force d'un mot de passe :


  const [password, setPassword] = useState('password')

    const hashedPassword = useMemo(() => {
        return slowHashingMethod(password)
    }, [password])


    

Le Hook useId

Le hook useId permet de générer un id propre au composant comme par exemple l'id ":r1:". On va l'utiliser quand un composant est uniquement un input ce qui permet de lui lier simplement son libellé :


const id= useId()

return <div>
	<label htmlFor={id}></lable>
        <input id={id} .../>
	</div>


    

Le Hook useRef

Le hook useRef permet d'interagir avec l'HTML et de conserver des valeurs sans changer l'état. Par exemple, sauvegarder la référence vers un élément html :


const ref = useRef(null)
console.log(ref) // OUTPUT Objet ou la clé currente est associée à notre div
return <div ref={ref}> petit texte </div>


    

et donc si on veut print la hauteur de la div au premier render, on peut faire :


useEffect( () => {
	console.log(ref.current.offsetHeight);
}, []);


    

La référence ne change jamais entre deux render donc ça ne sert à rien de mettre ref dans le tableau du useEffect.

Les useRef peuvent être utilisés pour contourner le problème des dépendances des useEffect.

Par exemple, pour afficher le contenu d'un input toutes les secondes. Si on n'utilise pas de useRef, on va générer un timer pour chaque input, si on tape 10 caractères, il faudra attendre 9secondes avant d'avoir al version avec le 10ème caractère. Problème que l'on peut résoudre avec les useRef :


const texteRef = useRef(null)
const [texte, setTexte] = useState('')
texteRef.current = texte

useEffect( () =>
	const timer = setInterval( () => {console.log(texteRef.current)},1000)
	return () => {clearInterval(timer)}
}, []);


    

Pour passer un useRef à un enfant, il faut l'appeler différemment que ref dans la déclaration de l'enfant sinon react ne va pas aimer :


App(){
	inputRef = useRef()
}

export function Input( {placeholder, value, onChange, label, inputRef} )
{
	// là on peut utiliser inputRef
}

    
Loading…
Loading the web debug toolbar…
Attempt #