DEV Community

Traky Richard BLM
Traky Richard BLM

Posted on

Comprendre certaines notions des Hooks UseEffect UseCallBack et useMemo.

React hook UseEffect
Cela fait 2 ans j'utilise #React dans mes projets, l'utilisation de ces Hooks deviennent compliqués à comprendre dans certains cas d'utilisations(comme par exemple sur un projet plus ambitieux). Dans cet article je vais vous présenter certains erreurs qu'une mauvaise utilisation de ces hooks peut vous causer et ensuite nous verrons les bonnes pratiques pour y échapper.

1. Construire vos functions avec un seul UseEffect

Imaginons que vous ayez un composant fonctionnel de post d'article comme celui-ci

    const [comments, setComments] =
    useState<QueryDocumentSnapshot<DocumentData>[]>();
  const [likes, setLikes] = useState<QueryDocumentSnapshot<DocumentData>[]>();
  const [posts, setPosts] = useState<QueryDocumentSnapshot<DocumentData>[]>();
  const [user, setUser] = useState<DocumentSnapshot<DocumentData>>();
  const [hasLiked, setHasLiked] = useState(false);

// query to get all comments
  const getAllComments = () =>
    onSnapshot(
      query(collection(firestore, `posts/${postId}/comments`)),
      (snapshot) => setComments(snapshot.docs)
    );
    const getCurrentUser = async () => {
    // query to get current user informations
    const currentUser = await getDoc(doc(firestore, "users", userId));
    setUser(currentUser);
  };
  const getAllLikes = () => {
    // query to get all likes
    onSnapshot(
      query(collection(firestore, `posts/${postId}/likes`)),
      (snapshot) => setLikes(snapshot.docs)
    );
  };

// user like or not the current post
  const setUserHasLiked = () => {
    likes &&
      setHasLiked(
        likes.findIndex((like) => like.id === user?.data()?.uid) !== -1
      );
  };
  useEffect(() => {
    getCurrentUser();
    getAllComments();
    getAllLikes();
    setUserHasLiked();
  }, [firestore, postId, likes]);
Enter fullscreen mode Exit fullscreen mode

Dans ce composant fonctionnel les 4 fonctions contenus dans le useEffect s'exécuteront à chaque fois que la valeur d'une des dépendances changera. cela ne vous causera pas d'erreur mais en terme de complexité cela peut s'avérer difficile à comprendre et vous ferai des appels inutile à chaque changement de dépendance (comme par exemple recharger l'utilisateur actuel lorsque le nombre de likes change ) alors que vous pourriez éviter cela en faisant ça.

 const [comments, setComments] = useState<any[]>([]);
  const [likes, setLikes] = useState<any[]>([]);
  const [hasLiked, setHasLiked] = useState(false);

  useEffect(() => {
    getCurrentUser();
  }, []);

  const getCurrentUser = async () => {
    // query to get current user informations
    const currentUser = await getDoc(doc(firestore, "users", userId));
    setUser(currentUser);
  };

// get post comments
  useEffect(
    () =>
      onSnapshot(
        query(
          collection(firestore, `posts/${postId}/comments`),
          orderBy("timestamp", "desc")
        ),
        (snapshot) => setComments(snapshot.docs)
      ),
    [firestore, id]
  );

  useEffect(
    () =>
      onSnapshot(
        query(collection(firestore, `posts/${postId}/likes`)),
        (snapshot) => setLikes(snapshot.docs)
      ),
    [firestore, id]
  );

  useEffect(
    () =>
      setHasLiked(
        likes.findIndex((like) => like.id === (session as any)?.user?.uid) !==
          -1
      ),
    [likes]
  );
Enter fullscreen mode Exit fullscreen mode

L'utilisation de plusieurs useEffect dans un composant react vous permet de séparer vos besoins et vos préoccupations, cela rends votre code plus lisible et peut dans certains cas vous économiser des appels en base.

2. useCallback et UseMemo

intéressons nous à ce composant fonctionnel

import React from "react";
export default function DisplayDays() {
  const weekDays = [
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thrusday",
    "Friday",
    "Saturday",
    "Sunday",
  ];
  const [days, setDays] = React.useState(weekDays);
  const filterDays = (dayToFilter: string) => {
    setDays((weekDays) => weekDays.filter((day) => day !== dayToFilter));
  };
  return (
    <div>
      <h1>Display WeekDay</h1>
      <div>
        <div>WeekDays</div>
        {days.length === 0 ? (
          <button onClick={() => setDays(weekDays)}>recharge Days</button>
        ) : (
          <ul>
            {days.map((day) => (
              <li key={day}>
                {day} <button onClick={() => filterDays(day)}>remove</button>
              </li>
            ))}
          </ul>
        )}
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Ce composant permet d'afficher les différents jours de la semaine et nous pouvons les supprimer successivement en cliquant sur le jour sélectionné.
faisons une comparaison des 2 fonctions suivante ?

  const filterDays = (dayToFilter: string) => {
    setDays((weekDays) => weekDays.filter((day) => day !== dayToFilter));
  };

  const filterDaysCallBack = useCallback(filterDays, []);
Enter fullscreen mode Exit fullscreen mode

En vrai elles font la même chose mais parmi elle figure une qui est plus optimisé pour notre besoin précis.
Dans cet exemple la fonction useCallBack ne nous sera d'aucune utilité, au contraire elle peut être la cause des problèmes de performance de notre application je vous explique pourquoi:

  • La fonction filterDays n'a aucune utilité d'être stocker en mémoire vu qu'elle filtre la donnée sélectionné et ne l'affiche plus dans notre tableau de jours.
  • Non seulement nous devons définir la fonction dans une variable, mais nous devons également définir un tableau ( []) et appeler le React.useCallback qui lui-même définit les propriétés et exécute des expressions logiques, etc.
  • lors d'une nouveau rendu du composant la fonction filterDays est supprimé pour libérer de la mémoire et recréer pour prendre ses nouveaux paramètres, ce qui n'est pas le cas avec useCallback qui garde toujours les ordures des anciens paramètres ce qui peut causer des problèmes de mémoires.

Deux bonnes raisons nous poussent à utiliser ces deux hooks:

  • L'inégalité référentiel
  • Les calculs coûteux en temps.

L'inégalité référentiel

Reprenons notre exemple précédent et changeons deux ou trois trucs.

const weekDays = [
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thrusday",
    "Friday",
    "Saturday",
    "Sunday",
  ];
  const mapMonth = {
    0: "Junuary",
    2: "February",
  };
  const [days, setDays] = React.useState(weekDays);
  const weekDayMemo = useMemo(() => weekDays, []);
  const monthCallBack = useCallback(() => mapMonth, []);

  useEffect(() => {
    console.log(weekDays, mapMonth);
  }, [weekDays, mapMonth]);
Enter fullscreen mode Exit fullscreen mode

si vous vérifiez la console de votre navigateur vous verrez ce code s'exécuter à chaque rendu malgré que les valeurs de Weekdays, et mapMonth restent intact.
La raison pour laquelle cela pose problème est que useEffect va effectuer une vérification d'égalité référentielle sur les weekDays, et mapMonth entre chaque rendu, et de la manière dont JavaScript fonctionne, les weekDays, et mapMonth seront nouvelles à chaque fois, donc lorsque React teste si les weekDays, et mapMonth ont changé entre les rendus, la réponse sera toujours évalué à true, ce qui signifie que le rappel useEffect sera éffectué après chaque rendu.
Une belle utilisation de useCallBack et de useMemo pourrait se faire ici pour éviter cela.

const weekDays = [
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thrusday",
    "Friday",
    "Saturday",
    "Sunday",
  ];
  const mapMonth = {
    0: "Junuary",
    2: "February",
  };
  const [days, setDays] = React.useState(weekDays);
  const filterDays = () => {
    setDays((weekDays) => weekDays.filter((day) => day !== "dayToFilter"));
  };
  const weekDayMemo = useMemo(() => weekDays, []);
  const monthCallBack = useCallback(() => mapMonth, []);

  useEffect(() => {
    console.log(weekDays);
  }, [weekDayMemo, monthCallBack]);
Enter fullscreen mode Exit fullscreen mode

Les Calculs coûteux en temps de calcul.

L'un des exemples les plus connu de la récursivité est la suite de fibonacci, implémentons cela dans notre application react pour mésurer les performances de useMemo.
Sans UseMemo

function fibonacci(num: any): any {
  if (num <= 1) return 1;

  return fibonacci(num - 1) + fibonacci(num - 2);
}

function PromptFibonacciNumber(props: any) {
  const fibonnacciNumber = fibonacci(props.number);
  return fibonnacciNumber;
}

// Our Counter
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <>
      {count}
      <button onClick={() => setCount(count + 1)}> Add </button>
      <br />
      <br />
      <br />
      <PromptFibonacciNumber number={42} />
    </>
  );
}
export default Counter;
Enter fullscreen mode Exit fullscreen mode

En testant ce code dans votre application react vous pouvez constater le temps énorme mis par l'application avant de générer un nouveau rendu, cela est du au fait que le composant <PromptFibonacciNumber number={42} /> calcule la valeur du composant à chaque rendu ce qui n'est pas très pratique puis que la valeur reste intacte.
Voyons comment optimiser cela.
Avec useMemo

function PromptFibonacciNumber(props: { number: number }) {
  const fibonnacciNumber = useMemo(
    () => fibonacci(props.number),
    // the function will not be executed again until the `number` property changes.
    [props.number]
  );
  return fibonnacciNumber;
}
Enter fullscreen mode Exit fullscreen mode

Les performances de notre applications sont nettement meilleures qu'auparavant.

Je suis à la fin de mon article j'espère que vous aviez appris.
A la semaine prochaine pour un autre article.

Discussion (0)