DEV Community

lawalalao
lawalalao

Posted on

Vous utilisez mal `useState`

L'une des raisons pour lesquelles nous pouvons utiliser des composants fonctionnels comme composant principal est qu'il peut désormais contenir son propre «état» en utilisant des Hooks tels que useState. Pour cette raison, il est possible d'abandonner complètement les composants basés sur les classes.

Malgré cet avantage donné par Hooks, il est toujours possible de cultiver de mauvaises pratiques en utilisant useState dans nos composants fonctionnels. Nous ne sommes toujours pas à l'abri des pièges potentiels que nous pourrions introduire lors de la construction de nos composants sous forme fonctionnelle.

Comment savoir si vous utilisez useState de manière incorrecte? la suite dans les lignes suivantes.

État mutant au lieu d'utiliser setState fourni par useState.

Tout d'abord, l'état en mutation est un grand non-non dans l'écosystème React en raison du fait qu'il pratique fortement le concept d'immuabilité. Pour montrer comment vous pouvez faire muter l'état sans le savoir, considérez l'extrait de code suivant:

const [MyValue, setMyValue] = useState(0);

MyValue = 55;
Enter fullscreen mode Exit fullscreen mode

Ceci est considéré comme la mutation directe d'un état. Nous violons gravement la règle empirique en manipulant correctement notre état, car il était censé être traité comme immuable à moins que nous n'appelions le deuxième élément du tableau, setMyValue.

Étant donné que la valeur de l'état est «en lecture seule», vous ne pouvez pas la modifier de cette façon. Cela lancera une erreur:

L'exemple suivant peut également vous permettre de muter par erreur un état:

const [myValues, setMyValues] = useState([1,2,3,4,5]);

myValues[2] = 55;
const [myValues, setMyValues] = useState([1,2,3,4,5]);

//map crée un nouveau tableau. Mais il fait toujours référence à l'ancien tableau, donc dans ce cas, nous sommes toujours en train de muter le tableau myValues.
const newValues = myValues.map((item, idx) => {
        if(idx === 2) item = 55;

        return item;
});
Enter fullscreen mode Exit fullscreen mode

Dans cet exemple, vous essayez de muter une valeur d'état, qui est un tableau. Vous pourrez peut-être le faire muter, mais cela n'émettra pas de «re-rendu» dans votre composant, ce qui signifie que la nouvelle valeur ne sera pas affichée dans votre interface utilisateur.

Pour le montrer en temps réel, laissez-moi vous donner un exemple de mutation d'un tableau :

let count = 0;
const App = () => {

  const [stateVal, setStateVal] = React.useState([1,2,3,4,5]);

  const onChangeArrayValues = () => {
    stateVal[count] = "Changed";

    count += 1;

    alert("updated array: " + stateVal);
  }

  return (
    <div>
      <h1>Changing array state values</h1>
      <h2>Array values: {stateVal}</h2>
      {/* <h2>Sum result: {multiplyByThree(5, 5)}</h2> */}


      <button onClick={() => onChangeArrayValues()}>Click to change</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Ainsi, comme nous pouvons le voir dans cet exemple, même si nous avons muté le tableau d'état, il ne reflète pas notre interface utilisateur. React est suffisamment intelligent pour savoir si l'état est défini ou simplement «muté». S'il est muté, il n'émettra pas de "re-rendu" dans ses composants pour refléter la nouvelle valeur d'état dans notre interface utilisateur.

La même chose peut être dite avec l'état basé sur les objets:

const App = () => {

  const [stateVal, setStateVal] = useState({ val1: "Hello world!" });

  return (
    <div>
      <h1 onClick={() => stateVal.val1 = "Mutated value..."}>
                                Test state: {stateVal.val1}
                        </h1>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

https://codepen.io/reciosonny/pen/ExNaagg

Nous pourrons peut-être le faire muter sans que React ne remarque que vous l'avez muté. Le même problème se produira que le dernier exemple avec la mutation d'un tableau: la nouvelle valeur ne sera pas reflétée dans notre interface utilisateur.

Dans cet exemple, l'état doit toujours être défini correctement à l'aide de la fonction setState fournie par useState.

Ce n'est pas uniquement le cas des hooks d'état. En fait, vous pouvez faire la même erreur de gestion des états dans un composant basé sur des classes.

Comment définir l'état?

Une façon de résoudre ce problème est de nous assurer que nous utilisons une approche immuable comme la définition de valeurs d'état à l'aide d'un deuxième élément de useState, comme ceci:

const [myValues, setMyValues] = useState(0);

setMyValues(55);
Enter fullscreen mode Exit fullscreen mode

Il s'agit de la méthode officielle de définition d'une valeur d'état de manière immuable. Nous utilisons le deuxième élément, qui est une fonction pour définir l'état.

Nous pouvons toujours utiliser cette approche pour les états basés sur des objets. Cependant, nous devons toujours observer le concept d'immuabilité lors de la modification d'un tel état. Cet exemple d'extrait de code vous aidera à faire l'affaire:

// En utilisant la méthode Object.assign:
const newState = Object.assign({}, state, {[item.id]: item});

// Ou en utilisant la syntaxe de diffusion ES6:
const newState = { ...oldState, prop1: "modified value" };
Enter fullscreen mode Exit fullscreen mode

Lors de la définition d'un état pour les tableaux, le meilleur moyen est de recréer le tableau que vous vouliez modifier avec ses modifications. C'est l'un des meilleurs moyens que je connaisse pour modifier le tableau:

const [myValues, setMyValues] = useState ([1,2,3,4,5]);

// Copie d'un nouvel ensemble de tableaux à l'aide de la syntaxe de diffusion ES6
const newItems = [... mesValeurs];
newItems [2] = 55; // modification d'un élément de tableau spécifique

setMyValues (newItems); // définit le nouveau tableau avec des valeurs modifiées

Enter fullscreen mode Exit fullscreen mode

Voici à quoi cela ressemblerait en temps réel.

Dans cet exemple d'extrait de code, nous nous assurons effectivement de recréer ce tableau, puis d'appliquer les modifications dans l'élément spécifique que nous voulions modifier. Avec cette méthode, nous faisons savoir à React qu'un état est modifié de manière immuable. Cela déclenchera le «re-rendu» du composant .

Passer useState dans les accessoires (props) de composants enfants pour l'utiliser

Passer useState comme accessoire dans un autre composant est tout à fait possible. Mais cela n’a aucun avantage, car vous pouvez toujours appeler useState en important React en haut de votre code JavaScript et en l’appelant dans tous vos composants.

Voici l'exemple d'extrait de code pour illustrer ceci:

import React, {Component, useState} de 'react';
import {hot} de "react-hot-loader";

const NewComponent = ({useStateFn}) => {

   const [val, setVal] = useStateFn (0); // nous avons utilisé useState à partir des accessoires passés à ce composant

   revenir (
                 <div>

             <h2> Valeur: {val} </h2>
             <br/> <br/>
             <button onClick = {() => setVal (25)}> Modifier la valeur </button>
           </div>
         );
}

const App = () => {

   revenir (
     <div>
       <h1> Bonjour tout le monde! </h1>

                         {/ * Nous avons transmis useState dans le composant enfant pour qu'ils soient consommés * /}
       <NewComponent useStateFn = {useState} />
     </div>
   )
}
Enter fullscreen mode Exit fullscreen mode

C'est une mauvaise pratique, et vous ne devriez jamais utiliser useState comme ça. En outre, cela pourrait potentiellement introduire du code spaghetti (rire) qui pourrait rendre l'application beaucoup plus difficile à corriger. Évitez cela comme la peste.

Ne pas mettre useState en haut du corps du composant ou des fonctions

Selon la documentation officielle de React :

N'appelez pas les Hooks dans des boucles, des conditions ou des fonctions imbriquées. Au lieu de cela, utilisez toujours Hooks au niveau supérieur de votre fonction React

Parce que useState est un hook, nous devons le placer au niveau supérieur de notre composant, donc le placer sur des zones autres que le niveau supérieur peut potentiellement introduire une confusion dans la structure de notre composant.

Au lieu de cela:

const App = () => {

  const onValueChanged = (input) => {
    setVal(input);
  }

  const [val, setVal] = useState(0);

  return (
    <div>
      <h1>Hello world!</h1>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

faire ceci

const App = () => {

  const [val, setVal] = useState(0);


  const onValueChanged = (input) => {
    setVal(input);
  }

  return (
    <div>
      <h1>Hello world!</h1>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

L'utilisation de cette meilleure pratique nous permettra d'éviter les bogues potentiels liés à l'appel d'un état lorsque notre application s'agrandit.

Utilisation de useState dans des composants de classe ou des fonctions JavaScript classiques

Si vous avez lu les règles des hooks dans la documentation officielle de React, elles vous encouragent à ne pas mettre de hooks tels que useState dans la classe ou les fonctions JavaScript classiques. En effet, les hooks ne fonctionnent pas très bien avec ceux-ci, en particulier dans les structures de composants basées sur les classes.

Supposons que vous insistiez toujours sur l'utilisation de useState sur des composants basés sur des classes, comme dans cet exemple:

class App extends Component {

  render() {

    const [inputVal, setInputVal] = useState("");

    return (
      <div>
        <input type="text" onChange={(e) => setInputVal(e.target.value)} />

        <h1>Input value: {inputVal}</h1>
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Voici ce que vous verrez:

Dans ce cas, React vous informera immédiatement qu’il s’agit d’un cas d’utilisation non valide d’utilisation de hooks sur un composant basé sur une classe. Cela signifie que vous ne pouvez pas utiliser des hooks tels que useState dessus.

Il existe d'autres cas d'utilisation subtils mais utilisant la mauvaise implémentation de useState, comme son utilisation dans des expressions de fonction simples. Voici un exemple.

const maFonction = (arg1, arg2, arg3) => {
   const [myStateValue, setMyStateValue] = useState ("");

   // faire la logique ici ...
}
Enter fullscreen mode Exit fullscreen mode

Si vous vous souvenez, les règles des crochets disent le long de ces lignes:

N'appelez pas les Hooks à partir des fonctions JavaScript classiques

Il s'agit alors d'une utilisation non valide de useState, sauf si nous utilisons cette fonction comme un hook personnalisé . Un hook personnalisé n'est également qu'une fonction JavaScript, mais cette fois, il a son propre cycle de vie, comme l'ajout de useEffect pour suivre les changements de son état.

Ainsi, au lieu d'une fonction normale, vous faites un meilleur usage de useState en construisant un hook personnalisé:

function useUpdateUserAccount(updatedUserAccount) {
  const [userState, setUserState] = useState(null);

  useEffect(() => {
    function handleStatusChange(user) {
                        setUserState(user);
    }

    UserAPI.updateAccount(updatedUserAccount, handleUserChange);
    return () => {

    };
  }, []);

  return userState;
}
Enter fullscreen mode Exit fullscreen mode

Dans ce scénario, nous avons maintenant un cycle de vie complet d'une fonction, grâce à des hooks supplémentaires comme useEffect. Cela peut maintenant être utilisé comme un hook personnalisé entre différents composants que vous pouvez avoir. Cela pourrait même être un début pour créer votre propre store plutôt que de compter sur Redux pour des cas d'utilisation plus simples.

Et n'oubliez pas d'ajouter une utilisation comme préfixe au nom de votre fonction afin de suivre les règles des hooks!

Transmission de la fonction setState aux composants enfants pour définir l'état parent

C'est fondamentalement la même mauvaise pratique que de passer useState dans le composant enfant. Cette fois, nous transmettons uniquement la fonction setState pour définir l'état de notre composant parent.

C'est possible de le faire. Mais c'est une mauvaise pratique et peut potentiellement introduire des effets secondaires involontaires au fur et à mesure que l'application évolue.

Il n’est pas non plus facile à lire et peut prêter à confusion, en particulier lorsque les composants s’intègrent dans des cas d’utilisation compliqués.

Donc, au lieu de faire ça:

const NewComponent = ({ setValFn }) => {

  return (<div>
    <button onClick={() => setValFn(25)}>Change value</button>
  </div>);
}

const App = () => {
  const [val, setVal] = useState(0);

  return (
    <div>
      <h2>Value: {val}</h2>
      <br/><br/>

      <NewComponent setValFn={setVal} />      
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

faites plutot ceci

const NewComponent = ({ onChangeValue }) => {

  return (<div>
    <button onClick={() => onChangeValue(25)}>Change value</button>
  </div>);
}

const App = () => {

  const [val, setVal] = useState(0);

  const onValueChanged = (input) => {
    setVal(input);
  }

  return (
    <div>
      <h2>Value: {val}</h2>
      <br/><br/>

      <NewComponent onChangeValue={onValueChanged} />      
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Vous faites essentiellement la même chose qu'avant, où nous avons l'intention de définir l'état d'un composant parent. Seulement cette fois, la dernière approche émet des événements du composant enfant au composant parent. Ensuite, vous laissez le composant parent faire le réglage de l'état.

Ne pas utiliser de tableau de destructure pour utiliser useState

Vous n'en avez peut-être pas conscience, mais vous pouvez utiliser useState de cette façon:

const count = useState[0];
const setCount = useState[1];
Enter fullscreen mode Exit fullscreen mode

En effet, les hooks comme useState sont en fait un tableau qui renvoie les implémentations suivantes dans chaque élément:

  1. Valeur d'état initialisée: la valeur que vous avez passée dans sa fonction. Cela peut être une valeur, une chaîne, un objet, un tableau, etc.)

  2. Fonction pour définir votre état

Les documents officiels React préfèrent que vous utilisiez plutôt la destructuration de tableau, car elle est plus propre et plus facile à lire chaque fois que vous déclarez un hook d'état. De plus, ils utilisent la déstructuration des tableaux, ce qui convient à leur cas d'utilisation consistant à déclarer l'état avec élégance.

Cela ne signifie pas que vous utilisez useState de manière incorrecte, mais que le fait de ne pas utiliser la destructure ES6 vous enlève le sucre syntaxique de la façon dont useState est censé être déclaré, sans oublier que vous avez également ajouté une ligne de code supplémentaire pour les déclarer tous les deux.

Nous pouvons voir la documentation officielle de React sur la façon dont ils préfèrent que useState soit appelé à l'aide de la destructuration de tableau ES6, comme ceci:

const [count, setCount] = useState(0); //Déstructuration du tableau
Enter fullscreen mode Exit fullscreen mode

S'appuyer uniquement sur useState pour gérer l'état dans les applications à plus grande échelle

Il ne devrait y avoir aucun problème en s'appuyant sur useState pour des cas isolés dans la logique des composants et des cas d'utilisation simples. Mais si l'ensemble de notre application se compose uniquement de useState pour gérer l'état, nous pourrions avoir un problème à long terme en raison de la complexité et d'un cas d'utilisation impliquant plus de deux composants.

Les cas d'utilisation qui nécessitent plus que la simple utilisation de useState incluent:

  1. Si un état est nécessaire dans quelques composants

  2. Si une application évolue

  3. Si nous avons besoin d'un store global

Si nous nous appuyons uniquement sur useState et passons l'état aux seuls accessoires de composants, nous pourrions nous retrouver avec le problème «Prop Drilling». De plus, si nous allons ajouter une logique liée à l'authentification et à la sécurité (ce qui sera nécessaire pour exiger le maintien de la session utilisateur dans un état à un moment donné), alors nous aurons besoin d'une meilleure gestion de l'état pour stocker et utiliser correctement la logique sur différentes pages contenant différents composants.

Les bibliothèques de gestion d'état telles que Redux ou même l'API context offrent un avantage significatif sur les applications à plus grande échelle, car elles peuvent partager l'état entre différents composants. Ils viennent souvent avec des outils de navigateur pour suivre l'état qui est passé dans quelques composants.

Cela facilite le partage et la vérification de la logique grâce aux outils sophistiqués activés par l'utilisation de solutions de gestion d'état telles que Redux.

Donc, pour les applications et l'état à plus grande échelle, qu'est-ce qui est nécessaire dans plusieurs composants?

Optez pour une gestion d'état telle que Redux. Mais il existe quelques solutions de gestion d'état parmi lesquelles vous pouvez choisir, allant de Flux ou simplement de l'API context.

Que diriez-vous d'utiliser des hooks personnalisés? Possible. Mais pour être sûr, il est préférable de s'appuyer sur Redux pour des cas d'utilisation plus importants.

Conclusion

React est désormais flexible grâce aux Hooks. Plus particulièrement, c'est grâce à useState que nous n'avons plus besoin de s'appuyer sur des composants basés sur des classes uniquement pour construire nos composants d'interface utilisateur.

Visibilité totale sur les applications de production React

Le débogage des applications React peut être difficile, en particulier lorsque les utilisateurs rencontrent des problèmes difficiles à reproduire. Si vous souhaitez surveiller et suivre l'état de Redux, faire apparaître automatiquement les erreurs JavaScript et suivre les requêtes réseau lentes et le temps de chargement des composants, essayez LogRocket .

Discussion (0)