Loupe

Les hooks dans react - Présentation

Introduction

Aujourd’hui, nous allons introduire une nouvelle feature mise en place par React dans la version 16.8 du framework et qui devrait prendre beaucoup d’importance dans les mois à venir. A noter que c’est une fonctionnalité native au framework et qu’aucune dépendance supplémentaire n'est nécessaire.

Comme vous le savez (ou peut-être pas), il existe deux façons de créer un composant dans React. La première façon consiste à utiliser une Class :

class ClassComponent extends React.Component {
    render() {<div></div>}
}

La deuxième version, appelée “stateless Component”, n’est autre qu’une fonction qui retourne du JSX :

const StatelessComponent = (props) => {
    return <div></div>
}

L’avantage de la 2ème version par rapport à la 1ère (en plus d’être plus lisible et donc plus facile à comprendre) est son optimisation : elle ne nécessite pas d’instanciation de classe et prend donc moins de place en mémoire. En revanche, elle permet moins de choses, sa structure limitant l’usage de logique complexe, et on se passe de beaucoup de notions relatives à React comme le State ! d’où son nom de “stateless component”. On peut l'associer à une pure function javascript. C’est pour cela que l’usage majeur des stateless components était majoritairement réserve à des petits bouts de code réutilisables tels que des boutons, des containers, un formatage de données, etc… mais ça, c’était avant !

Ce que les hooks changent

L’arrivé des hooks renverse ce que l’on vient de voir précédemment et apporte une vision nouvelle à React.

Pour l’instant expérimentaux, les hooks ont pour ambition de se passer des “class Components” en donnant aux “function Components” (nouveau nom pour les “stateless Components”, vous verrez pourquoi) la plupart des fonctionnalités intéressantes de React normalement réservées uniquement aux “class Components”.

useState

L’un des hooks les plus utiles est useState. Ce hook nous permet d’avoir un state dans nos function components et nous débloque donc beaucoup plus de possibilités !

Voici comment l"utiliser :

const MyFirstFunctionComponent = (props) => {
     let [name, setName] = useState('');
        
        return (
            <input type="text" onChange={(event) => setName(event.target.value)}></input>
            <span>Bonjour, je m'appelle {name}</span>
        )
}

Et c’est tout, rien de plus simple !

On commence tout d’abord par appeler la fonction useState() qui nous permet de créer une nouvelle variable de state. Cette fonction nous retourne un tableau de 2 éléments que l’on assigne à des variables grâce à la syntaxe “destructuring assignment” arrivée avec ES6. La première variable retournée (ici name) correspond à notre nouvelle variable de state créée ; la deuxième variable retournée (ici setName) correspond elle, à la fonction à utiliser pour mettre à jour name. Cette fonction prend en paramètre la nouvelle valeur que l’on souhaite assigner à notre variable. Pour simplifier, c’est simplement un setState, mais juste sur le scope de notre variable.

A noter que lors de l’appel à useState, la valeur passé en paramètre est la valeur initiale que l’on souhaite donner à notre variable.

useEffect

Le deuxième hook important est useEffect. Celui-là nous permet d’injecter dans nos function components la notion de lifeCycle hook utilisée dans tous les frameworks front-end récents. En React, il y en a plusieurs, on les connait sous le nom de ComponentWillMount, ComponenentWillReceiveProps, ComponentWillUpdate, etc…
On se sert principalement de ces fonctions pour récupérer des données (call Api), mettre à jour des variables ou encore faire des modifications sur le DOM.

useEffect nous permet de combiner 3 lifecycle hooks de React, componentDidMount, componentDidUpdate et componentWillUnmount.

Prenons maintenant un exemple, disons que l’on souhaite mettre à jour le titre de notre document à chaque fois qu’une variable est mise à jour.
Avec React Class, le changement de state dû à un changement de valeur d’une variable aurait déclenché un nouveau rendu, il nous restait simplement à mettre notre code dans un componentDidUpdate et le tour était joué !

Avec les hooks, c’est la même chose, il nous suffit simplement de remplacer notre componentDidUpdate par notre fameux useEffect !
On a donc :

const App = (props) => {
        const [items, setItems] = useState<Array<CartItem>([]);
        
        function addItem(item: CartItem) {
            setItems(items.concat(item));
        }
        
        function deleteItem(item: CartItem) {
            setItems(items.filter((i: CartItem) => i.id !== item.id));
        }
        
        useEffect(() => {
            document.title = `${items.length} article(s) dans votre panier`;
        });
        
        return (
            <div className="App">
                <ShoppingCart items={items} deleteItem={deleteItem} />
                <Shop addItem={addItem} />
            </div>
            )
    }

Tada ! A noter que si vous souhaitez avoir un effet “cleanup” (vous désinscrire d’une subscription, terminer une session, etc…) dans vos composants lorsque celui-ci “unmount”, il vous sera possible de le faire dans votre useEffect en retournant une fonction.

Comme un exemple vaut plus que mille mots, voici comment mettre en place un cleanup :

useEffect(() => {
     //Imaginons qu'apres chaque rendu on souhaite afficher une photo de chat d'une API
     catApi.subscribeToApi(myUserId);
     document.getElementById('catImg').src = catApi.getRandomCuteImg();
        return () => { catApi.unsubscribeToApi(myUserId) };
});

A noter cependant que les effects étant run après chaque rendu, le cleanup de l’effet précédent sera également exécuté lors d’un nouveau rendu !

Si vous souhaitez limiter l’exécution d’effect pour que ça ne soit pas le cas après chaque rendu, vous pouvez passer en deuxième paramètre un tableau de valeurs qui sera comparé entre chaque rendu. Si une des valeurs du tableau est différente, l’effect sera exécuté.

Exemple :

useEffect(() => {
     //Affiche un carré après chaque rendu mais n'est executé que lorsque la couleur change
     document.getElementById('mySquare').style.backgroundColor = props.color;
}, [props.color]);

Ici, lorsque la props de couleur changera, le carré sera mis à jour, ce qui évitera les mises à jour du DOM inutiles.

Imaginons maintenant que l’on souhaite faire disparaître le carré lorsque le composant unmount et seulement lorsque le composant unMount.

useEffect(() => {
         document.getElementById('mySquare').style.backgroundColor = props.color;
}, [props.color]);

useEffet(() => {
     // Executé seulement au mount du composant (ComponentDidMount)
        document.getElementById('mySquare').style.display = "block";
        
        return () => {
            // Executé seulement au unMount du composant (ComponentWillUnmount)
            document.getElementById('mySquare').style.display = "none";
        };
}, []);

La technique ici est d’utiliser un tableau vide ; la valeur du tableau ne changeant jamais, l’effect sera exécuté une fois au montage et son cleanup une fois lors de l’unMount.

Les autres hooks

Il existe d’autres Hooks fournis par React, parmi eux :

useContext

Ce hook permet l’implementation des contexts React avec les Hooks. Le hook prend en paramètre un object Context precedemment créé avec
React.createContext() et retourne la valeur du context en question. A noter qu’un composant utilisant useContext déclenchera un nouveau rendu lorsque la valeur du context changera.

import { NameContext } from './nameContext.js';

const TestContextComponent = (props) => {
        let name = useContext(NameContext);
        return <span>My name is {}</span>
}

useMemo

Ce hook permet de mémoriser une valeur afin d’améliorer les performances de notre composant. Ce hook prend en premier paramètre la fonction de création de la valeur et en deuxième argument un tableau de valeurs. La fonction sera exécutée si et seulement si au moins une valeur du tableau change. Cela permet empêcher les calculs inutiles et coûteux entre chaque rendu.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

 

... Et il y en a plein d’autres que je vous laisse découvrir dans la doc ! :)

Créer ses propres Hooks (custom Hooks)

Un hook étant simplement une fonction Javascript avec un nom commençant par use et pouvant utiliser d’autres hooks, il est très facile de créer ses propres hook.

Imaginons que l’on souhaite créer un hook qui nous retourne l’url d’une image en fonction d’un titre ou d’un id :

export default function useImgUrl(id) {
     const [url, setUrl] = useState('');
        
        useEffect(() => {
            setUrl('http://www.mon-incroyable-banque-d-image/' + id);
        });

        return url;
}

On repassera sur l’utilité réelle de ce hook… Cependant le principe est là et on peut ainsi s’en servir comme d’un hook classique :

import useImgUrl from './mon_incroyable_hook.js';

const sampleComponent = (props) => {
     const [imgToken, setImgToken] = useState('');
     let currentImgUrl = useImgUrl(imgToken);
        
        function changeImg() {
            setImgToken(document.getElementById('myInput').value);
        }   
        
        return (
            <div>
                <img src={currentImgUrl} />
                <input id="myInput" type="text" />
                <button onClick={() => changeImg()}>Envoyer</button>
            </div>
        );
}

Et voilà ! La “magie” ici, est que la modification d’imgToken déclenchant un nouveau rendu, currentImgUrl sera automatiquement mis à jour sans avoir d’actions supplémentaires à faire ! :)

En définitif, l’avantage de pouvoir créer ses propres Hooks est de nous permettre d’avoir de la logique stateful externalisable, réutilisable beaucoup plus simplement et en totale abstraction par rapport à des class Component standards !

La conclusion

On peut dire que les hooks sont une petite révolution à leur niveau dans le monde de React. Ils permettent de simplifier le code et de résoudre pas mal de soucis inhérents à React en plus d’avoir un certain impact niveau optimisation sur nos projets.

Si vous souhaitez en savoir plus, je ne peux que vous conseiller d’aller jeter un coup d’oeil à la documentation qui explique très bien la vision de la team React (non, les class Component ne disparaîtront pas ;) ) ainsi que d’approfondir et d’apporter pleins de choses pertinentes sur ce que je viens de vous parler aujourd’hui !

Happy Coding ! :-)

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus