React 17 les hooks

Introduction

Cette section explique comment utiliser les nouveaux React Hooks, leurs règles et comment vous pouvez créer vos propres Hooks. Comme vous le savez React évolue très rapidement et depuis React 16.8, les nouveaux React Hooks ont été introduits, qui changent la donne en ce qui concerne le développement de React en ce sens qu'ils vont augmenter la vitesse de codage et améliorer les performances de nos applications. React nous permet d'écrire des applications React en utilisant uniquement des composants fonctionnels, ce qui signifie qu'il n'est plus nécessaire d'utiliser des composants de classe.

Les hooks c'est quoi ?

React Hooks est un nouvel ajout dans React 16.8. Ils vous permettent d'utiliser l'état et d'autres fonctionnalités de React sans écrire de composant de classe React. Les hooks React sont également rétrocompatibles, ce qui signifie qu'ils ne contiennent aucun changement avec rupture et qu'ils ne remplacent pas votre connaissance des concepts React.

Au cours de ce chapitre, nous verrons un aperçu des Hooks pour les utilisateurs expérimentés de React, et nous allons également apprendre certains des Hooks React les plus courants tels que:

  • useState
  • useEffect
  • useReducer
  • useRef
  • useImperativeHandle
  • useLayoutEffect
  • useMemo
  • useCallback
  • useDispatch && useSelector
  • memo
  • useDebugValue

Rules

Les React Hooks sont essentiellement des fonctions JavaScript, mais vous devez suivre deux règles pour les utiliser :

  • N'appelez les hooks qu'au niveau supérieur : Documentation officielle. En d'autres termes, éviter d'appeler les hooks à l'intérieur des boucles, des conditions ou des fonctions imbriquées. Je cite : "En suivant cette règle, vous vous assurez que les crochets sont appelés dans le même ordre à chaque rendu d'un composant.''
  • Appelez uniquement les hooks à partir des fonctions React : Documentation officielle. On les appelle à partir des composants de la fonction React ou à partir de hooks personnalisés.

React fournit un plugin linter pour appliquer ces règles pour vous.

 npm install --save-dev eslint-plugin-react-hooks 

useState

Vous savez probablement comment utiliser le state du composant en l'utilisant dans une classe avec this.setState(). Maintenant, vous pouvez utiliser l'état du composant en utilisant le nouveau React Hook : useState.

Importez le Hook useState de React :

 import { useState } from 'react'

Ensuite, vous devez déclarer l'état que vous souhaitez utiliser en définissant une variable d'état et le setter pour cet état spécifique :

 const Counter = () => {
  const [counter, setCounter] = useState<number>(0)
}

Afin de tester notre état, nous devons créer une méthode qui sera déclenchée par l'événement onClick :

 const Counter = () => {
  const [counter, setCounter] = useState<number>(0)
  
  const handleCounter = (operation: 'add' | 'subtract') => {
    if (operation === 'add') {
      return setCounter(counter + 1)
    }
    
    return setCounter(counter - 1)
  }
}

Enfin, nous pouvons afficher l'état du counter et certains boutons pour augmenter ou diminuer le state :

 return (
  <p>
    Counter: {counter} <br />
    <button onClick={() => handleCounter('add')}>+ Add</button>
    <button onClick={() => handleCounter('subtract')}>- Subtract</button>
  </p>
)

Comme vous pouvez le constater, le Hook useState change la donne dans React et facilite la gestion de l'état dans un composant fonctionnel.

Migration d'un composant de classe vers un composant fonctionnel

Considérons un exemple de migration d'un composant utilisant une classe vers les Hooks React.

 class Issues extends Component<Props, State> {
  constructor(props: Props) {
    super(props)

    this.state = {
      issues: []
    }
  }
}

Premièrement, nous devons détruire la classe pour la transformer en composant fonctionnel :

 type Props = {
  propX: string
  propY: number
  propZ: boolean  
}

const Issues: FC<Props> = () => {...}

L'étape suivante consiste à détruire le constructeur (nous ne sommes plus dans une classe) et à y ajouter le Hook useState :

 // Le Hook useState remplace la méthode this.setState()
const [issues, setIssues] = useState<Issue[]>([])

useReducer

Le Hook useReducer est souvent préférable à useState lorsque vous avez une logique d'état local complexe (objet avec plusieurs niveaux) ou lorsque l'état suivant dépend de l'état précédent de cet objet. useReducer vous permet également d'optimiser les performances pour des composants qui déclenchent des mises à jour profondes, car vous pouvez fournir dispatch à la place de fonctions de rappel.

 const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Total : {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useRef

Le Hook useRef renvoie un objet ref modifiable dont la propriété current est initialisée avec l'argument fourni (initialValue). L'objet renvoyé persistera pendant toute la durée de vie du composant. Il permet d'avoir accès à un nœud dans le DOM, ce qui permet, par exemple, d'utiliser une méthode de l'enfant ou de capturer un événement.

 function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` fait référence au champ textuel monté dans le DOM
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Donner le focus au champ</button>
    </>
  );
}

useImperativeHandle

Le Hook useImperativeHandle personnalise l'instance qui est exposée au composant parent lors de l'utilisation de ref. Comme toujours, il vaut mieux s'abstenir d'utiliser du code impératif manipulant des refs dans la plupart des cas.

 function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}

FancyInput = forwardRef(FancyInput);

useLayoutEffect

La signature est identique à celle de useEffect, mais useLayoutEffect s’exécute de manière synchrone après que toutes les mutations du DOM ont eu lieu. Utilisez-le pour inspecter la mise en page du DOM et effectuer un nouveau rendu de manière synchrone. Les mises à jour planifiées dans useLayoutEffect seront traitées de manière synchrone avant que le navigateur ait pu procéder à l’affichage. Préférez l’utilisation du useEffect standard chaque fois que possible, pour éviter de bloquer les mises à jour visuelles.

Si vous migrez du code depuis un composant écrit à l’aide d’une classe, sachez que useLayoutEffect s’exécute dans la même phase que componentDidMount et componentDidUpdate. Nous vous conseillons de commencer avec useEffect, et de ne tenter useLayoutEffect que si vous rencontrez des problèmes.

useEffect

Lorsque vous travaillez avec useEffect, vous devez penser aux effets. Si vous souhaitez obtenir l'effet équivalent à la méthode componentDidMount avec useEffect, vous pouvez procéder comme suit :

Réalisons la migration du composant classe, pour accepter notre useEffect (basé sur l'exemple plus haut) :

 useEffect(() => {
  // Here you perform your side effect
}, [])

componentDidMount() {
  axios
   .get('https://api.github.com/repos/ContentPI/ContentPI/issues')
   .then((response: any) => {
      this.setState({
        issues: response.data
      })
    })
}

Le premier paramètre est le callback de l'effet que vous souhaitez exécuter, et le second paramètre est le tableau des dépendances. Si vous passez un tableau vide ([]) sur les dépendances, le state et les props auront leurs valeurs initiales d'origine. Si vous transmettez un tableau de dépendances, le Hook useEffect ne s'exécutera que si l'une de ces dépendances change :

⚠️ Cependant, il est important de mentionner que même s'il s'agit de l'équivalent le plus proche de componentDidMount, il n'a pas le même comportement. Contrairement à componentDidMount et componentDidUpdate, la fonction que nous passons à useEffect est appelée après la mise en page du composant. Cela fonctionne normalement pour de nombreux effets secondaires courants, tels que la configuration des abonnements et des gestionnaires d'événements, car la plupart des types de travail ne doivent pas empêcher le navigateur de mettre à jour l'écran.

Si vous avez besoin de déclencher un effet de manière conditionnelle, vous devez ajouter une dépendance au tableau de dépendances, sinon vous exécuterez l'effet plusieurs fois et cela peut provoquer une boucle infinie.

Nous avons utilisé la méthode du cycle de vie appelée componentDidMount, qui est exécutée lorsque le composant est monté et ne va s'exécuter qu'une seule fois. Notre Hook useEffect gérera désormais toutes les méthodes de cycle de vie en utilisant une syntaxe différente pour chacune, voyons comment obtenir le même effet que componentDidMount :

 useEffect(() => {
  // When you pass an array of dependencies the useEffect hook will only 
  // run if one of the dependencies changes.
}, [dependencyA, dependencyB])

// When we use the useEffect hook with an empty array [] on the 
// dependencies (second parameter) 
// this represents the componentDidMount method (will be executed when the 
// component is mounted).
useEffect(() => {
  axios
    .get('https://api.github.com/repos/ContentPI/ContentPI/issues')
    .then((response: any) => {
      // Here we update directly our issue state
      setIssues(response.data)
    })
}, [])

useCallback, useMemo et memo

Afin de comprendre la différence entre useCallback, useMemo et memo, nous allons faire un exemple de liste de tâches. (L'ensemble du code se trouve dans la sidebar, branche main) :

Vous pouvez supprimer l'ensemble des fichiers inutiles et changer votre App pour qu'il ressemble à ceci :

 // Dependencies
import { useState, useEffect, useMemo, useCallback } from 'react'

// Components
import List, { Todo } from './List'

const initialTodos = [
  { id: 1, task: 'Go shopping' },
  { id: 2, task: 'Pay the electricity bill'}
]

function App() {
  const [todoList, setTodoList] = useState(initialTodos)
  const [task, setTask] = useState('')

  useEffect(() => {
    console.log('Rendering <App />')
  })

  const handleCreate = useCallback(() => {
    const newTodo = {
      id: Date.now(), 
      task
    }
    
    // Pushing the new todo to the list
    setTodoList(prevTodoList => [...prevTodoList, newTodo])
    
    // Resetting input value
    setTask('')
  }, [task])

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

      <button onClick={handleCreate}>Create</button>

      <List todoList={todoList} />
    </>
  )
}

export default App

Nous définissons certaines tâches initiales et créons le state todoList, que nous transmettrons à travers le composant List. Créons le composant List.tsx :

 // Dependencies
import { FC, useEffect, memo } from 'react'

// Components
import Task from './Task'

// Types
export type Todo = {
  id: number
  task: string
}

interface Props {
  todoList: Todo[]
}

const List: FC<Props> = memo(({ todoList }) => {
  useEffect(() => {
    // This effect is executed every new render
    console.log('Rendering <List />')
  })

  return (
    <ul>
      {todoList.map((todo: Todo) => (
        <Task key={todo.id} id={todo.id} task={todo.task} />
      ))}
    </ul>
  )
})

export default List

Une liste sans taches, ça ne sert à rien, donc créons ce composant :

 import { FC, useEffect, memo } from 'react'

interface Props {
  id: number
  task: string
}

const Task: FC<Props> = memo(({ task }) => {
  useEffect(() => {
    console.log('Rendering <Task />', task)
  })

  return (
    <li>{task}</li>
  )
})

export default Task

On ne va pas s'attarder sur le design, ici le but est de montrer la différence entre les hooks. Voyons le résultat :

Voici le résultat du rendering de nos composants :

resultat du rendering

Comme vous pouvez le voir, en écrivant simplement "Hello", nous avons de nouveaux lots de rendus. Nous pouvons donc déterminer que ce composant n'a pas de bonnes performances, et c'est là que memo va nous aider à améliorer les performances.

Memo

memo est un composant d'ordre élevé (HOC), similaire à PureComponent. Il effectue une comparaison superficielle des props (c'est-à-dire une vérification superficielle), donc si nous essayons de rendre un composant avec les mêmes accessoires tout le temps, le composant ne sera rendu qu'une seule fois et sera mémorisé. Le composant ne sera rendu que lorsqu'un props change de valeur.

Afin de corriger nos composants pour éviter les rendus multiples lorsque nous écrivons dans l'input, nous devons envelopper nos composants avec le HOC memo :

 import { FC, useEffect, memo } from 'react'

...

export default memo(List)

Mais aussi :

 import { FC, useEffect, memo } from 'react'

...

export default memo(Task)

Le résultat :

Maintenant, nous n'avons plus de rendus multiples à chaque fois que nous écrivons dans l'input. Nous obtenons juste le premier lot de rendus la première fois, puis, lorsque nous écrivons, nous obtenons simplement deux autres rendus du composant App, ce qui est tout à fait correct car l'état de la tâche (valeur d'entrée) que nous modifions fait en fait partie du composant App.

Comme vous pouvez le voir, nous avons beaucoup amélioré les performances, et nous exécutons juste le besoin demandé. À ce stade, vous pensez probablement que la bonne façon est de toujours ajouter un memo à nos composants, ou peut-être vous demandez-vous pourquoi React ne le fait pas par défaut pour nous ?

⚠️ La raison en est la performance, ce qui signifie que ce n'est pas une bonne idée d'ajouter memo à tous nos composants à moins que ce ne soit totalement nécessaire. Sinon, le processus de comparaisons superficielles et de mémorisation aura des performances inférieures à celles que nous n'utilisons pas.

J'ai une règle pour déterminer si c'est une bonne idée d'utiliser memo: ne l'utilisez pas. Normalement, lorsque nous avons de petits composants ou une logique de base, nous n'en avons pas besoin à moins que vous ne travailliez avec des données volumineuses provenant d'une API ou de votre composant doivent effectuer de nombreux rendus (généralement d'énormes listes), ou lorsque vous remarquez que votre application ralentit. Ce n'est que dans ce cas que je recommanderais d'utiliser memo.

useMemo

Supposons que nous souhaitions maintenant implémenter une fonction de recherche dans notre liste de tâches. La première chose que nous devons faire est d'ajouter un nouvel état appelé term :

 function App() {
  const [todoList, setTodoList] = useState(initialTodos)
  const [task, setTask] = useState('')
  const [term, setTerm] = useState('')

Ensuite, nous devons créer une fonction appelée handleSearch et ajoutons le bouton:

 const filteredTodoList = todoList.filter((todo: Todo) => {
  console.log('Filtering...')
  return todo.task.toLowerCase().includes(term.toLocaleLowerCase())
})
return (
  <>
    <input 
      type="text" 
      value={task} 
      onChange={(e) => setTask(e.target.value)} 
    />

    <button onClick={handleCreate}>Create</button>
    <button onClick={handleSearch}>Search</button>

    <List todoList={filteredTodoList} />
  </>
)

Vous devriez obtenir ce résultat :

Maintenant, regardons les performances :

Le filtrage est exécuté deux fois, puis le composant App est rendu, et tout semble bon ici, mais quel est le problème avec cela ? Essayez d'écrire "Go to the doctor" à nouveau dans l'input et voyons combien de rendus et de filtrages vous obtenez. Vous verrez que pour chaque lettre que vous écrivez, vous obtiendrez deux appels de filtrage et un rendu App et vous n'avez pas besoin d'être un génie pour voir que c'est une mauvaise performance. Sans oublier que si vous travaillez avec un grand tableau de données, ce sera pire. Alors comment pouvons-nous résoudre ce problème ?

🦸‍♂️ useMemo est notre héros !!! Nous devons déplacer notre filtre à l'intérieur de useMemo :

 const filteredTodoList = useMemo(() => todoList.filter((todo: Todo) => {
  console.log('Filtering...')
  return todo.task.toLowerCase().includes(term.toLowerCase())
}), [])

Ce hook mémorisera le résultat (valeur) d'une fonction et aura quelques dépendances à écouter. Il reste un petit problème. Si vous essayez de cliquer sur le bouton "Rechercher", il ne filtrera pas, et c'est parce que nous avons manqué les dépendances. Vous devez ajouter les dépendances term et todoList au tableau :

 const filteredTodoList = useMemo(() => todoList.filter((todo: Todo) => {
  console.log('Filtering...')
  return todo.task.toLowerCase().includes(term.toLocaleLowerCase())
}), [term, todoList])

Vous devriez obtenir les performances suivantes :

⚠️ Ici, nous devons utiliser la même règle que celle que nous avons utilisée pour memo ; ne l'utilisez qu'en cas d'absolue nécessité.

useCallback

Nous allons maintenant ajouter une fonctionnalité de suppression de tâche pour savoir comment useCallback fonctionne. La première chose que nous devons faire est de créer une nouvelle fonction appelée handleDelete dans notre App :

 const handleDelete = (taskId: number) => {
  const newTodoList = todoList.filter((todo: Todo) => todo.id !== taskId)
  setTodoList(newTodoList)
}

Ensuite, nous devons passer cette fonction au composant List en tant que prop :

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

      <button onClick={handleCreate}>Create</button>
      <button onClick={handleSearch}>Search</button>

      <List todoList={filteredTodoList} handleDelete={handleDelete} />
    </>
  );

Ensuite, dans notre List, vous devez ajouter la nouvelle prop :

 interface Props {
  todoList: Todo[];
  handleDelete: any;
}

Ensuite, vous devez l'extraire ce nouveau prop et le transmettre au composant Task :

 const List: FC<Props> = ({ todoList, handleDelete }) => {
  useEffect(() => {
    // This effect is executed every new render
    console.log('Rendering <List />')
  })

  return (
    <ul>
      {todoList.map((todo: Todo) => (
        <Task 
          key={todo.id} 
          id={todo.id}
          task={todo.task} 
          handleDelete={handleDelete}
        />
      ))}
    </ul>
  )
}

Ensuite ajustons le composant Task, pour qu'un bouton déclenche le clique de suppression :

 import { FC, useEffect, memo } from "react";

interface Props {
  id: number;
  task: string;
  handleDelete: any;
}

const Task: FC<Props> = ({ id, task, handleDelete }) => {
  useEffect(() => {
    console.log("Rendering <Task />", task);
  });

  return (
    <li>
      {task} <button onClick={() => handleDelete(id)}>X</button>
    </li>
  );
};

export default memo(Task);

Regardons les performances :

Lors du delete tout semble aller ! Par contre quand nous ajoutons du texte maintenant nous avons un nouveau soucis de performance :

A ce stade, vous vous demandez probablement, que se passe-t-il si nous avons déjà implémenté le HOC mémo pour mémoriser les composants ?

Le problème maintenant est que notre fonction handleDelete est passée à deux composants:

  1. De App à List
  2. De List à Task

Le problème est que cette fonction est régénérée chaque fois que nous avons un nouveau rendu, dans ce cas, chaque fois que nous écrivons quelque chose. Le hook useCallback est très similaire à useMemo dans la syntaxe, mais la principale différence est qu'au lieu de mémoriser la valeur résultante d'une fonction, il mémorise plutôt la définition de la fonction :

 const handleDelete = useCallback((taskId: number) => {
  const newTodoList = todoList.filter((todo: Todo) => todo.id !== taskId)
  setTodoList(newTodoList)
}, [todoList])

Nous devrions avoir résolu le soucis précédent ! Comme vous pouvez le constater, useCallback nous aide à améliorer considérablement les performances. Dans la section suivante, vous apprendrez à mémoriser une fonction passée en argument avec useEffect.

Il y a un cas particulier où nous aurons besoin d'utiliser useCallback, et c'est quand nous passons une fonction comme argument à travers useEffect, par exemple, dans notre App. Créons un nouveau useEffect :

 const printTodoList = useCallback(() => {
  console.log("Changing todoList", todoList);
}, [todoList]);

useEffect(() => {
  printTodoList();
}, [todoList, printTodoList]);

Dans le cas actuel, nous avons :

  • Utiliser useCallback, car nous manipulons un état
  • Ajouter printTodoList comme dépendance

Nous avons donc résolu le souci de performance en liant useCallback et useEffect.

En résumé :

memo useMemo useCallback
Mémorise un composant Mémorise une valeur calculée Mémorise une définition de fonction pour éviter de la redéfinir à chaque rendu.
Se remémore lorsque les accessoires changent Pour les propriétés calculées Utilisez-le chaque fois qu'une fonction est passée comme argument d'effet.
Évite les re-rendus Pour les processus lourds Utilisez-le chaque fois qu'une fonction est passée par des props à un composant mémorisé.

⚠️ Et enfin, n'oubliez pas la règle d'or : ne les utilisez qu'en cas d'absolue nécessité.

useReducer

Vous avez probablement une certaine expérience de l'utilisation de Redux (react-redux) avec des composants de classe, et si tel est le cas, vous comprendrez comment useReducer fonctionne. Les concepts sont fondamentalement les mêmes : actions, réducteurs, répartition, stockage et état. Même si, en général, cela semble très similaire à react-redux, ils ont quelques différences :

  • react-redux fournit des middleware et des wrappers tels que thunk, sagas et bien d'autres encore.
  • useReducer vous donne simplement une méthode dispatch que vous pouvez utiliser pour envoyer des objets simples en tant qu'actions.
  • useReducer n'a pas de magasin par défaut ; Vous pouvez en créer un en utilisant useContext, mais cela ne fait que réinventer la roue.

Implémentons un exemple :

 
import { useReducer, useState, ChangeEvent} from 'react'

type Note = {
  id: number
  note: string
}

type Action = {
  type: string
  payload?: any
}

type ActionTypes = {
  ADD: 'ADD'
  UPDATE: 'UPDATE'
  DELETE: 'DELETE'
}

const actionType: ActionTypes = {
  ADD: 'ADD',
  DELETE: 'DELETE',
  UPDATE: 'UPDATE'
}

const initialNotes: Note[] = [
  {
    id: 1,
    note: 'Note 1'
  },
  {
    id: 2,
    note: 'Note 2'
  }
]

const reducer = (state: Note[], action: Action) => {
  switch (action.type) {
    case actionType.ADD:
      return [...state, action.payload]

    case actionType.DELETE: 
      return state.filter(note => note.id !== action.payload)
    
    case actionType.UPDATE:
      const updatedNote = action.payload
      return state.map((n: Note) => n.id === updatedNote.id ? updatedNote : n)
    
    default:
      return state
  }
}

const Notes = () => {
  const [notes, dispatch] = useReducer(reducer, initialNotes)
  const [note, setNote] = useState('')

  const handleSubmit = (e: any) => {
    e.preventDefault()

    const newNote = {
      id: Date.now(),
      note
    }

    dispatch({ type: actionType.ADD, payload: newNote })
  }

  return (
    <div>
      <h2>Notes</h2>

      <ul>
        {notes.map((n: Note) => (
          <li key={n.id}>
            {n.note} {' '}
            <button 
              onClick={() => dispatch({ 
                type: actionType.DELETE,
                payload: n.id
              })}
            >
              X
            </button>

            <button 
              onClick={() => dispatch({ 
                type: actionType.UPDATE,
                payload: {...n, note}
              })}
            >
              Update
            </button>
          </li>
        ))}
      </ul>
      
      <form onSubmit={handleSubmit}>
        <input 
          placeholder="New note" 
          value={note} 
          onChange={e => setNote(e.target.value)} 
        />
      </form>
    </div>
  )
}

export default Notes

Nous avons défini 3 opérations : ADD, DELETE et UPDATE.

dispatch est similaire à useState, et dans notre cas, il nous permet dans un premier temps d'initialiser notre "State": const [notes, dispatch] = useReducer(reducer, initialNotes).

dispatch sert ensuite à envoyer les actions avec le type d'action que l'on veut réaliser : dispatch({ type: actionType.ADD, payload: newNote }).

Comme vous pouvez le voir, useReducer est à peu près le même que Redux en termes de méthode de répartition, d'actions et de réducteurs, mais la principale différence est que cela est limité au contexte de votre composant et de ses enfants. Donc, si vous avez besoin d'un magasin global pour être accessible depuis l'ensemble de votre application, vous devez utiliser react-redux à la place.

useDebugValue

Vous pouvez utiliser useDebugValue pour afficher une étiquette pour les Hooks personnalisés dans les outils de développement React (React DevTools, NdT).

 useDebugValue(value)

useDispatch && useSelector

Avant les Hooks, nous utilisions toujours connect(), qui est un composant d'ordre supérieur et un wrapper à notre composant. connect() permet de lire les valeurs du store Redux. connect() prend deux arguments, tous deux facultatifs :

  • mapStateToProps: appelé à chaque fois que l'état du store change. Il reçoit l'intégralité de l'état du store et doit renvoyer un objet de données dont ce composant a besoin.
  • mapDispatchToProps: Ce paramètre peut être soit une fonction, soit un objet. S'il s'agit d'une fonction, elle sera appelée une fois lors de la création du composant. Il recevra dispatch comme argument et devrait renvoyer un objet plein de fonctions qui utilisent dispatch pour répartir les actions.

useDispatch

C'est l'équivalent de mapDispatchToProps. Nous allons appeler useDispatch et le stocker dans une variable dispatch. Ce Hook renvoie une référence à la fonction dispatch du store.

 import React from "react";
//import useDispatch from react-redux
import { useDispatch} from "react-redux";
//these are actions define in redux>actions folder
import { updateFirstName } from "../redux/actions"; 

const Form = () => {

  const dispatch = useDispatch();

  const handleFirstName = () => {
    //dispatching the action
    dispatch(updateFirstName("Jason"));
  };

  return (
    <React.Fragment>
      <div className="container">
        <button onClick={handleFirstName}>Update First 
        Name</button>
      </div>
    </React.Fragment>
  );
};

export default Form;

useSelector

C'est l'équivalent de mapStateToProps. useSelector est une fonction qui prend l'état actuel comme argument et renvoie toutes les données que vous voulez. Elle vous permet de stocker les valeurs de retour dans une variable dans le cadre de vos composants fonctionnels, au lieu de les transmettre comme props.

 import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { updateFirstName } from "../redux/actions";

const Form = () => {
  const dispatch = useDispatch();
  const nameObj = useSelector((state) => state.nameReducer);
  const { firstName } = nameObj;
  const handleFirstName = () => {
    dispatch(updateFirstName("Jason"));
  };

  return (
    <React.Fragment>
      <div className="container">
        <label>First Name : {firstName}</label>
        <button onClick={handleFirstName}>Update First Name</button>

        <label>Last Name : {lastName}</label>
        <button type="submit" onClick={handleLastName}>
          Update First Name
        </button>
      </div>
    </React.Fragment>
  );
};

export default Form;

Migration des cycles de vies

Comment vous le savez, nous avons plusieurs cycles de vie en React :

React lifecycle methods

Nous allons lister comment vous pouvez migrer les anciens cycles de vie pour qu'ils fonctionnent avec les nouveaux hooks :

Lifecycle Hooks Spécification
componentDidMount useEffect useEffect(() => {}, []) : vous devez passer un tableau vide comme argument
componentDidUpdate useEffect useEffect(() => {}) : vous ne devez pas passer d'argument
componentWillUnmount useEffect useEffect(() => { return () => {} }, []) : nous utilisons la fonction return qui retourne une fonction qui sera appelée à la destruction

Conclusion

J'espère que vous avez apprécié la lecture qui regorge de très bonnes informations concernant les nouveaux React Hooks. Jusqu'à présent, vous avez appris :

  • Comment fonctionnent les nouveaux Hooks
  • Comment récupérer des données avec des Hooks
  • Comment migrer un composant de classe vers React Hooks
  • Comment fonctionnent les effets
  • La différence entre memo, useMemo et useCallback
  • Comment useReducer fonctionne et la principale différence par rapport à React-Redux

Toutes ces connaissances vous aideront à améliorer les performances de vos composants React.

Developpeur et architecte passionné, qui souhaite partagé son univers et ses découvertes afin de rendre les choses plus simple pour chacun

URLs

Check les divers liens pour cet article