_______     ________    ________    ________    ________ 
     |       \   |        |  |        |  |        |  |        |
     |   __   |  |   _____|  |   __   |  |   _____|  |__    __|
     |  |__| _|  |  |___     |  |__|  |  |  |           |  |   
     |   __  \   |   ___|_   |   __   |  |  |_____      |  |   
     |  |  |  |  |        |  |  |  |  |  |        |     |  |   
     |__|  |__|  |________|  |__|  |__|  |________|     |__|   
        Cours complet React R avec Jest Cypress et Axios



                        react.txt  05/02/2022
                           ultimecool.com 

Sommaire


  CHAPITRE 1 - TypeScript
     1.1. Introduction
     1.2. Typage et Fonctions
     1.3. Interfaces et classes
     1.4. Modules
     1.5. Les fichiers de déclaration
     1.6. Utilisation d'un code Javascript en TypeScript
     1.7. Comprendre les promises en JavaScript / TypeScript
  
  CHAPITRE 2 - SCSS
     2.1. SASS Syntactically awesome style sheets
     2.2. Sassy CSS (CSS à la Sass)
     2.3. Caractéristiques du SCSS
  
  CHAPITRE 3 - React Généralités
     3.1. ES2015
     3.2. Environnement de développement
     3.3. Introduction à JSX
     3.4. Le rendu des éléments
     3.5. Composants et props
     3.6. Etat et cycle de vie
     3.7. Gérer les événements
     3.8. Affichage conditionnel
     3.9. Listes et clés
    3.10. Formulaires
    3.11. Faire remonter l'état
    3.12. Router l'application
    3.13. Documenter le code
    3.14. Contexte
  
  CHAPITRE 4 - React Guides avancés
     4.1. Introduction aux Hooks
     4.2. Star Match Game
     4.3. Hook personnalisé
     4.4. List of items
     4.5. useState avancé
     4.6. Reducer avec Javascript
     4.7. useReducer avec React
     4.8. useReducer pour les états complexes
     4.9. useContext avancé
    4.10. Référence de l'API des Hooks
    4.11. React List: Mise à jour des items
    4.12. React Test unitaires
    4.13. Redux
    4.14. Redux Saga
  
  CHAPITRE 5 - Référence de l'API
     5.1. React.Component

  CHAPITRE 6 - Framworks
     6.1. Jest Enzime
     6.2. Cypress
     6.3. Axios
     6.4. Formik
     6.5. Validations Yup
     6.6. jsonserver
     6.7. Bootstrap
     6.8. AgGrid

  CHAPITRE 7 - React Native
     7.1. Découverte de React Native

  CHAPITRE 8 - Divers
     8.1. WebAssembly en 5 minutes

CHAPITRE 1 - TypeScript

1.1. Introduction

Le TypeScript est un langage de programmation développé par Microsoft en 2012. C'est un langage de transition vers le futur JavaScript qui permet de disposer des possibilités de ECMAScript 6 et 7, sans attendre leur implémentation dans les navigateurs.

C'est un langage open source, développé comme un sur-ensemble de Javascript. Tout code valide en Javascript l’est en TypeScript. Le langage a de nouvelles fonctionnalités comme le typage ou une meilleure programmation orientée objet.

Il suffit d’utiliser l’outil de compilation de TypeScript pour le transpiler (C'est le fait de compiler le code source d’un langage en un autre langage) en Javascript. Ainsi, le code exécuté sera un équivalent Javascript du code TypeScript compilé.

Avantages:

  • De nombreux outils sont disponibles
  • Un langage orienté objet avec l’introduction du typage, de l’héritage ou encore les notions de public et private
  • La transition du JS vers le Typescript peut se faire progressivement
  • La transition inverse très simple grâce à la transpilation en ECAMScript

Inconvéniants:

  • Le code a une syntaxe plus lourde et doit être compilé au préalable
  • Les fichiers de déclaration des librairies ne sont pas toujours à jour
  • Typescript ne corrige pas les travers du Javascript, il en limite certains impacts en amont

TypeScript est un langage qui permet de raccourcir le temps de développement en introduisant des fonctionnalités de la programmation orientée objet. Le compilateur est d’une aide précieuse pour repérer rapidement les erreurs car il réduit le délai entre écriture du code et identification d’erreurs grâce notamment au typage. A noter que le transpilage du TS en JS, peut réduire la barrière à l’entrée de l’usage de langage script pour des développeurs backend, délègue la gestion des subtilités du Javascript aux outils et assure un contrôle sur la compatibilité de l’application avec les différents navigateurs sous condition.

Pour installer les outils nécessaires pour TypeScript :

  • Via l’outil npm en installant Node.js
  • Via l’installation d’un plugin sur un IDE (Notepad++, Eclipse, Vim, VSCode..) Voici la commande à exécuter pour installer TypeScript avec NPM : npm install -g typescript

1.2. Typage et Fonctions

boolean, number, string array: var names: string[] = [“Pierre”,”Paul”,”Jaques”]; var otherNames: Array = [“Pierre”,”Paul”,”Jaques”]; any: C’est le type qui se rapproche le plus du typage dynamique du Javascript. Tous les types de données peuvent être affectés à une variable de type any. void: Pour déclarer le type de retour d'une fonction qui ne renvois rien. enum:

Casting de types: var myObject = {id:1, name:"John"}; var myOtherObject = {id:2, name:”Peter”} as any;

Les fonctions dans TypeScript s’écrivent de la même façon que dans Javascript avec les types. Dans le cas où notre fonction aurait des paramètres optionnels, il suffit d’ajouter après le nom du paramètre un point d’interrogation.


function greeter(name: string, lastname?: string): string {
  if(lastname)
    alert(lastname);
}
function greeter(name: string, lastname:string = ""): string {
}
 

1.3. Interfaces et classes

L’interface nous offre un mécanisme permettant de définir des conditions propriétés et/ou méthodes) requis pour un objet.

Pour qu’un objet satisfasse une interface, il doit contenir au moins toutes les propriétés et méthodes de cette interface.


interface IPerson{  
   firstName: string;
   lastName: string;
}
 

La classe est la notion qui définit un objet, elle est l’un des concepts clés de la programmation orientée objet avec l’interface. Ces dernières sont utilisées en tandem au travers de structures appelées “design pattern” dont l’objectif est de résoudre des problèmes spécifiques. Ci-dessous un exemple de classe.


class Personnage {
   public fullname: string;
 
   constructor(firstname: string, lastname: string) {
       this.fullname = `${firstname} ${lastname}`;
   }
 
   public greet(name?: string): string {
       if(name)
           return "Bonjour " + name + "! Je m'appelle " + this.fullname;
 
       return "Bonjour! Je m'appelle " + this.fullname;
    }
}

let vendeur = new Personnage("Pierre","Paul");
let msg = vendeur.greet("");
 
alert(msg);
 

1.4. Modules

Un module est ce qu'en .NET on appellerait une assembly ou namespace. C’est un regroupement logique de classes et d’interfaces qui permet de structurer un projet et de le rendre plus propice aux changements. Les modules peuvent aussi bien exposer des classes que des fonctions, des constantes ou des interfaces.

Le module se déclare à l’aide du mot-clé "module". Ici, la méthode "greeting" sans paramètres est accessible dans toute l’application au travers de Module1 alors que la méthode "greeting" avec paramètres n’est accessible que dans le fichier où elle est déclarée. Le module s’utilise comme suivant :


export module Module1{                     import {Module1} from './app/module1'
  export class Person{                     let md = new Module1.Person();
    constructor(){}                        md.greeting();
    greeting(): void{                      let person : Module1.IGreeter = md;
      greeting("Module1.print()");
    }
  }
  export const pi: number = 3.14;
  export interface IGreeter{
    greeting():void;
  }
  export function farwell(){
    alert("farwell of Module1");
  }
}
function greeting(sName: string):void{
   alert("Called method: " + sName);
}
 

1.5. Les fichiers de déclaration

C'est le fichier qui contient l’ensemble des signatures de méthode d’une librairie. Il permet entre autres, d’alimenter l’auto-complétion et surtout d’avoir rapidement l’API complète d’une librairie. Il n’est donc plus nécessaire de connaître par cœur l’API des librairies.

La source principale des fichiers de déclaration se trouve dans ce repository git : https://github.com/DefinitelyTyped/DefinitelyTyped. Vous y trouverez un très grand nombre de fichiers de déclaration des librairies connues.

Ici, nous souhaitons utiliser lodash ainsi que le fichier de déclaration associé dans notre application que nous installons grâce aux commandes ci-dessus. npm install --save lodash npm install --save @type/lodash

L’installation du fichier de déclaration permet de bénéficier de l’auto- complétion. Si vous recherchez le fichier de déclaration d’une librairie vous pouvez utiliser le site suivant: https://aka.ms/types

Ex: import * as _ from "lodash"; var test = _.[add, after, ...]

1.6. Utilisation d'un code Javascript en TypeScript

Si des interactions sont nécessaires entre les fichiers en TypeScript et en Javascript, alors on peut envisager l’utilisation d’un fichier de déclaration de type spécifique pour notre code Javascript.

legacy.js contient notre code JS App.ts contient notre nouveau code TypeScript Legacy-typing.d.ts contient la définition des types de l’ensemble des éléments que l’on souhaite utiliser dans notre TypeScript

App.ts

/// console.log("start"); var myName: string = "Sylvain Sidambarom"; LegacyGreeter.sayBye(myName); // Good bye Sylvain Sidambarom console.log("done.")

Legacy-typing.d.ts


declare module myLegacyModule
{
   export interface Greeter
   {
     sayBye(name:any): void;
 

On déclare un module dans lequel on définit une interface de correction aux éléments que l’on souhaite utiliser pour dAvantages:

  • Langage typé
  • Compilation en différentes versions ECAMScript à partir de la version 3
  • De nombreux outils sont disponibles
  • Un langage orienté objet avec l’introduction du typage, de l’héritage ou encore les notions de public et private
  • La transition du JS vers le Typescript peut se faire progressivement
  • La transition inverse très simple grâce à la transpilation en ECAMScript

Inconvéniants

  • Le code exécuté doit être compilé au préalable
  • Le code a une syntaxe plus lourde
  • Les fichiers de déclaration des librairies ne sont pas toujours à jour
  • Typescript ne corrige pas les travers du Javascript, il en limite certains impacts en amont

Conclusion TypeScript est un langage qui permet de raccourcir le temps de développement en introduisant des fonctionnalités de la programmation orientée objet. Le compilateur est d’une aide précieuse pour repérer rapidement les erreurs car il réduit le délai entre écriture du code et identification d’erreurs grâce notamment au typage. A noter que le transpilage du TS en JS, peut réduire la barrière à l’entrée de l’usage de langage script pour des développeurs backend, délègue la gestion des subtilités du Javascript aux outils et assure un contrôle sur la compatibilité de l’application avec les différents navigateurs, sous condition du code legacy. Puis on déclare une variable globale du même type que l’interface, que l’on a définie dans le module.

legacy.js


var LegacyGreeter = {
   sayBye : function(name)
   {
       console.log("Good bye" + name);
   }
};
exports = LegacyGreeter;
 

De cette façon, le TypeScript peut évoluer sans modifier le Javascript, ce qui permet une migration plus maîtrisée du Javascript au TypeScript.

1.7. Comprendre les promises en JavaScript / TypeScript

La norme ES2015 (ou ES6) s'est largement diffusée côté navigateurs, serveurs, transpileurs et développeurs au point que les Promises sont devenues un élément central de la programmation asynchrone sur lequel d'autres fonctionnalités de la norme ECMAScript se construisent, comme async/await. Les différences avec JS ne concernent que les annotations de typage qui peuvent être vues ici comme de la documentation supplémentaire.

Traitements asynchrones et callbacks

JavaScript est un langage reposant sur une architecture asynchrone. L'appel à des traitements asynchrones se faisait à l'aide de callbacks, ces fonctions assées en argument, permettant de poursuivre l'exécution du programme suite à la réalisation du traitement asynchrone. La plus connue des fonctions asynchrones dans les API JavaScript est XMLHttpRequest.send(). Voici une fonction asynchrone qui simulera la récupération d'une donnée sous la forme d'un nombre. Cette fonction «plante» aléatoirement dans 25% des cas.


type Callback = (error?: Error, data?: number) => void;
 
function getData(callback: Callback) {
    setTimeout(function setTimeoutCB(counter: number) {
        if (Math.random() < 0.25) {
            callback(new Error("Error in retrieving data."));
        }
        else {
            let data = Math.random();
            callback(undefined, data);
        }
    }, 500);
} // getData
 

Cette fonction asynchrone utilise une convention largement répandue en JS qui est le «callback pattern» reposant sur les quatre principes suivants:

  • une seule fonction callback gérant à la fois le succès et l'échec;
  • la fonction callback est appelée une seule fois (en cas de succès ou d'échec);
  • la fonction callback est le dernier argument de la fonction asynchrone;
  • le premier paramètre de la fonction callback représente l'erreur, le second représente le résultat en cas de succès: function (error, result) {}

function process1() {                     function process3() {                
  getData((error, data) => {                getData((error, data) => {          
    if (error)                                if (error)                        
      console.info(error);                      console.log(error);            
    else {                                    else {                            
      console.log("Process 1:", data);          console.log("Process 3:", data);
      process2();                             }                                
    }                                       });                                
  });                                     } // process3                        
} // process1

function process2() {                     Exemple de sortie sans erreur:
  getData((error, data) => {              Process 1: 0.062177133421918995
    if (error)                            Process 2: 0.2886812664927907
      console.log(error);                 Process 3: 0.2154450024357153
    else {
      console.log("Process 2:", data);    Exemple de sortie avec erreur:
      process3();                         Process 1: 0.41092615721662096
    }                                     Error: Error in retrieving data.
  });                                         at Timeout.setTimeoutCB [as ...]
} // process2                                 ...
 

Cette façon classique de programmer des appels à des fonctions asynchrones pose problème quand le code est plus gros, avec des fonctions d'appel dans le désordre et dans plusieurs fichiers source. Cela aboutit à ce qui est communément appelé la programmation spaghetti et qui est avec la «pyramide de la mort» (succession excessive d'indentations), le principal symptôme de «l'enfer des callbacks ». C'est là qu'entre en jeu un nouveau concept, les Promises.

La promesse des Promises

Une Promise est la transformation d'une fonction asynchrone en un objet (au sens JavaScript du terme) afin de faciliter la manipulation de ladite fonction asynchrone. Cette transformation d'une fonction en un objet est parfois appelée réification. C'est d'ailleurs un design pattern puissant.

La norme ES2015 propose un constructeur de la forme suivante :


new Promise((resolve, reject) => {
 /* appel à la fonction asynchrone */
});
 

Le constructeur d'une Promise prend en paramètre une fonction, désignée comme «l'exécuteur». Cet exécuteur prend 2 fonctions en argument: resolve et reject. La fonction resolve() sera appelée en cas de succès de la fonction asynchrone, tandis que la fonction reject() sera appelée en cas d'échec.


type PromiseResolve<T> = (value?: T | PromiseLike<T>) => void;
type PromiseReject = (error?: any) => void;
 
function getDataPromise() {
  return new Promise(
    (resolve: PromiseResolve<number>, reject: PromiseReject): void => {
    getData((error, data) => {
      if (error) reject(error)
      else resolve(data)
    });
  });
} // getDataPromise
 

Pour appeler notre fonction asynchrone getData() à partir de cet objet généré par le constructeur new Promise(), on peut utiliser une méthode nommée then() :


getDataPromise().then(
  data => { // resolve()
    console.log("Process 1:", data);
    return getDataPromise();
  }
)
 

La méthode then() va appeler l'exécuteur de la Promise qui lui-même appellera la fonction asynchrone. then() prend deux paramètres, le second étant optionnel. Ces paramètres correspondent respectivement à la fonction resolve() et reject() qui seront transmis à l'exécuteur. Dans l'exemple ci-dessus, seul le paramètre correspondant à la fonction resolve() a été spécifié.

A noter qu'il n'est pas obligatoire de spécifier la fonction reject() dans l'appel à la méthode then(), c'est même déconseillé. Si une erreur survient et que la fonction reject() n'est pas spécifiée, alors une exception sera signalée qui pourra être récupérée ultérieurement via une autre méthode catch().

Composition de Promises

L'état d'une Promise peut évoluer au cours de son existence.

Lors de sa création, une Promise est dans un état «en attente» (pending), sous- entendu, en attente d'être résolue. Lorsque la méthode then() de cette Promise est appelée, son état devient soit honoré (fullfilled) en cas de succès du traitement asynchrone sous-jacent, ou rompu (rejected) en cas d'échec. Une Promise honorée ou rompue est une Promise acquittée (settled) ou encore résolue, bien que ce dernier terme puisse porter à confusion avec la fonction resolve() qui correspond à une Promesse honorée uniquement.

Pour une Promise prise isolément, ces changements d'état n'ont pas une grande importance. Ce n'est plus le cas lorsqu'il s'agit de chaîner, de composer plusieurs Promises entre elles.

La méthode then() renvoyant elle-même une Promise, il est possible d'enchaîner les appels à cette méthode.


getDataPromise()
  .then(
    data => { // resolve()
        console.log("Process 1:", data);
        return getDataPromise();
    }
    //error => { // reject()
    //    console.log(error);
    //})
   )
  .then(
    data => { // resolve()
        console.log("Process 2:", data);
        return getDataPromise();
    })
  .then(
    data => { // resolve()
        console.log("Process 3:", data);
    })
  .catch(error => { // reject()
    console.log(error);
  })
;
 

Ici il n'a pas été nécessaire de créer les fonctions intermédiaires, limitant ainsi le recours au code spaghetti, tout en évitant la « pyramide de la mort ».

Autres méthodes utiles de Promises

Les méthodes all() et race prennent en paramètres une séquence de Promises.

all() renvoie une Promise qui est honorée si toutes les Promises sont honorées. La valeur finale étant un tableau des valeurs des Promises honorées. Dans le cas contraire, alors la Promise renvoyée par la méthode all() sera rompue.

race() renvoie la première Promise à avoir été honorée. Ce n'est que si toutes les Promises ont été rompues que la Promise renvoyée par race() sera rompue.


Promise.all([getDataPromise(), getDataPromise(), getDataPromise()])
  .then(
      data => {
          console.log("Success!", data);
      })
  .catch(error => {
      console.log(error);
  });
 

Exemple de sortie sans erreur: Success! [ 0.5930896059730313, 0.4881301731930028, 0.338379519116232 ]


Promise.race([getDataPromise(), getDataPromise(), getDataPromise()])
    .then(
        data => {
            console.log("Success!", data);
        })
    .catch(error => {
        console.log(error);
    });
 

Exemple de sortie sans erreur: Success! 0.2673875038150235

la fonction Promise.resolve(MaFonction/Ma variable) est très utile, elle permet de renvoyer une Promise sur une autre fonction ou même sur une variable, même si elles ne sont pas elle même des Promise. Ce qui permet de continuer une chaîne (ou composition dans l'article) même avec du code qui n'est pas de type Promise.

Si votre plateforme cible ne supporte pas les Promises (et plus généralement la norme ES2015), il est possible d'avoir recours à un polyfill. Il en existe de nombreux sur la toile, 100 % compatibles avec la norme ES2015. Il y a aussi le transpilateur Babel qui inclut un polyfill pour les Promises. Pour compiler du code TypeScript en ES5 tout en utilisant les Promises, il convient d'utiliser un polyfill tiers comme indiqué précédemment, et d'inclure la bibliothèque es2015.promise dans l'option lib du fichier de projet

Async/await

Le standard est basé sur des callbacks, qui ont vite été supplantées par les promesses, une manière plus élégante et efficace d’écrire du code asynchrone. Il en va de même pour le JavaScript côté client qui se base sur des évènements.

Une fonction définie avec async renvoie systématiquement une promesse : si une erreur est levée pendant l’exécution de la fonction, la promesse est rejetée, et si une valeur est retournée, la promesse est résolue avec cette valeur. Si une promesse est retournée, elle est inchangée.


async function fonctionAsynchroneOk() {
 // équivaut à :
 // return Promise.resolve('résultat');
 return 'résultat';
}
fonctionAsynchroneOk().then(console.log) // log "résultat"

async function fonctionAsynchroneKo() {
 // équivaut à :
 // return Promise.reject(new Error('erreur'));
 throw new Error('erreur');
}
fonctionAsynchroneKo().catch(err => console.log(err.message)) // log "erreur"
 

Retourner systématiquement une promesse rejetée lorsqu’une erreur est levée est un avantage indéniable qui justifie à lui seul l’utilisation du mot clé async, comme nous allons en parler par la suite. C’est aussi une excellente manière de documenter son code.

La partie la plus intéressante est l’utilisation du mot clé await, qui ne peut être utilisé que dans une fonction async. Il permet d’attendre la résolution d’une promesse et retourner sa valeur.


async function getNombreAsynchrone1() {/* traitement asynchrone       */}
async function getNombreAsynchrone2() {/* (e.g. appel d’une API HTTP) */}

async function getAdditionAsynchrone() {
 const nombre1 = await getNombreAsynchrone1();
 const nombre2 = await getNombreAsynchrone2();
 return nombre1 + nombre2;
}
 

Si on veut le même résultat avec une utilisation “classique” des promesses, on peut par exemple :

  • casser la chaîne de promesses avec un then imbriqué

function getAdditionAsynchrone() {
 return getNombreAsynchrone1()
   .then(nombre1 => {
     return getNombreAsynchrone2()
       .then(nombre2 => nombre1 + nombre2);
   });
}
 
  • ou encore déclarer une variable dans le scope de la fonction

function getAdditionAsynchrone() {
 let nombre1;
 return getNombreAsynchrone1()
   .then(vnombre1 => {
     nombre1 = vnombre1;
     return getNombreAsynchrone2();
   })
   .then(nombre2 => nombre1 + nombre2);
}
 

Ces équivalents sont moins clairs et élégants que la version avec async/await. Le mot clé await peut aussi être utilisé devant une valeur, et n’a alors aucun effet, ce qui peut être utile dans le cas d’un refactoring car on peut interchanger du code synchrone et asynchrone sans modifier le code appelant ; ce n’est cependant pas conseillé.

Parallélisation de traitements asynchrones via l’habituel Promise.all ;


async function getAdditionAsynchroneParallele() {
 const [nombre1, nombre2] = await Promise.all([
   getNombreAsynchrone1(),
   getNombreAsynchrone2(),
 ]);
 return nombre1 + nombre2;
}
 

Si une fonction contient à la fois du code synchrone et asynchrone, la gestion d’erreur se retrouve souvent dupliquée.


function appelSynchrone() { }         function appelSynchrone() { }            
function appelAsynchrone() { }        async function appelAsynchrone() { }      
function traitementAppel() { }        function traitementAppel() { }            
                                                                               
function run() {                      async function run() {                    
 try {                                 try {                                    
   appelSynchrone();                     appelSynchrone();                      
   return appelAsynchrone()              const result = await appelAsynchrone();
     .then(traitementAppel)              return traitementAppel(result);        
     .catch(e => {                     } catch (e) {                            
       console.log('Error', e);          console.log('Error', e);              
     });                               }                                        
 } catch (e) {                        }                                        
   console.log('Error', e);
 }
}
 

Comment gérer les éventuelles erreurs levées de manière synchrone ?


function appelAsynchrone() {
 /** bloc synchrone */
 console.log('synchrone');
 // erreur synchrone,
 // ce qui casse notre API et les éventuels .then/.catch de l'appelant
 throw new Error('oups');
 return blocAsynchrone();
}
 

Une erreur peut se glisser dans la partie synchrone, levant une exception ce qui ne respecte pas l’API de notre fonction qui est sensée retourner une promesse. La façon habituelle de contourner ce problème est de toujours commencer une fonction asynchrone par Promise.resolve :


function appelAsynchrone() {
 return Promise.resolve()
   .then(() => {
     console.log('sychrone');
     throw new Error('oups'); // l’erreur est correctement encapsulée
   })
   .then(blocAsynchrone);
}
 

Grâce au mot clé async, ce n’est plus la peine de polluer ainsi notre code, une fonction async renvoyant systématiquement une promesse :


async function appelAsynchrone() {
 /** bloc synchrone */
 console.log('synchrone');
 throw new Error('oups'); // l’erreur est bien encapsulée grâce au mot clé async
 return blocAsynchrone();
}
 

Migrer vers async/await

Si vous travaillez déjà avec des promesses, la migration est plutôt triviale : rajoutez le mot clé async aux fonctions retournant une promesse, remplacez les .then par des await, et les .catch par des blocs try/catch. Le code sera plus clair et concis.

Si vous travaillez avec des callbacks, c’est le même travail que pour migrer vers une utilisation de promesses classique: il faut promessifier vos fonctions. Historiquement on se tournait vers Bluebird, mais depuis sa v8.0 Node.js propose une fonction promisify dans le module util.


const { promisify } = require('util');
const fs = require('fs');
const readFileAsync = promisify(fs.readFile);

async function lireMonFichier() {
 try {
   const texteDuFichier
     = await readFileAsync('path/du/fichier', { encoding: 'utf8' });
   console.log('contenu :', texteDuFichier);
 } catch (err) {
   console.log('erreur :', err);
 }
}
 

réf: intro TypeScript
https://blog.cellenza.com/developpement-specifique/web-developpement-specifique/introduction-a-typescript/
réf: JS base de l'ascynchrone
https://www.developpez.net/forums/blogs/676693-yahiko/b1546/bases-lasynchrone-javascript/
réf: Promises en JS / TS
https://javascript.developpez.com/actu/146280/Comprendre-les-Promises-en-JavaScript-TypeScript-article-de-yahiko/
réf: async / await en JS
https://blog.xebia.fr/2017/11/14/asyncawait-une-meilleure-facon-de-faire-de-lasynchronisme-en-javascript/
réf: new TypeScript project
https://blog.bitsrc.io/keep-your-promises-in-typescript-using-async-await-7bdc57041308

CHAPITRE 2 - SCSS

2.1. SASS Syntactically awesome style sheets

La simplicité de CSS est depuis toujours indiscutable. Nos sites deviennent riches et complexes, ciblant un éventail toujours plus large d’appareils et de tailles d’écran, aujourd'hui cette simplicité devient un handicap.

Puisque les navigateurs ne sont pas prêts pour un nouveau CSS, Catlin et Weizenbaum ont conçu une nouvelle syntaxe de feuilles de style permettant d’écrire et de gérer un CSS toujours plus complexe, en utilisant ensuite un préprocesseur (un programme qui fonctionne sur votre ordinateur ou sur un serveur) chargé de traduire cette nouvelle syntaxe intelligente dans le bon vieux CSS que nos navigateurs comprennent.

2.2. Sassy CSS (CSS à la Sass)

SCSS est un "métalangage" de CSS, ce qui signifie que tout ce qui est valide en CSS l'est aussi en SCSS. Contrairement à CSS, le SCSS est un vrai langage de programmation, avec des expressions, des fonctions, des variables, une logique conditionnelle et des boucles. Sass est écrit en Ruby, et distribué via le package manager de Ruby, RubyGems. Si vous connaissez déjà Ruby, vous trouverez sur le site de Sass l'installation.

Ruby était notoirement lent. Vous pouvez maintenant opter pour Libsass Sass sera compilé instantanément. Libsass est un nouveau compilateur Sass, écrit en C. Un moyen simple et rapide de travailler avec Sass, est d'utiliser Scout (Mac et Windows), qui contient Ruby et le compilateur de Sass, et CodeKit (Mac).

Quelle que soit la solution que vous choisirez, elle compilera automatiquement votre SCSS en CSS à chaque fois que vous enregistrerez votre fichier. Le répertoire où se trouvent vos fichiers Sass s’appelle le dossier input (entrée). Vos fichiers CSS processés sont sauvegardés dans un dossier output (sortie). Ces dossiers peuvent être emboîtés l’un dans l’autre; en fait, un schéma typique serait que le dossier input ("scss" par ex.) soit à l'intérieur du dossier feuilles de styles

mon_projet/ index.html css/ style_principal.css scss/ style_principal.scss _mixins.scss _couleurs.scss

Les fichiers dont le nom commence avec un tiret bas (underscore), par exemple "_mixins.scss", sont appelés des partiels. Ils contiennent des morceaux de code SCSS qui doivent être importés dans votre fichier SCSS principal.

La directive @import existe déjà dans CSS, mais en CSS chaque import implique un nouveau chargement par le navigateur. Le nombre de chargements ou appels d'url, plus que la quantité de données, réduit la vitesse de chargement de la page.

Avec Sass quand vos fichiers SCSS sont processés, le contenu des partiels est inséré directement dans le CSS final.

//Syntaxe d’importation dans CSS //Syntaxe d’importation dans Sass @import url(’/partage/global.css’); @import ’/partage/global’;
@import url(’/pages/accueil.css’); @import ’/pages/accueil’;
@import url(’/pages/blog.css’); @import ’/pages/blog’;

2.3. Caractéristiques du SCSS

Imbrication

Sass permet d’imbriquer des règles. Dans un fichier CSS habituel, les règles (sélecteur/propriété/valeur) sont listées à la suite et chaque sélecteur doit inclure tous ses éléments. Avec l’imbrication, nous pouvons écrire un code SCSS qui est à la fois non redondant et plus facile à suivre :


//code CSS habituel                        //code imbriqué                  
body.home .media-unit {                    body.home {                      
  border: 1px solid #ccc;                    .media-unit {                  
  background-color: #fff;                      border: 1px solid #ccc;      
}                                              background-color: #fff;      
body.home .media-unit .right {                 .right {                      
  border-left: 1px solid #ccc;                   border-left: 1px solid #ccc;
}                                                h1 {                        
body.home .media-unit .right h1 {                  font-size: 24px;          
  font-size: 24px;                               }                          
}                                          }}}                    
 

Sass vous permet d’imbriquer les media queries dans d’autres règles, ce qui facilite la visualisation des styles appliqués à un objet donné :


//Imbrication de media query               //Le résultat en CSS                
.container {                               .container {                        
  width: 940px; // Si la largeur             width: 940px;                      
  // de l’écran est inférieure à 940px     }                                    
  @media screen and (max-width:940px) {    @media screen and (max-width:940px) {
    width: auto; }                          .container {                        
}                                             width: auto;                      
                                           }}                                  
 

Déclaration de variable

$typekit-green: "#99cc00"; $typekit-link-color: $typekit-green; a { color: $typekit-link-color; }

Mixins

Les Mixins sont des ensembles de règles que vous pouvez "mixer", dans d’autres règles. Ici on demande à Sass d’appliquer toutes les propriétés contenues dans le mixin texte-surligne-gras, à tous les éléments span situés à l’intérieur de .resultat-surligne


//Exemple de mixin
$couleur-surlignage: #ffa;
@mixin texte-surligne-gras {
  font-weight: bold;
  background-color: $couleur-surlignage;
}
.resultat-surligne {
  span {
    @include texte-surligne-gras;
} }

//Clearfix, version Sass
@mixin clearfix {
  // Pour les navigateurs modernes
  &:before,
  &:after {
    content:"";
    display:table;
  }
  &:after {
    clear:both;
  }

  // Pour IE 6/7 (trigger hasLayout)
  &{
    zoom:1;
  }
}
.group {
  @include clearfix;
}
 

Le sélecteur esperluette (&) est une convention Sass qui signifie “cet élément”. Quand ce code est compilé, Sass remplace tous les symboles "&" par le sélecteur courant, dans ce cas .group.

Fonctions intégrées

Il existe sur le marché de nombreux systèmes de grilles 960px prêts à l’usage, mais la plupart demandent d’ajouter à votre code des noms de classes non sémantiques. Sans parler du fait que pour les utiliser, vous devez charger le CSS du système entier, mais vous n’en utiliserez au final qu’une partie. Plutôt que de définir des noms de classes pour chaque unité de la grille, nous allons créer un mixin qui applique la largeur et les marges correctes.


//définition de la largeur des colonnes et des gouttières
$column-width: 60px;   // 12 colonnes = 720px
$gutter-width: 20px;   // 11 gouttières = 220px

//Simple Grille en Sass
@mixin grid-unit($span) {
  float: left;
  margin-right: $gutter-width;
  width: ($column-width * $span) + ($gutter-width * ($span - 1));
}

//Mise en page
.container {
  @include clearfix;
  @include grid-unit(12);
  float: none;
  margin: 0 auto;
}
.main-content {
  @include grid-unit(8);
}
.sidebar {
  @include grid-unit(4); margin-right: 0;
}
 

Avec les fonctions mathématiques de Sass il est facile de créer des présentations fluides. Ici, j’utilise la formule proposée par Ethan Marcotte (http: //alistapart.com/article/responsive-web-design/) pour créer une version responsive de ma grille de base.


//version responsive de la grille précédente
.container {
// result = target / context
// résultat = cible divisée par contexte
  width: percentage(940px / 960px);
  .main-content {
// cet élément est imbriqué dans .container,
// donc son contexte est 940px
    width: percentage(620px / 940px);
  }
  .sidebar {
    width: percentage(300px / 940px);
  }
}

//Transformation et ajustement des couleurs
$base-link-color: #00f;
a {
  color: $base-link-color;
}
a:visited {
// réduit la luminosité (en termes HSL)
// de 50%, sans modifier la teinte ni la saturation
  color: darken($base-link-color, 20%);
}
figcaption {
// Génère une valeur rgba() de couleur avec 50% d’opacité
  background-color: transparentize(#fff, 50%);
}
 

réf: intro SCSS
https://la-cascade.io/se-lancer-dans-sass/
réf: SCSS Built-In Functions
http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html

CHAPITRE 3 - React Généralités

React repose sur la composition de composants. Les composants sont imbriquebles, réutilisables, et testables. La version 0.14 se détache du DOM. React n’est plus une façon de représenter des éléments du DOM mais c’est une librairie qui permet de créer des composants. Le rendu en élément DOM n’est qu’une implémentation et peut être étendu à d’autres environnements. C’est comme ça qu’est né ReactNative qui propose d’utiliser React mais cette fois pour créer des interfaces natives pour IOS et Android.

L’application universelle (isomorphique) est née du besoin d’avoir un chargement initial qui renvoie l’application chargée avec les données. Le premier rendu de l’application est fait côté serveur, puis côté client, on retrouve une Single Page Application (SPA) traditionnelle. Pour que cela fonctionne, il faut que le code source exécuté sur le navigateur soit également exécutable sur le serveur. Le langage exécuté par le navigateur est le javascript, il faut que le serveur exécute également du javascript, comme c’est le cas avec Node.js.

Le DOM virtuel est un concept implémenté dans React. Il s’agit d’une représentation interne en javascript de l’arbre du DOM qui n’est pas le “vrai” DOM du navigateur. React va pouvoir agir sur cette abstraction et non pas directement sur le DOM du navigateur. Le DOM virtuel rend possible la génération d’une page HTML sans qu’elle soit couplée à un “vrai” DOM donc à un navigateur. Il est ainsi possible de générer des pages coté serveur. De plus, le DOM virtuel permet d’optimiser les accès au “vrai” DOM. Lors d’un changement, React établit à partir de sa représentation virtuelle le différentiel optimal à appliquer au “vrai” DOM. Les modifications sont appliquées en une fois.

Contrairement à ses concurrents (AngularJS, Ember ou Backbone), React n’est pas un framework. Il s’agit d’une librairie permettant de créer des composants, il n’y a pas de cadre pour structurer l'application (pas de service, de contrôleur ou de modèle). Facebook a publié le modèle qu’ils utilisent en interne : Flux, sans toutefois imposer une implémentation particulière. Il existe donc plusieurs mplémentations du modèle Flux (+/- proche de la description originelle).


https://blog.octo.com/pourquoi-sinteresser-a-react/

3.1. ES2015

L’univers React, comme ceux de la plupart des frameworks front-end, utilise désormais intensément des avancées récentes de JavaScript. Ces avancées sont apparues à partir de 2015, dans la version du langage appelée ES2015 (également connue sous le nom ES6). Voici les principales nouveautés que vous rencontrerez fréquemment à l’usage.

Classes


class Screen extends Component {
  constructor (props) {
    super(props)
    this.state = { loginState: 'logged-out' }
  }
  render () {
    // …
  }
}
 

Fonctions fléchées

Le premier avantage des fonctions fléchées est la concision de leur syntaxe.

Traditionnellement, une fonction se déclare avec le mot-clé function suivi de la signature puis du bloc de fonction, au sein duquel tout return doit être explicite. Les fonctions fléchées ne nécessitent pas de mot-clé déclaratif. Lorsqu’elles sont intégralement constituées d’une expression à renvoyer, les accolades du bloc de fonction peuvent être omises, ainsi que le mot-clé return . Entre la signature et le bloc ou l’expression, on trouve une fat arrow, à savoir => .


const adults = [], minors = []       const adults = [], minors = []          
people.forEach(function (person) {   people.forEach((person) => {            
  if (person.age >= 18) {              if (person.age >= 18) {              
    adults.push(person)                  adults.push(person)                
  } else {                             } else {                              
    minors.push(person)                  minors.push(person)                
  }                                    }                                    
})                                   })                                      
people.map(function (person) {                                              
  return person.firstName            people.map((person) => person.firstName)
})
 

C’est une syntaxe raccourcie très pratique pour les prédicats (fonctions qui renvoient vrai ou faux selon leurs arguments, et qui sont par exemple passées aux méthodes filter , every et some ) et les mappers (fonctions de transformation telles que celles passées à map ). Dans ce deuxième cas, elles seront par exemple très utiles pour « réduire le bruit » dans les méthodes render de nos composants React.

Historiquement, JavaScript ne proposait qu’une manière de déclarer des fonctions
le mot-clé function . Les fonctions ainsi déclarées avaient un comportement spécifique à JavaScript, fondé non pas sur l’endroit de leur déclaration dans le code, mais sur la syntaxe utilisée pour les appeler. Cette syntaxe déterminait notamment, au sein de la fonction, la valeur de this.

Le fait de pouvoir appeler une fonction en contrôlant le rôle de this à l’intérieur a de nombreux avantages, mais comporte aussi un inconvénient : dans les fonctions de rappel (callbacks) notamment, on voulait souvent pouvoir continuer à utiliser le this en vigueur « à la ligne du dessus », comme pour n'importe quel identifiant ordinaire.

Les fonctions fléchées contournent ce problème en ne redéfinissant aucun identifiant lors de leur invocation (pas même this ).


const name = 'Extérieur'
const obj = {
  name: 'Intérieur',
  runGreet () {
    // Ici, this.name est bien "Intérieur"
    setTimeout(function () {
      // Ici, this est soit l’objet global (mode laxiste de JS),
      // soit null (mode strict de JS)
    }, 0)
  }
}
obj.runGreet()


const name = 'Extérieur'
const obj = {
  name: 'Intérieur',
  runGreet () {
    // Ici, this.name est bien "Intérieur"
    setTimeout(() => {
      // Ici, this n’est pas redéfini par la fonction,
      // car c’est une fonction fléchée : comme n’importe
      // quel identifiant, il est donc recherché dans les
      // portées englobantes, et trouvé au niveau de
      // runGreet, c’est donc aussi "Intérieur".
    }, 0)
  }
}
obj.runGreet()
 

Cette capacité à ne pas interférer avec le this en vigueur les rendra inestimables pour les méthodes render de nos composants React... vous verrez !

Déstructuration

La déstructuration nous permet d’aller rapidement chercher plusieurs propriétés au sein d’un objet, ou plusieurs cellules au sein de n’importe quel objet itérable (comme un tableau) sans avoir à multiplier les déclarations ou affectations.


// À l'ancienne                        // Déstructuration basée sur les noms  
const firstName = this.props.firstName const { firstName, lastName, onClick }
const lastName = this.props.lastName    = this.props
const onClick = this.props.onClick    
 

Si on découpe un texte « prénom nom » en deux. Au lieu de faire :


// À l'ancienne                       // Déstructuration basée sur les positions
const names = fullName.split(' ')     const [firstName, lastName]
const firstName = names[0]             = fullName.split(' ')
const lastName = names[1]

const circle = { label: '', radius:2 };
const circleArea = ({radius}, {precision = 2} = {}) =>
  (PI * radius * radius).toFixed(precision);
console.log( circleArea(circle, { precision: 5 }));

const [first, second,, forth] = [10, 20, 30, 40];

// Exemple avec React
const {Component, Fragment, useState} = require('react');
useState();

const [first, ...restOfItems] = [10, 20, 30, 40];
console.log(first);       // 10
console.log(restOfItems); // [20, 30, 40]

const data = {
  temp1:'001',
  temp2:'002'
  firstName: 'John',
  lastName: 'Doe'
}
const {temp1, temp2, ...person} = data;
temp1:001, temp2:002, person: { firstName: 'John', lastName: 'Doe' }
const newObject = { ...person }; copie superficielle, les tableaux sont partagés
 

Imports et exports

Nous découperons notre application en modules, qui sont autant de fichiers sources séparés. Pour cela, nous aurons recours à la syntaxe officielle des ES Modules. Nous rendrons visibles les parties de nos modules que nous souhaitons à l’aide d’export et nous irons chercher les parties qui nous intéressent dans d’autres modules à l’aide d’import. Il est également possible de déclarer un export « par défaut ». Dans ce cas nous pouvons l’importer sous le nom que l’on veut, sans avoir à recourir aux accolades :


// Au sein du fichier textUtils.js
export function countWords (text) {
  return text.split(/\W+/u).filter(Boolean).length
}
export function normalizeSpacing (text) {
  return text.replace(/\s+/u, ' ').trim()
}
 

// Au sein d’un fichier main.js, dans le même répertoire : import { countWords } from './textUtils' console.log(countWords('Hello world, this is nice!'))

réf: ES 2015
https://openclassrooms.com/fr/courses/4664381-realisez-une-application-web-avec-react-js/4664806-modernisez-votre-javascript-avec-es2015
réf: ES 2015 arrays
https://www.robinwieruch.de/javascript-map-array/

3.2. Environnement de développement

npm est le gestionnaire de modules de Node. C’est à la fois un outil en ligne de commande (la commande npm ) et un référentiel en ligne de modules. On y trouve plus de 650 000 modules couvrant à peu près tous les besoins imaginables. La commande permet d'installer et de mettre à jour les modules que l'on sélectionne pour nos projets. Une version de npm est livrée par défaut avec Node.

Webpack permet de modulariser les fichiers et de concaténer les librairies js dans un seul fichier (bundle) pour optimiser le nombre d'appels. Actuellement il n'existe pas de système d'inclusion coté navigateur. Si on fait par exemple


  //app.js syntaxe node.js              //log.js syntaxe node.js
  var log = require('./log.js');        module.export = function(value) {
  log('Salut');                           console.log(value); }
 
  //app.js syntaxe ES2015               //log.js syntaxe ES2015
  import log from './log.js';           var log = function(value) {...}
  log('Salut');                         export default log
 

Le navigateur ne connais pas require, et c'est la que webpack rentre en jeux.

Création d'un projet frontend

Créer un dossier de projet, npm init, npm install webpack webpack-cli --save-dev Cela vat créer le binaire "./node_modules/.bin/webpack". Ce binaire sera capable de transformer les sources en quelque chose de connu par le navigateur web.


//project                  //webpack.config.js
  webpack-demo             const path = require('path');
  |- package.json          module.exports = {
+ |- webpack.config.js       entry: './src/index.js',
  |- /dist                   output: {
    |- index.html              filename: 'main.js',
  |- /src                      path: path.resolve(__dirname, 'dist'),
    |- index.js              },
                             optimization: { minimize: false },
                             mode: 'development',
                           };

"./node_modules/.bin/webpack" --config webpack.config.js --optimize-minimize webpack peut vérifier les modifications de code et regénérer automatiquement, en activant l'option --watch (ou watch: true, après entry dans webpack.config.js .

Modifier le fichier package.json ainsi, afin de pouvoir lancer "npm run build". Et "webpack serve" pour lancer le site sur un serveur web local par "npm start".


    "scripts": {
       "test": "...",
       "build": "webpack",
       "start": "webpack serve"
 

La commande générera le script dist/main.js, qui contient l'ensemble des scripts concaténés, il ne restera plus qu'à le charger à la fin du fichier index.html .


  <script src="../dist/main.js"></script>
  </body>
  </html>
 

Pour générer un code compatible avec les anciens navigateurs (var et non let), il faut mettre en place un loader comme babel, pour transformer le code. Depuis le site de babel loader, copier et executer la commande pour l'installer : npm install -D babel-loader @babel/core @babel/preset-env . Babel connais les extentions configurés par défaut, ce qui permet d'éviter de les saisir dans les import. Si on met pas de chemin relatif il cherche le package dans node_modules.


  //webpack.config.js                               //.babelrc
  const path = require('path');                     {
  module.exports = { ...                              "presets": [
    mode: 'development',                                ["env", {
    module: {                                             "targets": {
      rules: [                                              "browsers": [...]
        test:/\.js$/,                                     }
        exclude: /(node_modules|bower_components)/,     }]
        uses: ['babel-loader']                        ]
      ]                                             }
    }
  };
 

Il est possible de créer des variables d'environnement dans package.json ex: "build": "NODE_ENV=dev webpack" et de les lire depuis n'importe quel fichier ex: const dev = process.env.NODE_ENV === "dev"
Le devtool de webpack permet d'activer le source maps, pour retrouver le fichier source lors du debuguage et non uniquement le script bundle. ex: (webpack.config.js) devtool: dev ? "cheap-module-eval-source-map" : false, Le lazyLoading permet de charger du javascript, à la demande; modifier .babelrc "presets": [["env", { "modules":false,.. ], function () { "plugins": ["syntax-dynamic-import"] import('jquery').then(($) => { et rajoutter dans la section output de webpack.config.js : publicPath: "/dist"

réf: Comprendre Webpack
https://www.youtube.com/watch?v=iv4hu2Jz1w4

https://morioh.com/p/06a6e8d074a3

Création d'un projet react

npm install --global npm npm install --global create-react-app create-react-app my-app

Cette commande va créer un dossier my-app dans le répertoire où nous avons lancé celle-ci. Voici l’arborescence que la commande va générée :


.
|-- README.md                  On  y trouve le point  d’entrée  de l’application 
|-- package.json               (src/index.js), un premier composant (src/App.js)
|-- public                     avec ses styles à part et ses tests, ainsi qu'une 
|   |-- favicon.ico            page  de support pour le tout (public/index.html)
|   |-- index.html             l’ensemble  des fichiers sources  (JS, CSS, etc.) 
|   |-- manifest.json          devront être dans  "src/" ,  sans quoi le Webpack 
|-- src                        utilisé en interne ne les verra pas.
|   |-- App.css
|   |-- App.js                 Commandes disponibles:
|   |-- App.test.js            npm run start | build | test
|   |-- index.css
|   |-- index.js               Installer un module dans le projet, ex:
|   |-- logo.svg               npm install --save-dev parcel-bundler
|   |-- registerServiceWorker.js
|-- yarn.lock

Quand on lance npm run start, le CLI nous permet d'obtenir une application distribuée par un petit serveur node et avec hot reload (c'est-à-dire qu'elle se met à jour toute seule avec chaque sauvegarde).

package.json contient toutes vos dépendances et des scripts. Quand un module est installé avec npm install, il s'ajoutte dans ce fichier, et télécharge le module dans le dossier node_modules. La commande npm install permet de télécharger et d'installer l'ensemble des dépendances listés dans package. json . On peut ainsi exclure ce dossier du répository.

Dans node_modules/react-scripts/config se trouvent les fichiers de configuration de Webpack que CLI a créé pour nous. Il y en a un pour le serveur node de développement webpackDevServer.config.js, un pour l'application en développement webpack.config.dev.js, et un pour la production webpack.config.prod.js.

Webpack a également une API qui permet de gérer le code une fois qu'il l'a chargé, il s'agit des loaders. Par exemple, Babel a un loader qui permet de dire à webpack que lorsqu'il charge un fichier JavaScript il doit le passer dans Babel pour le transpiler avant de créer un bundle.

Heureusement, le CLI de React gère toute la configuration pour utiliser Webpack dans un projet React. Cela serai très long et complexe de le faire manuellement.

Installation manuelle

React utilise un certain nombre d'outils en interne qu'il gère et configure de façon transparente : - Webpack qui est responsable du packaging de l'application - Babel qui se charge de compiler le javascript de l'application - ESLint qui analyse le code de notre application à la recherche d'erreurs

Pour lancer ESLint sur un projet: npm install eslint --global eslint . --ignore-pattern node_modules/ --ignore-pattern build/

React est une simple librairie qui peut être intégrée dans une application existante et est accessible via un CDN :

Ou pour une version minifiée :

Pour utiliser le typescript, vous devez l'installer dans le nouveau projet. >npm install typescript Un fichier de configuration vide pour typescript se crée à la racine du projet. tsconfig.json, par ex:


{
  "compilerOptions": {
    "target": "es5",
    "allowJs": true,         --> permet le javascript
    "esModuleInterop": true, --> permet import React from 'react'
    "jsx": "preserve"        --> permet le jsx javascript avec des balises
    "noImplicitAny": true,   --> evite l'erreur suivante: "Could not find ...js'
                                 implicitly has an 'any' type." ts(7016)
  }
}

La liste des options de compilation pour le typescript est disponible ici:
https://www.typescriptlang.org/docs/handbook/compiler-options.html

Autre modules intéressants: >npm install node-sass --> permet d'utiliser scss >npm install react-redux redux >npm install connected-react-router react-router react-router-dom history cf: https://redux.js.org/recipes/configuring-your-store/
>npm install react-intl --> permet d'utiliser la localisation

npm install installe et sauve les dépendances dans le fichier package.json -P, --save-prod: valeur par defaut -D, --save-dev: section devDependencies. -O, --save-optional: section optionalDependencies. --no-save: dépendance non stockée. -E, --save-exact: dépendance configuré avec le numéro de version exacte. -B, --save-bundle: section bundleDependencies. npm uninstall désinstalle et retire les dépendances dans le fichier package.json

Déploiement d'un projet

Pour déployer: npm run build Créer une version optimisée de l'application dans le répertoire build. Le projet se construit en supposant qu'il est hébergé à la racine du serveur. Vous pouvez contrôler cela avec le champ homepage du fichier package.json ex:


{
  "name": "sfournis",
  "version": "0.1.0",
  "private": true,
  "homepage": "/jade/react",
 

Par défaut un projet react fonctionne sur Edge, mais plus sur internet explorer. Pour le faire fonctionner avec internet explorer 11, il faut réaliser les étapes suivantes. Pour ma part, cela fonctionne uniquement après déploiement... - Modifier package.json "development": [ "ie 11", "last 1 chrome version", ... - Ajouter ce code ligne 1 et 2 de index.js du dossier src import 'react-app-polyfill/ie11'; import 'react-app-polyfill/stable'; - Supprimer le dossier node_modules - lancer npm install react-app-polyfill IE ne supporte pas quelques fonctionnalités de ES6+ par défaut. voir:
https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill#polyfilling-other-language-features

Inclure jQuery dans react:

Voici la procédure à suivre pour inclure jQuery à votre application react - npm install jquery - npm install @types/jquery - créer un fichier loader.js avec cette commande: window.$ = window.jQuery = require('jquery') - import '/loader.js' Il est maintenant possible d'utiliser jQuery partout dans le code.

//SomeReact.js


class SomeClass extends React.Compontent {
...
handleClick = () => {
   $('.accor > .head').on('click', function(){
      $('.accor > .body').slideUp();
      $(this).next().slideDown();
   });
}
...
export default SomeClass
 

Compilateur tsx

L'évolution du compilateur TypeScript React peut enduire des problèmes de compatibilité de code, ex: Type 'string | void' is not assignable to type 'string | undefined'. Type 'void' is not assignable to type 'string | undefined'.ts(2322)

il suffit de repasser en version 3.3.3333 au lieu de 3.6.3

Structure d'un projet

TSX TypeScript JSX Javascript SCSS Sassy css

  1. Install NodeJs, check system PATH with correct path for nodejs Set NPM path into Visual Studio Tools/Options Project and Solutions, Web Package Management, External Web Tools Add C:\Program Files\nodejs, make sure it is above line $(VSINSTALLDIR)\Web\External

  2. Install VsCode let check box "add Path" checked

  3. Launch chrome https://code.visualstudio.com/docs/setup/additional-components
    and download : Debugger for Chrome or from Visual Studio code click the last icon and search for "Debugger for Chrome" https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
    and click "Add to Chrome".

  4. Go to Git repositorie Create a folder, Git clone "develop" branch

  5. Setup the libraries on source application folder type bash npm ci some web app is pointing to mock api... so we need to have mock api running in simultaneously npm run mock-api

  6. Launch Application install in IIS http://localhost:4000/
    bash npm start Run in Google Chrome, http://localhost:4000
    F12 to see console and Network in network we can see post and get queries

  7. Edit the code: >code . Then you can launch the application from VsCode: Terminal, new terminal, bash npm start Then when you save a file, you can see on the fly compilation errors. Stop application by tipping Ctrl-C.

  8. To launch the unit test type this place debugger in code to add a breakpoint bash npm run test:debug launch in chrome: "chrome://inspect" chrome will detect your test script. Click on corresponding RemoteTarget inspect link This will open chrome debug environnement. then type F8 to go to debugger tag, then F10 to switch on tsx file saving the code automaticaly ruload debugger (if not run stopped, type F8)

  9. Project

    package.json

  • dependencies : list of dependencies
  • devDependencies : used for developing, are not required to run application
  • scripts : user defined command into project launch by npm run "start" "start": "cross-env PORT=4000 react-scripts start" "e2e" -> end to end tests (integration tests) "test" -> unit tests "husky" "hooks" "pre-commit": "lint-staged" "lint" -> format the code
  • jest library for unit tests framework

    tsconfig.json : typeScript configuration file

    ts is a framework on top of javascript will be transpiled to html/js All js code is available on ts.

    cypress.json library for end to end test with selenium framework

    .env file : REACT_APP_CONFIG_URL=/example-config.json

    root folder ("~/") is "public/". self folder ("/") is "./"

    public/example-config.json

    { "apiUrl": "https://dev.azurewebsites.net/api/", "tenantId": "Tenant-Id-0" } change apiUrl to switch to local api: http://localhost:5020/api

    src/index.tsx :


   import { bootstrap } from './bootstrap';
   ...
   axios // is an HTTP client library
   .get(process.env.REACT_APP_CONFIG_URL!) // read config file
   .then(response => bootstrap(response.data)) // when finished
   .catch(err  // when errors
 

src/bootstrap.tsx


   import App from './app/app'; // no extension here
   ...
   // render the app
   ReactDOM.render(
     <Provider store={store}>
       <App tenantId={config.tenantId} />
     </Provider>,
     document.getElementById('root')
   );
 

src/app/app.tsx : do localisation render here


   <IntlProvider support multilanguages and localisation
   
   Project is monopage, routes allows code spliting. Each routes defined on
   routes.tsx expect a .routes.tsx in the folder
   src/app/modules/routes.tsx
   // load index.ts from home folder
   const Home = withLazy(() => import('./home'));
   ...
       <Switch>
         <Route
           path="/product-administration"
           component={ProductAdministrationRoutes}
         />
         <Route
           path="/catalogue-management"
           component={CatalogueManagementRoutes}
         />
 

src/app/modules/catalogue-management/catalogue-management.routes.tsx


   // load index.ts from catalogues folder
   const Catalogues = withLazy(() => import('./catalogues'));
   ...
     <Route
       exact={true}
       path="/catalogue-management/catalogue"
       component={Catalogues}
     />

   Export sub-component
 

src/app/modules/catalogue-management/catalogues/index.ts


     import { CatalogueContainer } from './catalogue.container';
     export default CatalogueContainer;
 

For saving data: src/app/modules/catalogue-management/catalogues/catalogue.container.tsx

For expose to view (logic for UI): src/app/modules/catalogue-management/catalogues/catalogue.view.tsx

src/app/shared/ui/add-edit-modal : reusable components

src/app/shared/ui/form/text-input/text-input.component.tsx ...rest : react default parameters


   export interface TextInputProps extends InputProps {
     onChange?: (value: ChangeEvent<HTMLInputElement>) => void;
     id?: string;
   }
 

properties are not mandatory we can define one or more of them


   <text-input id=() onChange=()>
   <TextInput
    className={classnames(searchInputStyles.searchInput, inputClassName)}
    {...props}
  />
 

src/app/shared/api : all backend API connexion

For UI model src/app/shared/api/manufacturers/manufacturer.model.ts

For connexion to Backend Api src/app/shared/api/manufacturers/manufacturer.api.ts

For mapping between api and model src/app/shared/api/manufacturers/manufacturer.mapper.ts

src/i18n/locale-data : ressources file

src/styles/ : sassy css

src/test/spec/app/modules Unit test

réf: react instances
https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html
réf: react router et deploiement
https://dev.to/sumitkharche/how-to-deploy-react-application-on-iis-server-1ied
réf: frameworks and react
https://putaindecode.io/articles/introduction-a-react/
réf: react intro props, custom hooks
https://reactjs.org/tutorial/tutorial.html
réf: new react app
https://openclassrooms.com/fr/courses/4664381-realisez-une-application-web-avec-react-js/4664801-demarrez-facilement-avec-create-react-app

https://www.accesscodeschool.fr/2019/01/17/apprendre-reactjs-partie-2-creer-rapidement-une-app-react/

https://www.smooth-code.com/articles/creer-un-projet-react-avec-parcel
réf: debug jest test
https://artsy.github.io/blog/2018/08/24/How-to-debug-jest-tests/

3.3. Introduction à JSX

Pour afficher un titre qui dit « Bonjour, monde ! » en React.


const name = 'monde';                  function formatName(user) {            
const element =                          return user.first + ' ' + user.last; }
 <h1>Bonjour, {name}</h1>;                                                    
                                       const user = {                          
ReactDOM.render(                         first: 'Kylian',                      
  element,                               last: 'Mbappé' };                    
  document.getElementById('root')                                              
);                                     const element = (                      
                                         <h1>                                  
                                           Bonjour, {formatName(user)} !      
                                         </h1>                                
                                       );
 

Cette syntaxe JSX est une extension syntaxique de JavaScript, qui produit des « éléments » React. Au lieu de séparer artificiellement les technologies en mettant le balisage et la logique dans des fichiers séparés, React sépare les préoccupations via des unités faiblement couplées appelées « composants », qui contiennent les deux. Les accolades contiennent du code JavaScript.

Après la compilation, les expressions JSX deviennent de simples appels de fonctions JavaScript, dont l’évaluation renvoie des objets JavaScript. JSX empêche les attaques d’injection car toutes les valeurs sont echappées.

Ça signifie que vous pouvez utiliser JSX à l’intérieur d’instructions if ou de boucles for, l’affecter à des variables, l’accepter en tant qu’argument, et le renvoyer depuis des fonctions :


function getGreeting(user) {
  if (user) {
    return <h1>Bonjour, {formatName(user)} !</h1>;
  }
  return <h1>Bonjour, Belle Inconnue.</h1>;
}
 

Spécifier des attributs en JSX

Ne mettez pas de guillemets autour des accolades quand vous utilisez une expression JavaScript dans un attribut. Vous pouvez utiliser soit des guillemets (pour des valeurs textuelles) soit des accolades (pour des expressions), mais pas les deux à la fois pour un même attribut.


const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
 

Dans la mesure où JSX est plus proche de JavaScript que de HTML, React DOM utilise la casse camelCase comme convention de nommage des propriétés, au lieu des noms d’attributs HTML. Par exemple, class devient className en JSX, et tabindex devient tabIndex.

Spécifier des éléments enfants en JSX


const element = (
  <div>
    <h1>Bonjour !</h1>
    <h2>Content de te voir ici.</h2>
  </div>
);
 

Retranscription en React

Babel compile JSX vers des appels à React.createElement(). Ces deux exemples sont identiques :


const element = (                   const element = React.createElement(
  <h1 className="greeting">           'h1',                            
    Bonjour, monde !                  {className: 'greeting'},          
  </h1>                               'Bonjour, monde !'                
);                                  );                                  
 

React.createElement() effectue des vérifications pour aider à écrire sans bug, mais pour l’essentiel il crée un objet qui ressemble à ceci :


// Remarque : cette structure est simplifiée
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Bonjour, monde !'
  }
};
 

Ces objets sont appelés des « éléments React ». Vous pouvez les considérer comme des descriptions de ce que vous voulez voir sur l’écran. React lit ces objets et les utilise pour construire le DOM et le tenir à jour.

3.4. Le rendu des éléments

Un élément décrit ce que vous voulez voir à l’écran. Contrairement aux éléments DOM d’un navigateur, les éléments React sont de simples objets. React DOM se charge de mettre à jour le DOM afin qu’il corresponde aux éléments React.

Pour faire le rendu d’un élément React dans un nœud DOM racine, passez les deux à la méthode ReactDOM.render() :

const element =

Bonjour, monde

; ReactDOM.render(element, document.getElementById('root'));

Mettre à jour un élément affiché

Les éléments React sont immuables. Une fois votre élément créé, vous ne pouvez plus modifier ses enfants ou ses attributs. Un élément est comme une image d’un film à un instant T : il représente l’interface utilisateur à un point précis dans le temps. Avec nos connaissances actuelles, la seule façon de mettre à jour l’interface utilisateur est de créer un nouvel élément et de le passer à ReactDOM.render().


function tick() {
  const element = (
    <div>
      <h1>Bonjour, monde !</h1>
      <h2>Il est {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
 

React DOM compare l’élément et ses enfants avec la version précédente, et applique uniquement les mises à jour DOM nécessaires pour refléter l’état voulu. Même si nous créons à chaque seconde un élément décrivant l’arborescence complète de l’interface utilisateur, seul le nœud texte dont le contenu a été modifié est mis à jour par React DOM.

3.5. Composants et props

Les composants vous permettent de découper l’interface utilisateur en éléments indépendants et réutilisables, vous permettant ainsi de considérer chaque élément de manière isolée. Conceptuellement, les composants sont comme des fonctions JavaScript. Ils acceptent des entrées quelconques (appelées « props ») et renvoient des éléments React décrivant ce qui doit apparaître à l’écran.

On peut définir un composant avec une fonction JavaScript, ou en utilisant une classe ES6. Les deux composants ci-dessus sont équivalents pour React.


function Welcome(props) {
  return <h1>Bonjour, {props.name}</h1>;
}

class Welcome extends React.Component {
  render() {
    return <h1>Bonjour, {this.props.name}</h1>;
  }
}
 

Lorsque React rencontre un élément représentant un composant défini par l’utilisateur, il transmet les attributs JSX à ce composant sous la forme d’un objet unique. Nous appelons cet objet « props ».


function Welcome(props) {
  return <h1>Bonjour, {props.name}</h1>; }

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);
 

React considère les composants commençant par des lettres minuscules comme des balises DOM.

__Composition de composants __

Les composants peuvent faire référence à d’autres composants dans leur sortie. Ça nous permet d’utiliser la même abstraction de composants pour n’importe quel niveau de détail. Un bouton, un formulaire, une boîte de dialogue, un écran : dans React, ils sont généralement tous exprimés par des composants.

Par ex, nous pouvons créer un composant App qui utilise plusieurs fois Welcome :


function Welcome(props) {
  return <h1>Bonjour, {props.name}</h1>;  }

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
 

En règle générale, les nouvelles applications React ont un seul composant App à la racine. En revanche, si vous intégrez React à une application existante, vous pouvez commencer tout en bas par un petit composant comme Button et remonter progressivement la hiérarchie des vues.

Que vous déclariez un composant sous forme de fonction ou de classe, il ne doit jamais modifier ses propres props. Ces fonctions sont dites « pures » parce qu’elles ne tentent pas de modifier leurs entrées et retournent toujours le même résultat pour les mêmes entrées.

React est plutôt flexible mais applique une règle stricte : Tout composant React doit agir comme une fonction pure vis-à-vis de ses props.

3.6. Etat et cycle de vie


function Clock(props) {
  return (
    <div>
      <h1>Bonjour, monde !</h1>
      <h2>Il est {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}
function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}
setInterval(tick, 1000);
 

Dans des applications avec de nombreux composants, il est très important de libérer les ressources utilisées par les composants quand ils sont détruits. Nous pouvons déclarer des méthodes spéciales sur un composant à base de classe pour exécuter du code quand un composant est monté et démonté :


class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Bonjour, monde !</h1>
        <h2>Il est {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
 

Utiliser l’état local correctement

Il y a trois choses que vous devriez savoir à propos de setState().

1) Ne modifiez pas l’état directement, utilisez setState()

2) Les mises à jour de l’état peuvent être asynchrones this.props et this.state peuvent être mises à jour de façon asynchrone, vous ne devez pas vous baser sur leurs valeurs pour calculer le prochain état. Utilisez la seconde forme de setState() qui accepte une fonction à la place d’un objet. Cette fonction recevra l’état précédent comme premier argument et les props au moment de la mise à jour comme second argument :

// Erroné this.setState({ counter: this.state.counter + this.props.increment, }); // Correct this.setState((state, props) => ({ counter: state.counter + props.increment })); // Correct this.setState(function(state, props) { return { counter: state.counter + props.increment }; });

3) Les mises à jour de l’état sont fusionnées

3.7. Gérer les événements

La gestion des événements pour les éléments React est très similaire à celle des éléments du DOM. Il y a tout de même quelques différences de syntaxe :

  • Les événements de React sont nommés en camelCase plutôt qu’en minuscules.
  • En JSX on passe une fonction comme gestionnaire d’événements.
  • On ne peut pas renvoyer false pour empêcher le comportement par défaut. Vous devez appeler explicitement preventDefault.

<button onclick="activateLasers()">   <button onClick={activateLasers}>
  Activer les lasers                    Activer les lasers              
</button>                             </button>                        

<a href="#" onclick="console.log('Le lien a été cliqué.'); return false">
  Clique ici
</a>
 

function ActionLink() {
  // syntaxe jquery
  function handleClick(e) {
    e.preventDefault();
    console.log('Le lien a été cliqué.');
  }
  return (
    <a href="#" onClick={handleClick}>
      Clique ici
    </a>
  );
}
 

Lorsque vous utilisez React, vous n’avez généralement pas besoin d’appeler la méthode addEventListener pour ajouter des écouteurs d’événements (event listeners, NdT) à un élément du DOM après que celui-ci est créé. À la place, on fournit l’écouteur lors du rendu initial de l’élément.

Lorsque on définit un composant en utilisant les classes ES6, il est d’usage que le gestionnaire d’événements soit une méthode de la classe.


class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // Cette liaison est nécéssaire afin de permettre
    // l'utilisation de `this` dans la fonction de rappel.
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);
 

En JSX, vous devez être prudent·e avec l’utilisation de this dans les fonctions de rappel. En JavaScript, les méthodes de classes ne sont pas liées par défaut. Si vous oubliez de lier this.handleClick et l’utilisez dans onClick, this sera undefined quand la fonction sera appelée.

Si vous ne souhaitez pas utiliser bind, vous avez deux alternatives possibles.


  handleClick = () => {
    console.log('this vaut :', this);
  }

  ou

  render() {
    // Cette syntaxe nous assure que `this` est bien lié dans la méthode handleClick
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Clique ici
      </button>
    );
  }
 

Au sein d’une boucle, il est courant de passer un argument supplémentaire à un gestionnaire d’événements. Par exemple, si id représente la ligne sélectionnée, on peut faire au choix :


<button onClick={(e) => this.deleteRow(id, e)}>Supprimer la ligne</button>
<button onClick={this.deleteRow.bind(this, id)}>Supprimer la ligne</button>
 

3.8. Affichage conditionnel

En React, vous pouvez concevoir des composants distincts qui encapsulent le comportement voulu. Vous pouvez alors n’afficher que certains d’entre eux, suivant l’état de votre application.

L’affichage conditionnel en React fonctionne de la même façon que les conditions en Javascript. On utilise l’instruction Javascript if ou l’opérateur ternaire pour créer des éléments représentant l’état courant, et on laisse React mettre à jour l’interface utilisateur (UI) pour qu’elle corresponde.

Empêcher l’affichage d’un composant

Dans de rares cas, vous voudrez peut-être qu’un composant se masque alors même qu’il figure dans le rendu d’un autre composant. Pour ce faire, il suffit de renvoyer null au lieu de son affichage habituel.

3.9. Listes et clés

La méthode map() prends un tableau de nombres et double leurs valeurs.


const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
 

Ce code affiche [2, 4, 6, 8, 10] dans la console.

Transformer un tableau en une liste d’éléments est presque identique avec React.

Afficher plusieurs composants

On peut construire des collections d’éléments et les inclure dans du JSX. Ci-dessous, on itère sur le tableau de nombres en utilisant la méthode JS map(). On retourne un élément

  • pour chaque entrée du tableau.


    const numbers = [1, 2, 3, 4, 5];
    const listItems = numbers.map((number) =>
      <li>{number}</li>
    );
    ReactDOM.render(
      <ul>{listItems}</ul>,
      document.getElementById('root')
    );
     

    ...

    3.10. Formulaires

    Les formulaires HTML fonctionnent un peu différemment des autres éléments du DOM en React car ils possèdent naturellement un état interne. Dans la plupart des cas, la soumission est géré avec une fonction JavaScript, qui accède aux données saisies par l’utilisateur. La manière classique de faire ça consiste à utiliser les « composants contrôlés ». En React, l’état modifiable est stocké dans la propriété state des composants, il est mis à jour uniquement avec setState(). Un champ de formulaire dont l’état est contrôlé de cette façon par React est appelé un « composant contrôlé ». handleChange est déclenché à chaque frappe. Ça permet de modifier ou valider facilement, à la volée, les saisies de l’utilisateur.


    class NameForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {value: ''};

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }

      handleChange(event) {
        this.setState({value: event.target.value.toUpperCase()});
      }

      handleSubmit(event) {
        alert('Le nom a été soumis : ' + this.state.value);
        event.preventDefault();
      }

      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Nom :
              <input type="text" value={this.state.value}
                onChange={this.handleChange} />
            </label>
            <input type="submit" value="Envoyer" />
          </form>
        );
      }
    }
     

    textarea en React


    <textarea value={this.state.value} onChange={this.handleChange} />
     

    select en React


    constructor(props) {
        super(props);
        this.state = {value: 'coconut'};
     

    <select value={this.state.value} onChange={this.handleChange}>
      <option value="grapefruit">Pamplemousse</option>
      <option value="lime">Citron vert</option>
      <option value="coconut">Noix de coco</option>
      <option value="mango">Mangue</option>
    </select>
     

    Gérer plusieurs saisies


      handleInputChange(event) {              C’est équivalent à ce code ES5 :
        const target = event.target;                                      
        const value =                         var partialState = {};          
         target.type === 'checkbox'           partialState[name] = value;    
         ? target.checked : target.value;     this.setState(partialState);    
        const name = target.name;

        this.setState({
          [name]: value
        });
      }
     

    Comme setState() fusionne automatiquement un état partiel dans l’état local courant, il nous a suffi de l’appeler avec les parties modifiées.

    Définir la prop value sur un composant contrôlé (et non par setState) empêche l’utilisateur de changer la saisie. Pour que le champ devienne modifiable:


    ReactDOM.render(<input value="Salut" />, mountNode);

    setTimeout(function() {
      ReactDOM.render(<input value={null} />, mountNode);
    }, 1000);
     

    Il est intéressant de connaître les composants non-contrôlés, une technique alternative pour implémenter les formulaires de saisie.

    Si vous cherchez une solution complète gérant la validation, l’historique des champs visités, et la gestion de soumission de formulaire, Formik fait partie des choix populaires. Ceci dit, il repose sur les mêmes principes de composants contrôlés et de gestion d’état local

    3.11. Faire remonter l'état

    --bug ligne coupée Plusieurs composants ont souvent besoin de refléter les mêmes données dynamiques. Nous conseillons de faire remonter l’état partagé dans leur ancêtre commun le plus proche. --bug d' disparait Les Hooks sont des fonctions qui permettent de « se brancher » sur la gestion d’état local et de cycle de vie de React depuis des fonctions composants. Les

    Plusieurs composants ont souvent besoin de refléter les mêmes données dynamiques. Nous conseillons de faire remonter l’état partagé dans leur ancêtre commun le plus proche.


    class TemperatureInput extends React.Component {
      constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
      }

      handleChange(e) {
        this.props.onTemperatureChange(e.target.value);
      }

      render() {
        const temperature = this.props.temperature;
        const scale = this.props.scale;
        return (
          <fieldset>
            <legend>Saisissez la température en {scaleNames[scale]} :</legend>
            <input value={temperature}
                   onChange={this.handleChange} />
          </fieldset>
        );
      }
    }

    class Calculator extends React.Component {
      constructor(props) {
        super(props);
        this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
        this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
        this.state = {temperature: '', scale: 'c'};
      }

      handleCelsiusChange(temperature) {
        this.setState({scale: 'c', temperature});
      }

      handleFahrenheitChange(temperature) {
        this.setState({scale: 'f', temperature});
      }

      render() {
        const scale = this.state.scale;
        const temperature = this.state.temperature;
        const celsius = scale === 'f'
        ? tryConvert(temperature, toCelsius) : temperature;
        const fahrenheit = scale === 'c'
        ? tryConvert(temperature, toFahrenheit) : temperature;

        return (
          <div>
            <TemperatureInput
              scale="c"
              temperature={celsius}
              onTemperatureChange={this.handleCelsiusChange} />
            <TemperatureInput
              scale="f"
              temperature={fahrenheit}
              onTemperatureChange={this.handleFahrenheitChange} />
            <BoilingVerdict
              celsius={parseFloat(celsius)} />
          </div>
        );
      }
    }
     


    https://fr.reactjs.org/docs/getting-started.html

    3.12. Router l'application

    Ajouter le package react-router-dom pour le routage.```javascript Pour IIS, il faut créer un fichier web.config dans le dossier public.


    <?xml version="1.0"?>
    <configuration>
     <system.webServer>
     <rewrite>
     <rules>
     <rule name="React Routes" stopProcessing="true">
     <match url=".*" />
     <conditions logicalGrouping="MatchAll">
     <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
     <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
     <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
     </conditions>
     <action type="Rewrite" url="/" />
     </rule>
     </rules>
     </rewrite>
     </system.webServer>
    </configuration>
     

    Routage basique

    3 pages sont gérées par le routeur: une page d'accueil, une page about et une page utilisateurs. Lorsque vous cliquez sur les différents , le routeur affiche le correspondant.

    Remarque: Dans les coulisses, un affiche un avec un vrai href, de sorte que les personnes utilisant le clavier pour la navigation ou les lecteurs d'écran pourront toujours utiliser cette application.


    import React from "react";
    import {
      BrowserRouter as Router,
      Switch,
      Route,
      Link
    } from "react-router-dom";

    export default function App() {
      return (
        <Router>
          <div>
            <nav>
              <ul>
                <li>
                  <Link to="/">Home</Link>
                </li>
                <li>
                  <Link to="/about">About</Link>
                </li>
                <li>
                  <Link to="/users">Users</Link>
                </li>
              </ul>
            </nav>

            {/* A <Switch> looks through its children <Route>s and
                renders the first one that matches the current URL. */
    }
            <Switch>
              <Route path="/about">
                <About />
              </Route>
              <Route path="/users">
                <Users />
              </Route>
              <Route path="/">
                <Home />
              </Route>
            </Switch>
          </div>
        </Router>
      );
    }
     

    Routage imbriqué

    Cet exemple montre comment fonctionne le routage imbriqué. La route / rubriques charge le composant Rubriques, qui prend en compte toute supplémentaire sur des chemins: id value.


    import React from "react";
    import {
      BrowserRouter as Router,
      Switch,
      Route,
      Link,
      useRouteMatch,
      useParams
    } from "react-router-dom";

    export default function App() {
      return (
        <Router>
          <div>
            <ul>
              <li>
                <Link to="/">Home</Link>
              </li>
              <li>
                <Link to="/about">About</Link>
              </li>
              <li>
                <Link to="/topics">Topics</Link>
              </li>
            </ul>

            <Switch>
              <Route path="/about">
                <About />
              </Route>
              <Route path="/topics">
                <Topics />
              </Route>
              <Route path="/">
                <Home />
              </Route>
            </Switch>
          </div>
        </Router>
      );
    }

    function Home() {
      return <h2>Home</h2>;
    }

    function About() {
      return <h2>About</h2>;
    }

    function Topics() {
      let match = useRouteMatch();

      return (
        <div>
          <h2>Topics</h2>

          <ul>
            <li>
              <Link to={`${match.url}/components`}>Components</Link>
            </li>
            <li>
              <Link to={`${match.url}/props-v-state`}>
                Props v. State
              </Link>
            </li>
          </ul>

          {/* La page Topics a ses propres <Switch> avec plus de routes
              qui se base sur le chemin d'url /topics. Il faut penser que la
              2eme <Route> a une page "index" pour tous les topics, ou
              la page qui s'affiche quand aucun topics est sélectionné */
    }
          <Switch>
            <Route path={`${match.path}/:topicId`}>
              <Topic />
            </Route>
            <Route path={match.path}>
              <h3>Please select a topic.</h3>
            </Route>
          </Switch>
        </div>
      );
    }

    function Topic() {
      let { topicId } = useParams();
      return <h3>Requested topic ID: {topicId}</h3>;
    }
     

    BrowserRouter permet d'avoir accés à l'historique, dans les props des composants routés. On y trouve notament props.history et props.location


            import { BrowserRouter } from 'react-router-dom'
            <BrowserRouter>
              <App />
            </BrowserRouter>
     

    Voici l'équivalent en utilisant Router au lieu de BrowserRouter :


            import { Router } from 'react-router-dom'
            import { createBrowserHistory } from 'history'
            const history = createBrowserHistory()
            <Router history={history}>
              <App />
            </Router>
     

    Pour créer une redirection dans un composant routé : ex:


      const Contact = props => {
        setTimeout(() => {
          props.history.push()
        }, 3000);
        return (
          <div> className="container">
             <h4>page contact</h4>
          </div>
        );
      };
     

    Par contre ça ne marche pas avec autre chose que des routes, donc il a été créé un composant withRouter qui permet de faire la même chose avec n'importe quel composant.


      import {withRouter } from "react-router-dom"  
      const NavBar = props => {
        return <div className="container"><a href="/contact">Contact</a></div>
      }
      export default withRouter(NavBar);
     

    réf: react router

    https://reacttraining.com/react-router/web/guides/quick-start
    réf: react router et deploiement
    https://dev.to/sumitkharche/how-to-deploy-react-application-on-iis-server-1ied
    réf: configurer un store
    https://redux.js.org/recipes/configuring-your-store/

    3.13. Documenter le code

    La documentation Reactjs est open source. Elle se trouve au format markdown sur cette page: https://github.com/reactjs/fr.reactjs.org
    Le format markdown permet également de générer des fichier CHM ou pdf.

    Supprimer les fichiers yml, et créer un fichier docfx.json dans content.


      {
      "build": {
        "content": [
          {
            "files": [
              "**/*.md",
              "**/*.yml"
            ],
            "exclude": [
              "**/obj/**",
              "**/sample/**",
              "**/samples/**",
              "aspnet/**",
              "**/includes/**",
              "**/aspnet-core-conceptual/**",
              "_site/**",
              "***/readme.md",
              "***/license.md"
            ],
            "group": "group1",
            "src": "."
          }
        ],
        "resource": [
          {
            "files": [
              "**/*.png",
              "**/*.jpg",
              "**/*.gif",
              "**/*.svg",
              "**/*.pdf",
              "**/zone-pivot-groups.json"
            ],
            "exclude": [
              "**/obj/**",
              "aspnet/core/**",
              "**/includes/**",
              "_site/**"
            ],
            "group": "group1",
            "src": "."
          }
        ],
        "overwrite": [],
        "externalReference": [],
        "template": [],
        "dest": "_site",
        "markdownEngineName": "markdig",
        "groups": {
          "group1": {
            "dest": "group1-dest",
            "moniker_range": ">= aspnetcore-1.0"
          }
        }
      }
    }
     

    Créer un fichier index.md contenant le sommaire de la documentation react:


    ---
    title: Doc React
    ---
    # ReactJs

    ## Installation

    * [Bien démarrer](docs/getting-started.md)
    * [Ajouter React à un site web](docs/add-react-to-a-website.md)
    * [Créer une nouvelle appli React](docs/create-a-new-react-app.md)
    * [Liens CDN](https://fr.reactjs.org/docs/cdn-links.md)
    * [Canaux de sortie](docs/release-channels.md)

    ## FONDAMENTAUX

    * [1. Bonjour, monde !](docs/hello-world.md)
    * [2. Introduction à JSX](docs/introducing-jsx.md)
    * [3. Le rendu des éléments](docs/rendering-elements.md)
    * [4. Composants et props](docs/components-and-props.md)
    * [5. État et cycle de vie](docs/state-and-lifecycle.md)
    * [6. Gérer les événements](docs/handling-events.md)
    * [7. Affichage conditionnel](docs/conditional-rendering.md)
    * [8. Listes et clés](docs/lists-and-keys.md)
    * [9. Formulaires](docs/forms.md)
    * [10. Faire remonter l'état](docs/lifting-state-up.md)
    * [11. Composition ou héritage ?](docs/composition-vs-inheritance.md)
    * [12. Penser en React](docs/thinking-in-react.md)

    ## GUIDES AVANCES

    * [Accessibilité](docs/accessibility.md)
    * [Découpage dynamique de code](docs/code-splitting.md)
    * [Contexte](docs/context.md)
    * [Périmètres d’erreurs](docs/error-boundaries.md)
    * [Transfert de refs](docs/forwarding-refs.md)
    * [Fragments](docs/fragments.md)
    * [Composants d’ordre supérieur](docs/higher-order-components.md)
    * [Intégration avec d’autres bibliothèques](docs/integrating-with-other-libraries.md)
    * [JSX dans le détail](docs/jsx-in-depth.md)
    * [Optimiser les performances](docs/optimizing-performance.md)
    * [Portails](docs/portals.md)
    * [Profileur](docs/profiler.md)
    * [React sans ES6](docs/react-without-es6.md)
    * [React sans JSX](docs/react-without-jsx.md)
    * [Réconciliation](docs/reconciliation.md)
    * [Les refs et le DOM](docs/refs-and-the-dom.md)
    * [Props de rendu](docs/render-props.md)
    * [Validation de types statique](docs/static-type-checking.md)
    * [Mode strict](docs/strict-mode.md)
    * [Validation de types avec PropTypes](docs/typechecking-with-proptypes.md)
    * [Composants non-contrôlés](docs/uncontrolled-components.md)
    * [Web Components](docs/web-components.md)

    ## REFERENCE DE L'API

    * [React](docs/react-api.md)
    * [React.Component](docs/react-component.md)
    * [ReactDOM](docs/react-dom.md)
    * [ReactDOMServer](docs/react-dom-server.md)
    * [Éléments DOM](docs/dom-elements.md)
    * [SyntheticEvent](docs/events.md)
    * [Utilitaires de test](docs/test-utils.md)
    * [Moteur de rendu de test](docs/test-renderer.md)
    * [Pré-requis pour l'environnement JS](docs/javascript-environment-requirements.md)
    * [Glossaire](docs/glossary.md)

    ## HOOKS

    * [1. Introduction aux Hooks](docs/hooks-intro.md)
    * [2. Aperçu des Hooks](docs/hooks-overview.md)
    * [3. Utiliser le Hook d’état](docs/hooks-state.md)
    * [4. Utiliser le Hook d’effet](docs/hooks-effect.md)
    * [5. Les règles des Hooks](docs/hooks-rules.md)
    * [6. Construire vos propres Hooks](docs/hooks-custom.md)
    * [7. Référence de l'API des Hooks](docs/hooks-reference.md)
    * [8. FAQ des Hooks](docs/hooks-faq.md)

    ## TESTS

    * [Aperçu des tests](docs/testing.md)
    * [Recettes de test](docs/testing-recipes.md)
    * [Environnements de test](docs/testing-environments.md)

    ## MODECONCURRENT

    * [1. Introduction au mode concurrent](docs/concurrent-mode-intro.md)
    * [2. Suspense pour le chargement de données](docs/concurrent-mode-suspense.md)
    * [3. Approches pour une UI concurrente](docs/concurrent-mode-patterns.md)
    * [4. Adopter le mode concurrent](docs/concurrent-mode-adoption.md)
    * [5. Référence de l’API du mode concurrent](docs/concurrent-mode-reference.md)

    ## CONTRIBUER

    * [Comment contribuer](docs/how-to-contribute.md)
    * [Aperçu du code source](docs/codebase-overview.md)
    * [Notes d’implémentation](docs/implementation-notes.md)
    * [Principes de conception](docs/design-principles.md)

    ## FAQ

    * [AJAX et les API](docs/faq-ajax.md)
    * [Babel, JSX, et étapes de construction](docs/faq-build.md)
    * [Passer des fonctions aux composants](docs/faq-functions.md)
    * [État local de composant](docs/faq-state.md)
    * [Styles et CSS](docs/faq-styling.md)
    * [Structure de fichier](docs/faq-structure.md)
    * [Politique de versions](docs/faq-versioning.md)
     
    * [DOM virtuel et autres détails](docs/faq-internals.md)

    * [AJAX et les API](docs/faq-ajax.md)
    * [Babel, JSX, et étapes de construction](docs/faq-build.md)
    * [Passer des fonctions aux composants](docs/faq-functions.md)
    * [État local de composant](docs/faq-state.md)
    * [Styles et CSS](docs/faq-styling.md)
    * [Structure de fichier](docs/faq-structure.md)
    * [Politique de versions](docs/faq-versioning.md)
    * [DOM virtuel et autres détails](docs/faq-internals.md)

    * [Ajouter React à un site web](docs/add-react-to-a-website.md)
    * [Installation](docs/getting-started.md)
    * [Fondamentaux](docs/hello-world.md)
    * [Guides avancés](docs/accessibility.md)
    * [Référence de l'API](docs/react-api.md)
    * [Hooks](docs/hooks-intro.md)
    * [Tests](docs/testing.md)
    * [Mode concurrent (expérimental)](docs/concurrent-mode-intro.md)
    * [Contribuer](docs/how-to-contribute.md)
    * [FAQ](docs/faq-ajax.md)
     

    Télécharger le fichier docfx.zip depuis https://github.com/dotnet/docfx/releases
    et le décompresser dans "C:\Program Files\docFx". Ajouter le chemin dans le PATH pour pouvoir l'exécuter n'importe où. Pour générer la documentation en html dans le sous dossier "_site", éxécuter la commande suivante:

    C:\> docfx content\docfx.json --serve

    Ensuite pour générer la documentation en chm le mieux est d'utiliser WinCHM Pro. Aller dans le dossier "_site\styles", et supprimer tous les fichiers ".js". Faire nouveau projet, Create a project using existing html files et sélectionner le dossier "_site". WinCHM vat générer le sommaire à partir des pages. Il est possible de le modifier, par exemple de déplacer les dossiers à la racine, etc. En cliquant sur un élément du sommaire, dans l'onglet "Topic options", on peut ajouter des mots clés pour la création d'un index.


    /**
     * The only true button.
     *
     * @version 1.0.1
     * @author [Artem Sapegin](https://github.com/sapegin)
     * @author [Andy Krings-Stern](https://github.com/ankri)
     */


    /**
     * Gets called when the user clicks on the button
     *
     * @see See ...
     * @param {SyntheticEvent} event The react `SyntheticEvent`
     * @param {Object} allProps All props of this Button
     */

     

    npm install jsdoc -g jsdoc src -r -d docs

    ou

    npm install jsdoc --save-dev example "createdoc": "jsdoc src -r -d docs" maintenant on peut lancer npm run createdoc

    Créera la documentation dans le dossier docs du projet. Il suffit alors de vider linenumber.js pour pouvoir générer un chm valide avec htmlHelpWorkshop.


    Source.hhp                       Source.hhc

    [OPTIONS]                        <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML/EN">
    Compatibility=1.1 or later       <HTML>
    Compiled file=Source.chm           <BODY>
    Contents file=Source.hhc             <UL>
    Default topic=index.htm                <LI><OBJECT type="text/sitemap">
    Display compile progress=No              <param name="Name" value="index">
    Index file=Ocuco.Server.hhk              <param name="Local" value="index.html">
    Language=0x40c Français (France)       </OBJECT></LI>
                                         </UL>
    [INFOTYPES]                        </UL>
                                       </BODY>
                                     </HTML>
     

    réf: devdocs
    https://devdocs.io/

    3.14. Contexte

    Le Contexte offre un moyen de faire passer des données à travers l’arborescence du composant sans avoir à passer manuellement les props à chaque niveau. Il est conçu pour partager des données qui peuvent être considérées comme « globales » pour une arborescence de composants React. Utilisez-le avec parcimonie car il rend la réutilisation des composants plus difficile. Par exemple, dans le code ci-dessous nous faisons passer manuellement la prop theme afin de styler le composant Button :


      class App extends React.Component {
        render() {
          return <Toolbar theme="dark" />;
        }
      }
     
      function Toolbar(props) {
        // Le composant Toolbar doit prendre une prop supplémentaire `theme` et la
        // passer au ThemedButton. Imaginez si chaque bouton a besoin du thème
        return (
          <div>
            <ThemedButton theme={props.theme} />    </div>
        );
      }
     
      class ThemedButton extends React.Component {
        render() {
          return <Button theme={this.props.theme} />;
        }
      }
     

    En utilisant le Contexte, nous pouvons éviter de passer les props à travers des éléments intermédiaires :


      class App extends React.Component {
        render() {
          return <Toolbar theme="dark" />;
        }
      }
     
      function Toolbar(props) {
        // Le composant Toolbar doit prendre une prop supplémentaire `theme` et la
        // passer au ThemedButton. Ça peut devenir pénible si chaque bouton de l’appli
        // a besoin de connaître le thème parce qu’il faudra le faire passer à travers
        // tous les composants.
        return (
          <div>
            <ThemedButton theme={props.theme} />
          </div>
        );
      }
     
      class ThemedButton extends React.Component {
        render() {
          return <Button theme={this.props.theme} />;
        }
      }
     

    En utilisant le Contexte, nous pouvons éviter de passer les props à travers des éléments intermédiaires :


      // Le Contexte nous permet de transmettre une prop profondément dans l’arbre des
      // composants sans la faire passer explicitement à travers tous les composants.
      // Crée un contexte pour le thème (avec “light” comme valeur par défaut).
      const ThemeContext = React.createContext('light');
      class App extends React.Component {
        render() {
          // Utilise un Provider pour passer le thème plus bas dans l’arbre.
          // N’importe quel composant peut le lire, quelle que soit sa profondeur.
          // Dans cet exemple, nous passons “dark” comme valeur actuelle.
          return (
            <ThemeContext.Provider value="dark">
              <Toolbar />
            </ThemeContext.Provider>
          );
        }
      }
     
      // Un composant au milieu n’a plus à transmettre explicitement le thème
      function Toolbar() {
        return (
          <div>
            <ThemedButton />
          </div>
        );
      }
     
      class ThemedButton extends React.Component {
        // Définit un contextType pour lire le contexte de thème actuel.  React va
        // trouver le Provider de thème ancêtre le plus proche et utiliser sa valeur.
        // Dans cet exemple, le thème actuel est “dark”.
        static contextType = ThemeContext;
        render() {
          return <Button theme={this.context} />;  }
      }
     

    Si vous voulez seulement éviter de passer certaines props à travers de nombreux niveaux, la composition des composants est souvent plus simple que le contexte. Par exemple, prenez un composant Page qui passe des props user et avatarSize plusieurs niveaux plus bas pour que les composants profondément imbriqués Link et Avatar puissent les lire :


      <Page user={user} avatarSize={avatarSize} />
      // ... qui affiche ...
      <PageLayout user={user} avatarSize={avatarSize} />
      // ... qui affiche ...
      <NavigationBar user={user} avatarSize={avatarSize} />
      // ... qui affiche ...
      <Link href={user.permalink}>
        <Avatar user={user} size={avatarSize} />
      </Link>
     

    Ça peut paraître redondant de passer les props user et avatarSize à travers plusieurs niveaux, si au final seul le composant Avatar en a réellement besoin.

    CHAPITRE 4 - React Guides avancés

    4.1. Introduction aux Hooks

    Les Hooks sont arrivés avec React 16.8. Ils vous permettent de bénéficier d’un état local et d’autres fonctionnalités de React sans avoir à écrire une classe. Les Hooks sont des fonctions qui permettent de « se brancher » sur la gestion d’état local et de cycle de vie de React depuis des fonctions composants. Les Hooks ne fonctionnent pas dans des classes : ils vous permettent d’utiliser React sans classes. React fournit quelques Hooks prédéfinis comme useState. Les Hooks sont des fonctions JavaScript, mais imposent 2 règles supplémentaires: - Appelez les Hooks uniquement au niveau racine. - Appelez les Hooks uniquement depuis des fonctions composants React.

    Hook d'état

    React va préserver cet état d’un affichage à l’autre. useState retourne une paire : la valeur de l’état actuel et une fonction qui vous permet de la mettre à jour. Vous pouvez appeler cette fonction depuis un gestionnaire d’événements, par exemple. Elle est similaire à this.setState dans une classe, à ceci près qu’elle ne fusionne pas l’ancien état et le nouveau. Le seul argument de useState est l’état initial. Remarquez que contrairement à this.state, ici l’état n’est pas nécessairement un objet, même si ça reste possible. L’argument d’état initial n’est utilisé que pour le premier affichage.


    import React, { useState } from 'react';

    function Example() {
      // Déclare une nouvelle variable d'état, qu’on va appeler « count »
      const [count, setCount] = useState(0);

      return (
        <div>
          <p>Vous avez cliqué {count} fois</p>
          <button onClick={() => setCount(count + 1)}>
            Cliquez ici
          </button>
        </div>
      );
    }
     

    Vous pouvez utiliser le Hook d’état plus d’une fois dans un seul composant :


    function ExampleWithManyStates() {
      // Déclaration de multiples variables d'état !
      const [age, setAge] = useState(42);
      const [fruit, setFruit] = useState('banane');
      const [todos, setTodos] = useState([{ text: 'Apprendre les Hooks' }]);
      // ...
    }
     

    La syntaxe de la déstructuration positionnelle nous permet de donner des noms distincts aux variables d’état que nous déclarons en appelant useState. Ces noms ne font pas partie de l’API useState. Au lieu de ça, React suppose que si vous appelez useState plusieurs fois, vous le faites dans le même ordre à chaque affichage.

    Hook d'effet

    Des modifications manuelles sur le DOM depuis un composant React constitue une opérations « effets de bord ». Le Hook d’effet, useEffect, permet aux fonctions composants de gérer des effets de bord. Il joue le même rôle que componentDidMount, componentDidUpdate, et componentWillUnmount dans les classes React, mais au travers d’une API unique.

    Par ex, ce composant change le titre du document après que React a mis à jour le DOM :


    import React, { useState, useEffect } from 'react';

    function Example() {
      const [count, setCount] = useState(0);

      // Équivalent à componentDidMount plus componentDidUpdate :
      useEffect(() => {
        // Mettre à jour le titre du document en utilisant l'API du navigateur
        document.title = `Vous avez cliqué ${count} fois`;
      });

      return (
        <div>
          <p>Vous avez cliqué {count} fois</p>
          <button onClick={() => setCount(count + 1)}>
            Cliquez ici
          </button>
        </div>
      );
    }
     

    Lorsque vous appelez useEffect, vous dites à React de lancer votre fonction d’ « effet » après qu’il a mis à jour le DOM. Les effets étant déclarés au sein du composant, ils ont accès à ses props et son état. Par défaut, React exécute les effets après chaque affichage, y compris le premier. Tout comme avec useState, vous pouvez utiliser plus d’un seul effet dans un composant :

    4.2. Star Match Game

    Un Hook personnalisé est une fonction JavaScript dont le nom commence par ”use” et qui peut appeler d’autres Hooks.

    Le jeux de match d'étoile consiste a donner une liste d'entiers entre 1 et 9 qui losqu'ils s'additionnent donnent le nombre d'étoiles affichés. Si la somme est supérieure au nombre d'étoiles, le nombre passe en rouge, et l'utilisateur peut le désectionner. Commençons à coder:

    // CSS

    .game { max-width: 500px; margin: 0 auto; }

    .body { display: flex; }

    .help { color: #666; margin: 10px; text-align: center; }

    .left { text-align: center; width: 50%; border: thin solid #ddd; }

    .right { text-align: center; padding: 10px; width: 50%; border: thin solid #ddd; }

    .star { display: inline-block; margin: 0 15px; }

    .star:after { content: "\2605"; font-size: 45px; }

    .number { background-color: #eee; border: thin solid #ddd; width: 45px; height: 45px; margin: 10px; font-size: 25px; }

    .number:focus, .number:active { outline: none; border: thin solid #ddd; }

    .timer { color: #666; margin-top: 3px; margin-left: 3px; }

    .game-done .message { font-size: 250%; font-weight: bold; margin: 15px; }

    // STAR MATCH - V6


    const StarsDisplay = props => (
      <>
        {utils.range(1, props.count).map(starId => (
          <div key={starId} className="star" />
        ))}
      </>
    );

    const PlayNumber = props => (
      <button
        className="number"
        style={{backgroundColor: colors[props.status]}}
        onClick={() => props.onClick(props.number, props.status)}
      >
        {props.number}
      </button>
    );

    const PlayAgain = props => (
      <div className="game-done">
        {/* step 3 */}
        <div
          className="message"
          style={{ color: props.gameStatus === 'lost' ? 'red' : 'green'}}
        >
          {props.gameStatus === 'lost' ? 'Game Over' : 'Nice'}
        </div>
        {/* end step */}
        <button onClick={props.onClick}>Play Again</button>
      </div>
    );

    // step 4
    const Game = (props) => {
    //const StarMatch = () => {
      const [stars, setStars] = useState(utils.random(1, 9));
      const [availableNums, setAvailableNums] = useState(utils.range(1, 9));
      const [candidateNums, setCandidateNums] = useState([]);
      const [secondsLeft, setSecondsLeft] = useState(10); // step 1

      // step 2
      useEffect(() => {
        if (secondsLeft > 0 && availableNums.length > 0) {
          const timerId = setTimeout(() => {
            setSecondsLeft(secondsLeft - 1);
          }, 1000);
          // Executé par React a chaque changement d'etat (rerender)
          // cela évite qu'il y ai plusieurs timer en même temps
          return () => clearTimeout(timerId);
        }
      });

      const candidatesAreWrong = utils.sum(candidateNums) > stars;
      // step 3
      const gameStatus = availableNums.length === 0
        ? 'won'
        : secondsLeft === 0 ? 'lost' : 'active';
      //const gameIsDone = availableNums.length === 0;
     
      // step 4
      //const resetGame = () => {
      //  setStars(utils.random(1, 9));
      //  setAvailableNums(utils.range(1, 9));
      //  setCandidateNums([]);
      //};
     
      const numberStatus = number => {
        if (!availableNums.includes(number)) {
          return 'used';
        }
        if (candidateNums.includes(number)) {
          return candidatesAreWrong ? 'wrong' : 'candidate';
        }
        return 'available';
      };
     
      const onNumberClick = (number, currentStatus) => {
        // step 3
        if (gameStatus !== 'active' || currentStatus === 'used') {
        //if (currentStatus === 'used') {
          return;
        }
     
        const newCandidateNums =
          currentStatus === 'available'
            ? candidateNums.concat(number)
            : candidateNums.filter(cn => cn !== number);

        if (utils.sum(newCandidateNums) !== stars) {
          setCandidateNums(newCandidateNums);
        } else {
          const newAvailableNums = availableNums.filter(
            n => !newCandidateNums.includes(n)
          );
          setStars(utils.randomSumIn(newAvailableNums, 9));
          setAvailableNums(newAvailableNums);
          setCandidateNums([]);
        }
      };

      return (
        <div className="game">
          <div className="help">
            Pick 1 or more numbers that sum to the number of stars
          </div>
          <div className="body">
            <div className="left">
              {/* step 4 onClick={props.startNewGame} gameStatus={gameStatus}
                  step 0 onClick={resetGame}, step 3 onClick={resetGame} gameStatus={gameStatus}
                  step 0 gameIsDone ?, step 3 gameStatus !== 'active' ? */
    }
              {gameStatus !== 'active' ? (
                <PlayAgain onClick={props.startNewGame} gameStatus={gameStatus} />
              ) : (
                    <StarsDisplay count={stars} />
              )}
            </div>
            <div className="right">
              {utils.range(1, 9).map(number => (
                <PlayNumber
                  key={number}
                  status={numberStatus(number)}
                  number={number}
                  onClick={onNumberClick}
                />
              ))}
            </div>
          </div>
          <div className="timer">Time Remaining: {secondsLeft}</div> {/* step 1 */}

          //<div className="timer">Time Remaining: 10</div>
        </div>
      );
    };

    // step 4
    const StarMatch = () => {
            const [gameId, setGameId] = useState(1);
            return <Game key={gameId} startNewGame={() => setGameId(gameId + 1)}/>;
    }

    // Color Theme
    const colors = {
      available: 'lightgray',
      used: 'lightgreen',
      wrong: 'lightcoral',
     
      candidate: 'deepskyblue',
    };

    // Math science
    const utils = {
      // Sum an array
      sum: arr => arr.reduce((acc, curr) => acc + curr, 0),

      // create an array of numbers between min and max (edges included)
      range: (min, max) => Array.from({length: max - min + 1}, (_, i) => min + i),

      // pick a random number between min and max (edges included)
      random: (min, max) => min + Math.floor(max * Math.random()),

      // Given an array of numbers and a max...
      // Pick a random sum (< max) from the set of all available sums in arr
      randomSumIn: (arr, max) => {
        const sets = [[]];
        const sums = [];
        for (let i = 0; i < arr.length; i++) {
          for (let j = 0, len = sets.length; j < len; j++) {
            const candidateSet = sets[j].concat(arr[i]);
            const candidateSum = utils.sum(candidateSet);
            if (candidateSum <= max) {
              sets.push(candidateSet);
              sums.push(candidateSum);
            }
          }
        }
        return sums[utils.random(0, sums.length)];
      },
    };

    ReactDOM.render(<StarMatch />, mountNode);
     

    4.3. Hook personnalisé (star Match Game)


    // step 5
    const useGameState = timeLimit => {
      const [stars, setStars] = useState(utils.random(1, 9));
      const [availableNums, setAvailableNums] = useState(utils.range(1, 9));
      const [candidateNums, setCandidateNums] = useState([]);
      const [secondsLeft, setSecondsLeft] = useState(10);

      useEffect(() => {
        if (secondsLeft > 0 && availableNums.length > 0) {
          const timerId = setTimeout(() => setSecondsLeft(secondsLeft - 1), 1000);
          return () => clearTimeout(timerId);
        }
      });

      const setGameState = (newCandidateNums) => {
        if (utils.sum(newCandidateNums) !== stars) {
          setCandidateNums(newCandidateNums);
        } else {
          const newAvailableNums = availableNums.filter(
            n => !newCandidateNums.includes(n)
          );
          setStars(utils.randomSumIn(newAvailableNums, 9));
          setAvailableNums(newAvailableNums);
          setCandidateNums([]);
        }
      };

      return { stars, availableNums, candidateNums, secondsLeft, setGameState };
    };

    const Game = props => {
      // step 5
      const {
        stars,
        availableNums,
        candidateNums,
        secondsLeft,
        setGameState,
      } = useGameState();
      //useEffect(() => {
      //  if (secondsLeft > 0 && availableNums.length > 0) {
      //    const timerId = setTimeout(() => {
      //      setSecondsLeft(secondsLeft - 1);
      //    }, 1000);
      //    return () => clearTimeout(timerId);
      //  }
      //});
     
      const candidatesAreWrong = utils.sum(candidateNums) > stars;
      const gameStatus = availableNums.length === 0
        ? 'won'
        : secondsLeft === 0 ? 'lost' : 'active'

      const numberStatus = number => {
        if (!availableNums.includes(number)) {
          return 'used';
        }

        if (candidateNums.includes(number)) {
          return candidatesAreWrong ? 'wrong' : 'candidate';
        }

        return 'available';
      };

      const onNumberClick = (number, currentStatus) => {
        if (currentStatus === 'used' || secondsLeft === 0) {
          return;
        }

        const newCandidateNums =
          currentStatus === 'available'
            ? candidateNums.concat(number)
            : candidateNums.filter(cn => cn !== number);

        // step 5
        setGameState(newCandidateNums);
        //if (utils.sum(newCandidateNums) !== stars) {
        //  setCandidateNums(newCandidateNums);
        //} else {
        //  const newAvailableNums = availableNums.filter(
        //    n => !newCandidateNums.includes(n)
        //  );
        //  setStars(utils.randomSumIn(newAvailableNums, 9));
        //  setAvailableNums(newAvailableNums);
        //  setCandidateNums([]);
        //}
      };

      return (
     

    Utilisation des effets secondaires Hooks


    A chaque fois qu'un element est cliqué, l'écran est raffraichit (rerender)
    // raffraichis au moins 1 fois par secondes
    const [secondsLeft, setSecondsLeft] = useState(10);
    useEffect(() => {
      if (secondsLeft > 0) {
        const timerId = setTimeout(() => {
          setSecondsLeft(secondsLeft - 1);
        }, 1000);
        // Executé par React a chaque changement d'etat (rerender)
        // cela évite qu'il y ai plusieurs timer en même temps
        return () => clearTimeout(timerId);
      }
    });
     


    https://fr.reactjs.org/docs/getting-started.html

    https://jscomplete.com/playground/rgs3.1
    to
    https://jscomplete.com/playground/rgs3.9

    https://jscomplete.com/playground/new

    https://app.pluralsight.com
    React: GettingStarted

    4.4. List of items

    La fonction suivante montre comment afficher une liste d'éléments (en JS). Nous pouvons utiliser la méthode de mappage de tableaux JavaScript intégrée pour parcourir les éléments de notre liste; et pour les mapper de JavaScript primitif aux éléments HTML. Chaque élément reçoit obligatoirement une propriété key.


    const list = ['a', 'b', 'c'];           const list = ['a', 'b', 'c'];          
    const SimpleList = () => (              const SimpleList = () => (              
      <ul>                                    <ul>                                  
        {list.map(function(item) {              {list.map(item => {                
          return <li key={item}>{item}</li>;      return <li key={item}>{item}</li>;
        })}                                     })}                                
      </ul>                                   </ul>                                
    );                                      );                                      
     

    Comme nous ne faisons rien dans le corps du bloc de la fonction, nous pouvons également le reformuler en un corps concis et omettre la déclaration return et les accolades du corps de la fonction:


    const list = ['a', 'b', 'c'];
    const SimpleList = () => (
      <ul>
        {list.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    );
     

    Si nous utilisions le composant List en tant qu'enfant dans un autre composant, nous pourrions lui transmettre la liste comme propriété:


    const mylist = ['a', 'b', 'c'];
    const App = () => (
      <SimpleList list={mylist} />
    );
    const SimpleList = ({ list }) => (
      <ul>
        {list.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    );
     

    C’est un exemple simple de composant de liste pour React. Nous avons seulement une liste de primitives JavaScript, telles que des chaînes ou des entiers, mappées dessus et générant un listitem HTML pour chaque élément du tableau. Si l'argument de valeur donné dans la fonction map est un objet, vous pouvez accéder à l'objet dans votre JSX pour et afficher les différentes propriétés:


    import React from 'react';
    const list = [
      { id: 'a', firstname: 'Robin', lastname: 'Wieruch',  year: 1988 },
      { id: 'b', firstname: 'Dave',  lastname: 'Davidds',  year: 1990 }];
    const ComplexList = () => (
      <ul>
        {list.map(item => (
          <li key={item.id}>
            <div>{item.id}</div>
            <div>{item.firstname}</div>
            <div>{item.lastname}</div>
            <div>{item.year}</div>
          </li>
        ))}
      </ul>
    );
    export default ComplexList;
     

    liste à 2 dimentions:


    const nestedLists = [list, list];
    const NestedList = () => (
      <ul>
        {nestedLists.map((nestedList, index) => (
          <ul key={index}>
            <h4>List {index + 1}</h4>
            {nestedList.map(item => (
              <li key={item.id}>
                <div>{item.id}</div>
                <div>{item.firstname}</div>
                <div>{item.lastname}</div>
                <div>{item.year}</div>
              </li>
            ))}
          </ul>
        ))}
      </ul>
    );
     

    Pour des listes en 2 dimensions, vous utiliser les mêmes techniques. Cependant, afin de garder les composants de votre liste React bien rangés, vous pouvez les extraire en composants autonomes. Par exemple, le composant List s'assure de mapper sur le tableau pour rendre la liste des composants ListItem de chaque élément en tant que composant enfant:


    const list = [...
    const App = () => <List list={list} />;
    const List = ({ list }) => (
      <ul>
        {list.map(item => (
          <ListItem key={item.id} item={item} />
        ))}
      </ul>
    );
    const ListItem = ({ item }) => (
      <li>
        <div>{item.id}</div>
        <div>{item.firstname}</div>
        <div>{item.lastname}</div>
        <div>{item.year}</div>
      </li>
    );
     

    Si vous ne savez pas si la liste entrante est nulle ou non définie, définissez vous-même une liste vide:


        {(list || []).map(item => (
     

    Les composants List et ListItem sont si souvent utilisés dans les applications React qu'ils peuvent être considérés comme un modèle de liste standard, car il suffit souvent de copier coller la même implémentation, pour obtenir une simple liste dans votre code. Donc, garder à l’esprit cette structure.

    réf: React List Components by Example
    https://www.robinwieruch.de/react-list-components/

    4.5. useState avancé


    import React from 'react';
    const initialTodos = [
      { id: 'a', task: 'Learn React',    complete: true },
      { id: 'b', task: 'Learn Firebase', complete: true },
      { id: 'c', task: 'Learn GraphQL',  complete: false}];
    const App = () => (
      <div>
        <ul>
          {initialTodos.map(todo => (
            <li key={todo.id}>
              <label>{todo.task}</label>
            </li>
          ))}
        </ul>
      </div>
    );
    export defaul
     

    Afin d'ajouter un nouvel élément de tâche à notre liste d'éléments, nous avons besoin d'un champ de saisie pour attribuer à un nouvel élément une entrée de tâche. L'ID et les propriétés complètes seront automatiquement ajoutés à l'élément. Dans React, nous pouvons utiliser l'état Hook appelé useState pour gérer quelque chose comme la valeur d'un champ de saisie, en tant qu'état dans le composant.

    Nous avons également dû créer une Fonction Flèche avec une instruction de retour explicite pour obtenir le hook useState. Maintenant, nous pouvons changer l’état de la tâche avec notre fonction de gestionnaire, car nous avons la valeur de l’entrée à notre disposition dans l’événement synthétique de React:


        const App = () => {
          const [task, setTask] = useState('');
       
          const handleChangeInput = event => {
            setTask(event.target.value);
          };
       
          return (
            <div>
              <ul>
                {initialTodos.map(todo => (
                  <li key={todo.id}>
                    <label>{todo.task}</label>
                  </li>
                ))}
              </ul>
       
              <input type="text" value={task} onChange={handleChangeInput} />
            </div>
          );
        };
     

    Ajout d'un bouton submit


        const handleSubmit = event => {
          if (task) {
            // add new todo item
          }
          setTask('');
          event.preventDefault();
        };
     
        return (
          <div>
            <ul>
              {initialTodos.map(todo => (
                <li key={todo.id}>
                  <label>{todo.task}</label>
                </li>
              ))}
            </ul>
            <form onSubmit={handleSubmit}>
              <input
                type="text"
                value={task}
                onChange={handleChangeInput}
              />
              <button type="submit">Add Todo</button>
            </form>
          </div>
        );
     

    preventDefault empêche le comportement par défaut du navigateur, car sinon le navigateur effectuerait une actualisation après avoir cliqué sur le bouton d'envoi. Afin d'ajouter l'élément à modifier à notre liste d'éléments, nous devons également rendre les éléments à contrôler en tant qu'état dans le composant. Nous pouvons utiliser à nouveau le hook useState:


      const [todos, setTodos] = useState(initialTodos);
     
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <label>{todo.task}</label>
          </li>
        ))}
      </ul>
     

    Avec la fonction setTodos, on ajoute le nouvel élément de tâche à la liste. Nous pouvons utiliser une bibliothèque pour générer un identifiant unique


      npm install uuid
     
      const handleSubmit = event => {
        if (task) {
          setTodos(todos.concat({ id: uuid(), task, complete: false }));
        }
     

    Enfin, implémentons une case à cocher pour chaque élément de la liste afin de basculer leurs indicateurs complets.


        const handleChangeCheckbox = event => { };
        {todos.map(todo => (
          <li key={todo.id}>
            <label>
              <input
                type="checkbox"
                checked={todo.complete}
                onChange={handleChangeCheckbox}
              />
              {todo.task}
            </label>
          </li>
        ))}
     

    Etant donné que nous avons besoin de l'identifiant de la tâche à exécuter dans notre fonction de gestionnaire, et non de l'événement, nous utilisons une fonction de flèche pour transmettre l'identificateur de la tâche individuelle à notre gestionnaire:


        const handleChangeCheckbox = id => {
          setTodos(
            todos.map(todo => {
              if (todo.id === id) {
                return { ...todo, complete: !todo.complete };
              } else {
                return todo;
              }
            })
          );
        }
        onChange={() => handleChangeCheckbox(todo.id)}
     

    réf: React State with Hooks: useReducer, useState, useContext
    https://www.robinwieruch.de/react-state-usereducer-usestate-usecontext/

    4.6. Reducer avec Javascript

    Le concept de réducteur est devenu populaire en JavaScript avec l’avènement de Redux en tant que solution de gestion d’état pour React. Mais pas de panique, vous n'avez pas besoin d'apprendre Redux pour comprendre les réducteurs. Fondamentalement, les réducteurs sont là pour gérer l'état dans une application. Par exemple, si un utilisateur écrit quelque chose dans un champ de saisie HTML, l’application doit gérer cet état de l’interface utilisateur (par exemple, des composants contrôlés).

    Intéressons-nous de plus près aux détails de l’implémentation: un réducteur est une fonction qui prend deux arguments - l’état actuel et une action - et retourne un nouvel état en fonction des deux arguments. Dans une pseudo-fonction, cela pourrait être exprimé par:


        (state, action) => newState
     

    À titre d’exemple, il s’agirait de ce qui suit en JavaScript pour le scénario d’augmentation d’un nombre:


        function counterReducer(state, action) { return state + 1; } // ou avec fct
        const counterReducer = (state, action) => { return state + 1; }; // flèche
     

    La fonction de réduction est une fonction pure sans effets secondaires, ce qui signifie que, avec la même entrée (par exemple, état et action), la sortie attendue (par exemple, newState) sera toujours la même. Cela fait des fonctions de réduction le choix idéal pour raisonner sur les changements d'état et les tester isolément. Vous pouvez répéter le même test avec la même entrée en tant qu'arguments et toujours vous attendre à la même sortie:


        expect(counterReducer(0)).to.equal(1); // successful test
        expect(counterReducer(0)).to.equal(1); // successful test
     

    L'action est normalement définie en tant qu'objet avec une propriété de type. En fonction du type de l'action, le réducteur peut effectuer des transitions d'état conditionnelles:


        const counterReducer = (count, action) => {
          switch (action.type) {
            case 'INCREASE': return count + 1;
            case 'DECREASE': return count - 1;
            default: return count;
          }
        };
     

    Le compte pourrai être un état de notre objet state.Dans la fonction ci dessous, il y a deux choses à comprendre:


        const counterReducer = (state, action) => {
          switch (action.type) {
            case 'INCREASE':
              return { ...state, count: state.count + 1 };
            case 'DECREASE':
              return { ...state, count: state.count - 1 };
            default:
              return state;
          }
        };
     

    L'état entrant comme argument n'est jamais changé directement. Par conséquent, la fonction de réduction doit toujours renvoyer un nouvel objet d'état.

    Puisque nous savons que l'état est une structure de données immuable, nous pouvons utiliser l'opérateur de propagation (...), pour créer un nouvel objet d'état à partir de l'état entrant et de la partie à modifier (par exemple, la propriété count). De cette façon, nous nous assurons que les autres propriétés qui ne sont pas touchées par l’objet d’état entrant restent intactes pour le nouvel objet d’état.


        const personReducer = (person, action) => {
          switch (action.type) {
            case 'INCREASE_AGE':
              return { ...person, age: person.age + 1 };
            case 'CHANGE_LASTNAME':
              return { ...person, lastname: action.lastname };
            default:
              return person;
          }
        };

        const initialState = {
          firstname: 'Liesa',
          lastname: 'Huppertz',
          age: 30,
        };
       
        const action = {
          type: 'CHANGE_LASTNAME',
          lastname: 'Wieruch',
        };
       
        const result = personReducer(initialState, action);
       
        expect(result).to.equal({
          firstname: 'Liesa',
          lastname: 'Wieruch',
          age: 30,
        });
     

    La propriété "type" obligatoire dans l'objet action, peut être complété par une information supplémentaire pour effectuer la transition d'état. Souvent, la propriété optionnelle d'une action est placée dans une autre propriété générique pour conserver le plus haut niveau des propriétés d'un objet d'action plus général (.e.g {type, charge utile}).


        const action = {
          type: 'CHANGE_LASTNAME',
          payload: {
            lastname: 'Wieruch',
          },
        };
       
        const personReducer = (person, action) => {
          switch (action.type) {
            case 'INCREASE_AGE':
              return { ...person, age: person.age + 1 };
            case 'CHANGE_LASTNAME':
              return { ...person, lastname: action.payload.lastname };
            default:
              return person;
          }
        };
     

    réf: What is a reducer (React/Redux) in JavaScript?
    https://www.robinwieruch.de/javascript-reducer/

    4.7. useReducer avec React

    La fonction suivante est une fonction de réduction permettant de gérer les transitions d'état pour une liste d'éléments:


        const todoReducer = (state, action) => {
          switch (action.type) {
            case 'DO_TODO':
              return state.map(todo => {
                if (todo.id === action.id) {
                  return { ...todo, complete: true };
                } else {
                  return todo;
                }
              });
            case 'UNDO_TODO':
              return state.map(todo => {
                if (todo.id === action.id) {
                  return { ...todo, complete: false };
                } else {
                  return todo;
                }
              });
            default:
              return state;
          }
        };
       
        const todos = [
          {
            id: 'a',
            task: 'Learn React',
            complete: false,
          },
          {
            id: 'b',
            task: 'Learn Firebase',
            complete: false,
          },
        ];
       
        const action = {
          type: 'DO_TODO',
          id: 'a',
        };
        const newTodos = todoReducer(todos, action);
        console.log(newTodos);
     

    Le hook useReducer est utilisé pour les états complexes et les transitions. Il prend une fonction réducteur et un état initial en entrée et renvoie l'état actuel et une fonction de répartition en sortie avec la déstructuration :


        const initialTodos = [
          {
            id: 'a',
            task: 'Learn React',
            complete: false,
          },
          {
            id: 'b',
            task: 'Learn Firebase',
            complete: false,
          },
        ];
       
        const todoReducer = (state, action) => {
          switch (action.type) {
            case 'DO_TODO':
              return state.map(todo => {
                if (todo.id === action.id) {
                  return { ...todo, complete: true };
                } else {
                  return todo;
                }
              });
            case 'UNDO_TODO':
              return state.map(todo => {
                if (todo.id === action.id) {
                  return { ...todo, complete: false };
                } else {
                  return todo;
                }
              });
            default:
              return state;
          }
        };
       
        const [todos, dispatch] = useReducer(todoReducer, initialTodos);
     

    La fonction dispatch peut être utilisée pour envoyer une action au réducteur qui modifierait implicitement l'état actuel:


        const [todos, dispatch] = React.useReducer(todoReducer, initialTodos);
        dispatch({ type: 'DO_TODO', id: 'a' });
     
        import React from 'react';
       
        const initialTodos = [
          {
            id: 'a',
            task: 'Learn React',
            complete: false,
          },
          {
            id: 'b',
            task: 'Learn Firebase',
            complete: false,
          },
        ];
       
        const App = () => {
          const handleChange = () => {};
       
          return (
            <ul>
              {initialTodos.map(todo => (
                <li key={todo.id}>
                  <label>
                    <input
                      type="checkbox"
                      checked={todo.complete}
                      onChange={handleChange}
                    />
                    {todo.task}
                  </label>
                </li>
              ))}
            </ul>
          );
        };
       
        export default App;
     

    Il n’est pas encore possible de changer l’état d’un élément avec la fonction de gestionnaire. Cependant, avant que nous puissions le faire, nous devons rendre la liste des éléments avec état en les utilisant comme état initial pour notre hook useReducer avec la fonction de réduction précédemment définie:


        import React from 'react';

        const initialTodos = [...];
       
        const todoReducer = (state, action) => {
          switch (action.type) {
            case 'DO_TODO':
              return state.map(todo => {
                if (todo.id === action.id) {
                  return { ...todo, complete: true };
                } else {
                  return todo;
                }
              });
            case 'UNDO_TODO':
              return state.map(todo => {
                if (todo.id === action.id) {
                  return { ...todo, complete: false };
                } else {
                  return todo;
                }
              });
            default:
              return state;
          }
        };
       
        const App = () => {
          const [todos, dispatch] = React.useReducer(
            todoReducer,
            initialTodos
          );
       
          const handleChange = () => {};
       
          return (
            <ul>
              {todos.map(todo => (
                <li key={todo.id}>
                  ...
                </li>
              ))}
            </ul>
          );
        };
       
        export default App;
     

    Maintenant, nous pouvons utiliser le gestionnaire pour envoyer une action pour notre fonction de réduction. Puisque nous avons besoin de l'identifiant en tant qu'identifiant d'un élément à exécuter pour pouvoir basculer son indicateur complet, nous pouvons le transmettre dans la fonction de gestionnaire à l'aide d'une fonction de flèche d'encapsulation:


        const App = () => {
          const [todos, dispatch] = React.useReducer(
            todoReducer,
            initialTodos
          );
       
          const handleChange = todo => {
            dispatch({ type: 'DO_TODO', id: todo.id });
          };
       
          return (
            <ul>
              {todos.map(todo => (
                <li key={todo.id}>
                  <label>
                    <input
                      type="checkbox"
                      checked={todo.complete}
                      onChange={() => handleChange(todo)}
                    />
                    {todo.task}
                  </label>
                </li>
              ))}
            </ul>
          );
        };
     

    Cependant, cette implémentation ne fonctionne que dans un sens: les éléments non terminés peuvent être terminés, mais l’opération ne peut pas être inversée en utilisant la deuxième transition d’état de notre réducteur. Implémentons ce comportement dans notre gestionnaire en vérifiant si un élément à compléter est terminé ou non:


        const App = () => {
          const [todos, dispatch] = React.useReducer(
            todoReducer,
            initialTodos
          );
       
          const handleChange = todo => {
            dispatch({
              type: todo.complete ? 'UNDO_TODO' : 'DO_TODO',
              id: todo.id,
            });
          };
       
          return (
            <ul>
              {todos.map(todo => (
                <li key={todo.id}>
                  <label>
                    <input
                      type="checkbox"
                      checked={todo.complete}
                      onChange={() => handleChange(todo)}
                    />
                    {todo.task}
                  </label>
                </li>
              ))}
            </ul>
          );
        };
     

    En fonction de l'état de l'élément todo, l'action correcte est envoyée pour notre fonction de réduction. Ensuite, le composant React est rendu à nouveau, mais en utilisant le nouvel état du hook useReducer.

    L'exemple d'useReducer peut être trouvé dans ce référentiel GitHub.
    https://github.com/the-road-to-learn-react/react-usereducer-hook

    Le crochet d’utilisation du réducteur de React est un moyen puissant de gérer l’état de React. Il peut être utilisé avec useState et useContext pour la gestion d’état moderne dans React. De plus, il est souvent utilisé en faveur de useState

    pour les états complexes et les transitions d'état. Après tout, le crochet useReducer vise particulièrement bien les applications de taille moyenne, qui n'ont pas encore besoin de Redux pour React.

    réf: How to useReducer in React?
    https://www.robinwieruch.de/react-usereducer-hook/

    de nombreaux composants payants:
    https://wwwyncfusion.com/react-ui-components

    4.8. useReducer pour les états complexes

    réf: React State with Hooks: useReducer, useState, useContext
    https://www.robinwieruch.de/react-state-usereducer-usestate-usecontext/

    4.9. useContext pour l'état "global"

    L'état est géré au niveau du composant. Et si nous avions un arbre de composants profond ? Comment pouvons-nous envoyer des changements d'état de n'importe où ?

    Explorons l’API de contexte de React et le hook useContext pour imiter davantage la philosophie de Redux en rendant disponibles les changements d’état dans l’arborescence complète des composants. Avant de pouvoir le faire, modifions notre composant en une arborescence de composants. Tout d'abord, le composant App rend tous ses composants enfants et leur transmet les fonctions d'état et de répartition nécessaires:


    const App = () => {
      const [filter, dispatchFilter] = useReducer(filterReducer, 'ALL');
      const [todos, dispatchTodos] = useReducer(todoReducer, initialTodos);

      const filteredTodos = todos.filter(todo => {
        if (filter === 'ALL') return true;
        if (filter === 'COMPLETE' && todo.complete) return true;
        if (filter === 'INCOMPLETE' && !todo.complete) return true;
        return false;
      });

      return (
        <div>
          <Filter dispatch={dispatchFilter} />
          <TodoList dispatch={dispatchTodos} todos={filteredTodos} />
          <AddTodo dispatch={dispatchTodos} />
        </div>
      );
    };

    const Filter = ({ dispatch }) => {
      const handleShowAll = () => {
        dispatch({ type: 'SHOW_ALL' });
      };
     
      const handleShowComplete = () => {
        dispatch({ type: 'SHOW_COMPLETE' });
      };
     
      const handleShowIncomplete = () => {
        dispatch({ type: 'SHOW_INCOMPLETE' });
      };
     
      return (
        <div>
          <button type="button" onClick={handleShowAll}>
            Show All
          </button>
          <button type="button" onClick={handleShowComplete}>
            Show Complete
          </button>
          <button type="button" onClick={handleShowIncomplete}>
            Show Incomplete
          </button>
        </div>
      );
    };
    const TodoList = ({ dispatch, todos }) => (
      <ul>
        {todos.map(todo => (
          <TodoItem key={todo.id} dispatch={dispatch} todo={todo} />
        ))}
      </ul>
    );

    const TodoItem = ({ dispatch, todo }) => {
      const handleChange = () =>
        dispatch({
          type: todo.complete ? 'UNDO_TODO' : 'DO_TODO',
          id: todo.id,
        });
     
      return (
        <li>
          <label>
            <input
              type="checkbox"
              checked={todo.complete}
              onChange={handleChange}
            />
            {todo.task}
          </label>
        </li>
      );
    };
     

    réf: React State with Hooks: useReducer, useState, useContext
    https://www.robinwieruch.de/react-state-usereducer-usestate-usecontext/

    4.10. Référence de l'API des Hooks

    useEffect concerne la synchronisation, pas les événements du cycle de vie.

    useCallback: Assurez-vous que les dépendances de fonction sont mises à jour Cas qui ne devraient pas utiliser useCallback (): - aucun entrée / état n'a été utilisé - fonction utilisée une seule fois dans useEffect

    useMemo: Ne pas utiliser si la valeur de retour n'est pas volumineuse (comme une chaîne), cela ajoute une complexité inutile.

    4.11. React List: Mise à jour des items

    Il est possible d’ajouter, mettre à jour et supprimer des éléments de la liste.


    import React from 'react';
    const initialList = [];
    const List = () => {
      const [list, setList] = React.useState(initialList);

      return (
        <div>
          <ul>
            {list.map(item => (
              <li key={item}>{item}</li>
            ))}
          </ul>
        </div>
      );
    };
    export default List;
     

    réf: React List Components by Example
    https://www.robinwieruch.de/react-list-components/

    4.12. React Test unitaires

    L'instruction debugger permet de mettre un point d'arrêt, pour débuguer le test unitaire. Il ne fonctionne qu'en dehors de l'instruction "act", et peut être placé sur le code de l'application, (lors d'un appel api, axios par exemple). L'extension Jest permet d'accélérer le débuguage de test unitaires. Il est plus rapide et ne ré-exécute que les tests non passants. Un accés direct au débuguage apparait au dessus du mot clé "test('should do smthg'" des test non passants.

    before() is run once before all the tests in a describe after() is run once after all the tests in a describe beforeEach() is run before each test in a describe afterEach() is run after each test in a describe

    1) on monte un composant Wrapper. L'instruction debug permet d'afficher en html.


    it('should be possible to activate button with Spacebar', () => {
      const component = mount(<MyComponent />);

      const testWrapper = component.find('button#my-button-one');
      test = testWrapper.debug();
      debugger
       
      component
        .find('button#my-button-one')
        .simulate('keydown', { keyCode: 32 });
      expect(component).toMatchSnapshot();
      component.unmount();
    });
     

    Si le composant requier des props ils faut les passer. Exemple pour un lien:


        act(() => {
          const match = {
            isExact: true,
            params : { catalogueId: stgCatalogueId},
            path: '/catalogue-management/frame-brand-mappings/:catalogueId',
            url: `/catalogue-management/frame-brand-mappings/${stgCatalogueId}`
          };
          brandMapping = mount(
            <DefaultContext>
              <FrameBrandMappingFormData match={match}/>
            </DefaultContext>
          );
        });
     

    2) si le composant lance des requettes, avec axios, on doit mocker les données. Autrement on obtient une erreur 404.


    mock
      .onGet('/users').reply(200, users)
      .onGet('/posts').reply(200, posts);

    // After the first request to /users, this handler is removed
    // The second request to /users will have status code 500
    // Any following request would return a 404 since there are
    // no matching handlers left
    mock
      .onGet('/users').replyOnce(200, users)
      .onGet('/users').replyOnce(500);
     

    3) Pour simuler un click sur un bouton (ici: ouverture d'une fenètre modale): Les commandes releaseEventLoop et update sont très importante; elles lancent la page avec le mocking, et la raffraichissent.


        act(() => {
          wrapper.update();
          wrapper
            .find('tbody tr')
            .at(1)                 // ligne ou se trouve le bouton
            .find('[name="add-edit-brand-mapping"]')  // bouton
            .hostNodes()
            .simulate('click', {});
        });
        debugger
        await releaseEventLoop();
        act(() => {
          wrapper.update();
        });
     

    3) Pour simuler un click sur un radio bouton ou un checkbox :


        act(() => {
          wrapper.update();

          wrapper
            .find('form[name="the-form"] input[name="mapTo"]')
            .at(0)
            .hostNodes()
            .simulate('change', {});
        });
     

    réf: mock adapter
    https://github.com/ctimmerm/axios-mock-adapter
    réf:
    https://airbnb.io/enzyme/docs/api/ShallowWrapper/find.html
    réf:
    https://medium.com/codeclan/testing-react-with-jest-and-enzyme-20505fec4675
    réf: convertisseur en ligne de json en objet javascript
    https://json-to-js.com/

    4.13. Redux

    Redux est une librairie Javascript créée par deux développeurs qui, en utilisant React, ont été confrontés au même problème. Ils souhaitaient pouvoir gérer un state global dans leur application et proprement. N'ayant pas trouvé chaussure à leur pied, ils ont créé leur propre librairie en se basant sur Flux.

    Avantages: Avantages: centraliser les états, stocker des morceaux par domaine, fonctions pures, prédiction

    Cas pouvant nécessiter l'utilisation du formulaire Redux: - Lorsqu'un formulaire dont chaque valeur change dans chacun des champs, et doit envoyer une action. - Lorsque chaque élément du formulaire a besoin de sa propre paire action / réducteur. - Validation compliquée: synchrone, asynchrone, au niveau du champ… - Soumettre le formulaire via une action envoyée - Le formulaire Redux a des réducteurs pré-construits, les actions de focus / changement / flou sont automatiquement envoyées et écoutées pour les composants du formulaire. Ils peuvent également importer des actions dans nos propres actions

    En redux utiliser useEffect à la place componentDidMount et componentDidUpdate.

    Utiliser des variables globale dans une application Redux:

    1) Utilisez le state et ne le changez pas.

    2) Utilisez le contexte. C'est une solution simple, mais selon la page d'accueil de React, c'est expérimental, ce qui peut rendre mon application instable.

    3) Collectez toutes les variables globales dans un seul fichier js et utilisez-le comme un module js.

    4) Déclarez les variables d'environnement npm du fichier de config webpack et utilisez-les comme constantes globales.

    Voici donc, schématiquement, le fonctionnement de la librairie Redux : ______ |-- Action <- | | | _______ | View | |-> | Store | -> |______| | State |

    Action : flux d'information que l'on souhaite envoyer à notre state global.

    Store : objet qui possède et gère un state de votre application. Avec Flux, il effectue le traitement nécessaire sur son state en fonction de l'action reçue.

    View : ce que l'utilisateur voit, ce sur quoi il interagit. En React, une View correspond à un component. Ce sont donc nos components qui vont émettre des actions.

    Votre View crée et envoie une action. Cette action est récupérée par le store. Le store modifie le state de votre application en fonction de l'action reçue. Votre View détecte les changements du state global et se re-rend. On garde toujours ce fonctionnement avec un flux de données à sens unique, propre au one-way data flow et à Flux. Pour modifier le state de l'application, le store contient des reducers qui modifient le state. Généralement, chaque reducer gère une fonctionnalité de votre state global.


      import { createStore } from "redux";
      import { Provider } from "react-redux";
      import rootReducer from "./reducers/rootReducer";
      const store = createStore(rootReducer);
      ReactDom.render(
        <Provider store={store}>
          <App />
        </Provider>,
        document.getElementById("roor");
      );

      const initState = { posts: [
       {id: "1", title: "", body: ""},
       {id: "2", title: "", body: ""},
       {id: "3", title: "", body: ""}] }
      function rootReducer (state= initState, action) => {
        // Modification du state en fonction de l'action ici
        return nextState // Renvoie le state mis à jour
      }
      export default rootReducer;

      // Composant Accueil sans Redux
      const Accueil = () {
        return (
          <div className="container">
            <h4>Page accueil</h4>
          </div>
        );
      }
      export default Accueil;
     
      // Composant Accueil avec Redux
      import { connect } from "react-redux";
      class Accueil extends Component {
        render() {
          const { posts } = this.props;
          const postData = posts.length ? (
            posts.map(post => {
              return (
                <div className="base" key={post.id}>
                  <h2>{post.title}</h2>
                  <p>{post.body}</p>
                </div>
              );
            });
          ) : (
            <p>Auvun article</p>
          ); // const postData =
          return (
            <div className="container">
              <h4>Page accueil</h4>
              {postData}
            </div>
          );
        }
      }
      const mapStateToProps = state => {// state est le state de rootReducer (store)
        return { posts: state.posts };  // posts recevra le tableau posts du reducer
      };
      // Pour connecter l'accueil au store
      export default connect(mapStateToProps)(Accueil);
     

    réf:
    https://openclassrooms.com/fr/courses/4902061-developpez-une-application-mobile-react-native/5046311-decouvrez-redux
    réf: Découvrez async/await sous React 16 et Intégration Redux
    https://www.youtube.com/watch?v=AUV87UIc5nc
    réf: Découvrez Redux Kezako sous React 16 et Intégration
    https://www.youtube.com/watch?v=sUsfbTii-G8
    réf: premiers pas avec React
    https://www.youtube.com/watch?v=W8ePTZXSkTY&list=PLuWyq_EO5_AIonD4eb0-s2BziRaGpKCps&index=24
    réf: Fetch Data (appel API) sous React 16 et Intégration Redux
    https://www.youtube.com/watch?v=frqL3jyU0Yc

    4.14. Redux Saga

    Un projet React sans Redux (ou sans Flux, ou Reflux, ou Fluxxor, ou Mobx), c’est très rare. Aujourd’hui, on va s’intéresser à un moyen de rendre notre state encore plus prévisible avec Redux Saga. C’est simple, et efficace.

    Vous connaissez surement Redux thunk. Si si. Un Thunk permet notamment de lancer des tâches asynchrones. Enfin, c’est pas tout à fait ça car les dispatch envoyés depuis un middleware sont synchrones, mais pas leur réponse si par exemple il s’agit d’appels Ajax, mais ça peut globalement être tout ce qui est asynchrone en général, comme une tâche de fond.

    Ca ressemble à ça :


      export function monaction(payload){
        return (dispatch){
          dispatch(mangeca1(payload))
          dispatch(mangeca2(payload))
        }
      }
     

    C’est très pratique, mais ça a un coût : vous ne maitrisez pas ce qui se passe exactement dans le state de votre application. C’est précisément ce qu’on veut éviter avec Redux qui est, de base, une façon plutôt simple de gérer un state révisible.

    Quand vous faites un appel AJAX, il y a plein de choses que vous ne maitrisez pas et qui ont des conséquences sur le state, notamment le fait que l’appel peut prendre beaucoup de temps et que l’utilisateur peut déclencher d’autres actions entre le moment où l’appel est lancé et où l’appel reçoit une réponse. Peut être que, parmi la quantité d’appels qu’il envoi au même webservice, seul la dernière réponse l’intéresse (pour suivre un hashtag sur Twitter par exemple). Tout ça – et bien plus, on peut le gérer avec les Sagas.

    Mise en pratique d'un cas assez simple. Admettons que vous avez un projet structuré comme ça :

    actions/users.js
    reducers/users.js
    

    Au sein de actions/users, vous dispatcher une action comme ça :


    export function login(payload){
      return {
        type: LOGIN,
        payload
      }
    }
     

    Vous aimeriez pouvoir lancer d’autres actions si l’utilisateur arrive à se logger (par exemple en stockant un JWT en localstorage, puis en redirigeant l’utilisateur sur son dashboard), ou d’autres si la tentative de login a échouée (afficher un message d’erreur, afficher un captcha au bout de 3 tentatives…).

    Old school: Vous pourriez très bien vous débrouiller sans les Sagas, uniquement avec les Thunks, mais ça rendrait vos actions crades et, surtout, impures, sans parler des éventuels effets de bord.


    export function login(payload){
      return (dispatch) => {
        fetch(endPoint, {
            method  : 'POST',
            headers : {}
            body: {}
          })
          .then(statusHelper)
          .then(response => response.json())
          .catch( error => {
            dispatch(loginError(error)
          })
          .then(data => {
              dispatch(loginSuccess(data))
           })
      }
    }
     

    Nan, tout ça c’est pas terrible.

    La même chose avec les Sagas: On a toujours besoin de l’action type LOGIN, on revient donc à la première solution :


    export function login(payload){
      return {
        type: LOGIN,
        payload
      }
    }
     

    Désormais ce sont les SAGAS qui vont écouter le dispatch de l’action LOGIN, voici une version simplifiée sans l’implémentation de l’effect combinator Race (vous devriez tout de même l’utiliser pour gérer la concurrence entre l’action de Login et l’action de Logout) :


    // Les effects creators dont on a besoin pour écouter, appeler et renvoyer quelque chose, cf la doc :)
    import {takeEvery, call, put} from 'redux-sagas/effects'
    // Avec un bon vieux fetch des familles
    import authApi from '../endpoints/authApi'
    import {loginSuccess, loginError} from '../actions/users'

    export function* authorize(action){
      try{
        const loginResult = yield call(authApi.login, action);
        yield put(loginSuccess(loginResult))
      }
      catch(error){
        yield put(loginError(error))
      }
    }

    export function* watchLoginFlow(){
     yield takeEvery('LOGIN', authorize)
    }

    export default watchLoginFlow;
     

    Notre authApi pour info :


    const authApi = {

      login(action){
        return fetch(authEndpoint, {
            method  : 'POST',
            headers : {
              'Accept': 'application/json',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({username: action.user, password: action.password})
          })
          .then(statusHelper)
          .then(response => response.json())
          .then(data => {
              return data
           })
      },
    }

    export default authApi
     

    Notez qu’on catch déjà dans la saga, on ne catch donc pas dans l’appel de l’API, ce serait de la gourmandise et ça enlèverait tout le charme des Sagas et des puts qu’on voudrait potentiellement mettre en place en cas de loginError.

    Le plus beau dans tout ça, c’est qu’on pourrait coller tout plein d’autres put pour dispatcher d’autres actions, elles s’exécuteraient les unes à la suite des autres grâce aux générateurs. Ca peut aller nettement plus loin.

    Je parlais de Race tout à l’heure, qui est un effect combinator. L’idée derrière Race est de définir un winner :


    let request = yield take('LOGIN_REQUEST')
    let winner = yield race({
      auth: call(authorize, request),
      logout: take('LOGOUT')
    })
     

    On peut ensuite travailler avec le winner pour définir la stratégie à employer. - Si le winner est auth, on fait un call à l’api pour authentifier, - si le winner est logout, on travaille sur la déconnexion. C’est très bien expliqué dans la documentation RacingEffects.

    Vu le gain apporté par Redux-Sagas, que ce soit en terme de lisibilité dans le code, ou (surtout) en prévention des effets de bord, ce serait dommage de s’en privé. Mais… comment fonctionnent les Sagas au sein d’une application React / Redux ? C’est plutôt simple en fait, comme un middleware :

    Vous avez juste à modifier la configuration de votre store en y insérant votre middleware, sans oublier de définir de le run :


    import { createStore, compose, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import createSagaMiddleware from 'redux-saga'
    import rootReducer from '../reducers';

    import rootSaga from '../sagas'
    const sagaMiddleware = createSagaMiddleware(rootSaga)

    export default function configureStore(initialState) {
      const store = createStore(
        rootReducer,
        initialState,
        compose (
          applyMiddleware(thunk),
          applyMiddleware(sagaMiddleware),
        )
      );
      sagaMiddleware.run(rootSaga) // IMPORTANT !!!
      return store;
    }
     

    Votre RootSaga pourrait ressembler à ça :


    import {fork} from 'redux-saga/effects'
    import watchLoginFlow from './login'

    const sagas = [
      watchLoginFlow,
    ]

    function* rootSaga() {
      yield sagas.map(saga => fork(saga));
    }
    export default rootSaga
     

    réf: si on decouvrait redux saga
    http://www.babonaux.com/2017/07/17/on-decouvrait-redux-saga/
    réf: redux-saga RacingEffects
    https://redux-saga.js.org/docs/advanced/RacingEffects.html
    réf: redux-saga
    https://redux-saga.js.org/

    CHAPITRE 5 - Référence de l'API

    L’objet React est le point d’entrée de la bibliothèque React. Si vous chargez React depuis une balise "script", ces API de haut-niveau sont disponibles depuis l’objet global React. Si vous utilisez npm avec la syntaxe ES6, vous pouvez écrire : import React from 'react'. Si vous utilisez npm avec la syntaxe ES5, vous pouvez écrire : var React = require('react').

    5.1. React.Component

    Les méthodes suivantes sont appelées dans cet ordre lorsqu’une instance d’un composant est créée puis insérée dans le DOM :

    • constructor()
    • static getDerivedStateFromProps()
    • render()
    • componentDidMount()

    Une mise à jour est déclenchée par des changements dans les props ou l’état local. Les méthodes suivantes sont appelées dans cet ordre quand un composant se rafraîchit :

    • static getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()

    La méthode suivante est appelée quand un composant est retiré du DOM :

    • componentWillUnmount

    Ces méthodes sont appelées lorsqu’une erreur survient au sein de n’importe quel composant enfant lors de son rendu, dans une méthode de cycle de vie, ou dans son constructeur.

    • static getDerivedStateFromError()
    • componentDidCatch()

    Chaque composant fournit par ailleurs quelques API supplémentaires :

    • setState()
    • forceUpdate()

    Propriétés de classes: defaultProps, displayName Propriétés d’instances: props, state

    render()

    Lorsqu’elle est appelée, elle examine en général this. props et this. state et renvoie un des types suivants :

    Éléments React. Typiquement créés via JSX. Par exemple,

    et sont des éléments React qui demandent à React de produire, respectivement, un nœud DOM et un autre composant défini par l’utilisateur.

    Tableaux et fragments. Ils permettent de renvoyer plusieurs éléments racines depuis un rendu. Il existe une nouvelle syntaxe plus concise, que vous pouvez utiliser pour déclarer des fragments. Ça ressemble à des balises vides :


                  <>
                    <td>Bonjour</td>
                    <td>Monde</td>
                  </>
     

    Portails. Ils permettent d’effectuer le rendu des enfants dans une autre partie du DOM. React ne crée pas une nouvelle div, mais affiche les enfants dans "domNode". "domNode" peut être n’importe quel élément valide du DOM, peu importe sa position.


                  render() {
                    return ReactDOM.createPortal(
                      this.props.children,
                      domNode  );
                  }
     

    Chaînes de caractères et nombres. Ils deviennent des nœuds textuels dans le DOM.

    Booléens ou null. Ils ne produisent rien. (Ça existe principalement pour permettre des motifs de code tels que return test && , ou test serait booléen.)

    render() ne sera pas appelée si shouldComponentUpdate() renvoie false.

    constructor()

    componentDidMount()

    componentDidMount() est appelée immédiatement après que le composant est monté (inséré dans l’arbre). C’est ici que vous devriez placer les initialisations qui requièrent l’existence de nœuds du DOM. Si vous avez besoin de charger des données depuis un point d’accès distant, c’est aussi le bon endroit pour déclencher votre requête réseau.

    C’est enfin là que vous devriez mettre en place vos abonnements. Si vous en faites, n’oubliez pas de vous désabonner dans componentWillUnmount().

    Vous avez le droit d’appeler setState() directement dans componentDidMount(). Ça déclenchera un rendu supplémentaire, mais il surviendra avant que le navigateur ne mette à jour l’écran. A éviter car cela nuit à la performance, mais dans certains cas tels que les boîtes de dialogues et infobulles, qui ont souvent besoin de mesurer un nœud du DOM avant d’afficher quelque chose qui dépend de leur taille ou de leur position, ce second rendu peut s’avérer nécessaire.

    componentDidUpdate()

    componentDidUpdate() est appelée immédiatement après que la mise à jour a eu lieu. Cette méthode n’est pas appelée pour le rendu initial. Elle vous donne l’opportunité de travailler sur le DOM une fois que le composant a été mis à jour. C’est aussi un bon endroit pour faire des requêtes réseau, du moment que vous prenez soin de vérifier que les props actuelles concernées diffèrent des anciennes props (dans le sens où une requête réseau est peut-être superflue si les props en question n’ont pas changé).


      componentDidUpdate(prevProps) {
        if (this.props.userID !== prevProps.userID) {
          this.fetchData(this.props.userID);
        }
      }
     

    Vous avez le droit d’appeler setState() directement dans componentDidUpdate() mais notez bien que vous devez l’enrober dans une condition, comme dans l’exemple ci-dessus, ou vous obtiendrez l’équivalent d’une boucle infinie. Là aussi, vous déclencherez un rendu supplémentaire. Si vous essayez de refléter dans l’état local une prop venant de plus haut, voyez si vous ne pouvez pas plutôt utiliser directement la prop.

    componentDidUpdate ne sera pas appelée si shouldComponentUpdate() renvoie false.

    componentWillUnmount()

    componentWillUnmount() est appelée immédiatement avant qu’un composant soit démonté ou détruit. Mettez-y tout nettoyage nécessaire, tel que l’invalidation de minuteurs, l’annulation de requêtes réseau, ou la résiliation d’abonnements effectués dans componentDidMount()

    Autres API

    Contrairement aux méthodes de cycle de vie ci-dessus (que React appelle pour vous), c’est vous qui appelez les méthodes ci-dessous depuis vos composants. Il n’y en a que deux : setState() et forceUpdate()

    setState() planifie des modifications à l’état local du composant, et indique à React que ce composant et ses enfants ont besoin d’être rafraîchis une fois l’état mis à jour. C’est en général ainsi qu’on met à jour l’interface utilisateur en réaction à des événements ou réponses réseau.

    setState() ne met pas toujours immédiatement le composant à jour. Il peut regrouper les mises à jour voire les différer. En conséquence, lire la valeur de this.state juste après avoir appelé setState() est une mauvaise idée. Utilisez plutôt componentDidUpdate ou la fonction de rappel de setState: (setState(updater, callback))

    Le premier argument updater est une fonction dont la signature est : (state, props) => stateChange

    state est une référence à l’état local du composant au moment où cette modification est appliquée. Cet état ne devrait pas être modifié directement. Au lieu de ça, on représente les changements à apporter en construisant un nouvel objet basé sur les données entrantes de state et props. Par exemple, imaginons que nous voulions incrémenter une valeur dans l’état à raison de props.step :

    this.setState((state, props) => { return {counter: state.counter + props.step}; });

    Le second argument de setState() est une fonction de rappel optionnelle qui sera exécutée une fois que setState est terminé et le composant rafraîchi. D’une façon générale, nous vous recommandons plutôt d’utiliser componentDidUpdate() pour ce genre de besoin.

    Si votre méthode render() dépend d’autres données, vous pouvez indiquer à React que le composant a besoin d’un rafraîchissement en appelant forceUpdate(). Appeler forceUpdate() déclenchera le render() du composant, en faisant l’impasse sur shouldComponentUpdate(). Ça déclenchera les méthodes usuelles de cycle de vie des composants enfants, y compris la méthode shouldComponentUpdate() de chaque enfant. React continuera à ne mettre à jour le DOM que si le balisage change. De façon générale, vous devriez tout faire pour éviter de recourir à forceUpdate(), et faire que votre render() ne lise que this.props et this.state.

    Propriétés

    defaultProps peut être définie comme propriété sur la classe du composant elle-même, pour définir les valeurs par défaut de props pour cette classe. On s’en sert pour les props undefined, mais pas pour celles à null. Par exemple :


      class CustomButton extends React.Component {
        // ...
      }
     
      CustomButton.defaultProps = {
        color: 'blue'
      };
     

    La chaîne de caractères displayName est utilisée dans les messages de débogage. La plupart du temps, vous n’avez pas besoin de la définir explicitement parce qu’elle est déduite du nom de la fonction ou classe qui définit le composant. Mais on peut vouloir la définir lorsqu’on veut afficher un nom différent pour des raisons de débogage ou lorsqu’on crée un composant d’ordre supérieur.

    Au fur et à mesure que votre application grandit, vous pouvez détecter un grand nombre de bugs grâce à la validation de types. Dans certains cas, vous pouvez utiliser des extensions JavaScript comme Flow ou TypeScript pour valider les types de toute votre application. Mais même si vous ne les utilisez pas, React possède ses propres fonctionnalités de validation de types. Pour lancer la validation de types des props d’un composant, vous pouvez ajouter la propriété spéciale propTypes :


      import PropTypes from 'prop-types';
     
      class Greeting extends React.Component {
        render() {
          return (
            <h1>Bonjour, {this.props.name}</h1>
          );
        }
      }
      Greeting.propTypes = {  name: PropTypes.string};
     

    CHAPITRE 6 - Framworks

    Frameworks: - Jest: librairie de test unitaire - Cypress: librairie de tests end to end - Bootstrap: librairie HTMLclient - Axios: librarie HTML client

    6.1. Jest

    Jest est un framework pour faire du test unitaire sur du javascript. installation: yarn add --dev jest | npm install --save-dev jest premier test: package.json, sum.js, sum.test.js lancer le test: yarn test | npm run test resultat: PASS ./sum.test.js ? adds 1 + 2 to equal 3 (5ms)

    package.json : prise en compte de Jest

    { "scripts": { "test": "jest" } }

    sum.js : code javascript sum.test.js : test unitaire

    function sum(a, b) { const sum = require('./sum');
    return a + b; test('adds 1 + 2 to equal 3', () => { } expect(sum(1, 2)).toBe(3);
    module.exports = sum; });

    Exemples:


    test('object assignment', () => {
      const data = {one: 1};
      data['two'] = 2;
      expect(data).toEqual({one: 1, two: 2});
    });
    expect(a + b).not.toBe(0);
    expect(value).toBeCloseTo(0.3)

    toBeNull, toBeUndefined, toBeDefined, toBeTruthy, toBeFalsy
    toBeGreaterThan, toBeGreaterThanOrEqual, toBeLessThan, toBeLessThanOrEqual
    toMatch

    const shoppingList = [
      'diapers',
      'kleenex',
      'trash bags',
      'paper towels',
      'beer',
    ];

    test('the shopping list has beer on it', () => {
      expect(shoppingList).toContain('beer');
      expect(new Set(shoppingList)).toContain('beer');
    });
     

    Exeptions:


    function compileAndroidCode() {
      throw new ConfigError('you are using the wrong JDK');
    }

    test('compiling android goes as expected', () => {
      expect(compileAndroidCode).toThrow();
      expect(compileAndroidCode).toThrow(ConfigError);

      // You can also use the exact error message or a regexp
      expect(compileAndroidCode).toThrow('you are using the wrong JDK');
      expect(compileAndroidCode).toThrow(/JDK/);
    });
     

    Jest enzyme est une librairie permettant de simplifier les tests sur les composants React.

    Enzime n'est pas encore compatible avec react 17. Autrement vous aurrez l'erreur TypeError: Cannot read property 'child' of undefined sur la fonction mount npm install --save react@^16.8.0 react-dom@^16.8.0


    import React from 'react';
    import {Page, UserInfoWithTitle, UserInfo} from './App';
    import { mount } from 'enzyme';

    test('Has expected user info', () => {
      const component = mount(<Page user={{firstName:'Simon', lastName:'templar'}}/>);
      const userInfoComponent = component.find(UserInfoWithTitle).find(UserInfo)
      expect(userInfoComponent.text()).toMatch('Simontemplar');
    });
     

    réf: jest enzyme avec babel
    https://leprogrammeurmarocain.com/react-jest-et-enzyme/s
    réf: jest enzyme
    https://blog.ineat-group.com/2019/09/creer-et-tester-vos-composants-react-avec-jest-enzyme/
    réf: jestjs
    https://jestjs.io/docs/en/using-matchers

    6.2. Cypress

    Des tests rapides, simples et fiables pour tout ce qui tourne dans un navigateur npm install cypress

    Les tests écrits avec Cypress sont faciles à lire et à comprendre. Ceux-ci sont écrits en Javascript, à l’aide des librairies Mocha, Chai et Sinon.js

    Cypress fonctionne avec un serveur Node.js. Cypress et Node. js communiquent et synchronisent constamment leurs tâches. Ce mode permet de répondre aux événements de l'application en temps réel, tout en travaillant à l'extérieur du navigateur, pour les tâches qui requièrent un privilège plus élevé.

    Cypress opère également au niveau de la couche réseau, en lisant et en modifiant le trafic à la volée. Ainsi, Cypress peut modifier tout ce qui entre et sort du navigateur.

    Enfin, comme Cypress est installé en local, il peut également utiliser le système d'exploitation pour des tâches d'automatisation. Cela permet d'effectuer des tâches telles que la prise de captures d'écran et l'enregistrement de vidéos

    Cypress fonctionne au sein de l’application testée, il a alors un accès natif à chaque objet. Qu'il s'agisse de la fenêtre, du document, d'un élément DOM, d'une fonction, d'un timer ou de tout autre chose, vous y avez accès dans vos tests Cypress. Le code de test peut accéder aux mêmes objets que le code applicatif.

    Le fait d’avoir le contrôle sur l’application testée, le trafic réseau et l'accès natif à chaque objet hôte, donne de nouvelles possibilités. Cypress permet de modifier n'importe quel aspect du fonctionnement de l'application. Au lieu de tests lents et coûteux, tels que la création de l'état requis pour une situation donnée, vous pouvez simplement créer ces états artificiellement comme vous le feriez dans un test unitaire. Il est, par exemple, possible de :

    • Mocker le navigateur ou les fonctions de l’application et de les forcer à se comporter comme il le faut dans un scénario de test.

    • Exposer des data-stores afin de modifier l'état de l’application directement à partir du code de test.

    • Tester comment l’application réagit aux erreurs en retournant des codes d’erreurs HTTP.

    • Modifier les éléments DOM.

    • Piloter des composants tiers : au lieu de se préoccuper des composants comme les sélections multiples, les listes déroulantes, ou les date-pickers, il suffit d'appeler les méthodes directement à partir du code de test pour les contrôler.

    • Contrôler le temps de façon à ce que les timers se déclenchent directement sans avoir à attendre le temps requis.

    Cypress a avant tout été conçu dans un soucis d’ergonomie. Des centaines de messages d'erreur personnalisés décrivent la raison d’échec d’un test. Une interface montre visuellement l'exécution des commandes, les assertions, les requêtes réseau, les chargements de pages ou les changements d'URL. Cypress prend des snapshots de l’application et permet de remonter dans le temps jusqu'à l'état dans lequel elle se trouvait au moment de l'exécution des commandes. Enfin, il est possible d’utiliser DevTools pendant l'exécution des tests, de consulter les messages de la console et les requêtes réseau, et même d’inspecter les éléments du DOM.


    https://blog.sodifrance.fr/creez-vos-tests-end-to-end-avec-cypress-io/

    6.3. Axios

    Axios est un client HTTP léger basé sur le service $ http de Angular.js v1.x et similaire à l'API Fetch. Axios est basé sur des promesses et nous pouvons donc tirer parti de l’async et attendre un code asynchrone plus lisible. Nous pouvons également intercepter et annuler des demandes, et il existe une protection intégrée du côté client contre la falsification de demandes intersites.

    Son utilisation dans un projet React est simple! Dans cet exemple, nous utiliserons Axios pour accéder à l'API JSON Placeholder commune au sein d'une application React. Nous pouvons commencer par ajouter Axios à notre projet:


    # Yarn
    $ yarn add axios
    
    # npm
    $ npm install axios --save
    

    Requettes GET

    Si nous créons ensuite un nouveau composant nommé PersonList, nous pourrons nous connecter au hook de cycle de vie composantDidMount et exécuter une requête GET après avoir importé axios.


    import React from 'react';
    import axios from 'axios';

    export default class PersonList extends React.Component {
      state = {
        persons: []
      }

      componentDidMount() {
        axios.get(`https://jsonplaceholder.typicode.com/users`)
          .then(res => {
            const persons = res.data;
            this.setState({ persons });
          })
      }

      render() {
        return (
          <ul>
            { this.state.persons.map(person => <li>{person.name}</li>)}
          </ul>
        )
      }
    }
     

    En utilisant axios.get (url), nous obtenons une promesse qui renvoie un objet de réponse. En recherchant les données de réponse, nous avons attribué la valeur personne à res.data.

    Nous pouvons également obtenir d'autres informations sur notre demande, telles que le code de statut sous res. status ou des informations supplémentaires dans res.request.

    Requettes POST

    Nous pouvons gérer d'autres verbes tels que POST et PUT de la même manière. Créons un formulaire qui permet la saisie de l’utilisateur, puis POST le contenu à une API:


    import React from 'react';
    import axios from 'axios';

    export default class PersonList extends React.Component {
      state = {
        name: '',
      }

      handleChange = event => {
        this.setState({ name: event.target.value });
      }

      handleSubmit = event => {
        event.preventDefault();

        const user = {
          name: this.state.name
        };

        axios.post(`https://jsonplaceholder.typicode.com/users`, { user })
          .then(res => {
            console.log(res);
            console.log(res.data);
          })
      }

      render() {
        return (
          <div>
            <form onSubmit={this.handleSubmit}>
              <label>
                Person Name:
                <input type="text" name="name" onChange={this.handleChange} />
              </label>
              <button type="submit">Add</button>
            </form>
          </div>
        )
      }
    }
     

    L'utilisation de POST nous donne le même objet de réponse avec des informations que nous pouvons utiliser dans notre appel.

    Requettes DELETE

    Nous pouvons supprimer des éléments de notre API en utilisant axios.delete et en transmettant l’URL en tant que paramètre. Changeons notre formulaire pour supprimer un utilisateur au lieu d’en ajouter un nouveau:


    import React from 'react';
    import axios from 'axios';

    export default class PersonList extends React.Component {
      state = {
        id: '',
      }

      handleChange = event => {
        this.setState({ id: event.target.value });
      }

      handleSubmit = event => {
        event.preventDefault();

        axios.delete(`https://jsonplaceholder.typicode.com/users/${this.state.id}`)
          .then(res => {
            console.log(res);
            console.log(res.data);
          })
      }

      render() {
        return (
          <div>
            <form onSubmit={this.handleSubmit}>
              <label>
                Person ID:
                <input type="text" name="id" onChange={this.handleChange} />
              </label>
              <button type="submit">Delete</button>
            </form>
          </div>
        )
      }
    }
     

    Une fois encore, notre objet res nous fournit des informations.

    Instance de base

    Axios nous permet de définir une instance de base dans laquelle nous pouvons définir une URL et tout autre élément de configuration. Créons un fichier nommé api. js et exportons une nouvelle instance axios avec les valeurs par défaut suivantes:


    import axios from 'axios';

    export default axios.create({
      baseURL: `http://jsonplaceholder.typicode.com/`
    });
     

    Il peut ensuite être utilisé dans notre composant en important notre nouvelle instance de la manière suivante:


    // Omitted
    import API from '../api';

    export default class PersonList extends React.Component {
      handleSubmit = event => {
        event.preventDefault();

        API.delete(`users/${this.state.id}`)
          .then(res => {
            console.log(res);
            console.log(res.data);
          })
      }
    }
     

    Utilisation de async et await

    Nous pouvons rendre le travail avec des promesses encore plus simple avec async et attendre. Le mot-clé await résout la promesse et renvoie la valeur que nous pouvons affecter à une variable. Voici un exemple:


    handleSubmit = async event => {
      event.preventDefault();

      // Promise is resolved and value is inside of the response const.
      const response = await API.delete(`users/${this.state.id}`);

      console.log(response);
      console.log(response.data);
    };
     

    réf: https://alligator.io/react/axios-react/

    6.4. Formik

    Formik est une petite bibliothèque qui vous aide avec les 3 parties ennuyeuses: - Obtenir des valeurs dans et hors de l'état de la forme - Messages de validation et d'erreur - Traitement du formulaire de soumission

    Formik est devenu l'outil de gestion des formulaires le plus adopté pour React dans le but de simplifier le processus de création et de maintenance des formulaires. Formik fournit un contexte de formulaire, définissant la structure sous-jacente de gestion d'un formulaire avec un petit nombre de composants importables et de HOC. Avec le problème du contexte de formulaire résoluc avec Formik, les développeurs sont libres de se concentrer sur le comportement qu'ils souhaitent obtenir à partir de leurs formulaires.

    Pour ajoutez Formik en tant que dépendance à votre projet: yarn add formik

    Formik transmet une série d'entrées props dans les éléments du formulaire, gère la validation et la soumission, et vérifie automatiquement si les entrées ont été interagies. Ces entrées, qui sont tous répertoriés ici, constituent le pivot de Formik: tant que ces entrées sont injectés dans des composants , ce composant aura un contexte Formik et sera capable d’utiliser tout ce que Formik a à offrir Il existe deux moyens d'injecter le contexte Formik dans des composants - soit en encapsulant vos composants avec , - soit en utilisant un HOC fourni par Formik - withFormik.


    import { Formik, Field, Form, FieldArray } from 'formik';

    // defining a form within <Formik />
    const FormikForm = () =>
      <Formik
        ...
        onSubmit={() => {...}}
        validate={() => {...}}
        render={
          ({ handleSubmit,
             handleChange,
             handleBlur,
             values,
             errors
          }) => {
          <form onSubmit={handleSubmit}>
             <Field
               name="email"
               component='input'
              />
              ...
           </form>
          }
        }
     />

    // destructuring formik propsrender={({ values }) => {
       console.log(values.email);
    }// this is the same value as:render={props => {
       console.log(props.values.email);
    }
     

    sur un formulaire Formik, utiliser les événements ainsi


      // used only for refresh
      const [selectedEntity, setSelectedEntity] = useState(null);
     
      <Field
        component={LookupSelect}
        id={formFieldId}
        className={className}
        name={'selectedCountryToAdd'}
        invalid={isInvalid}
        apiRequest={searchCountry}
        onChange={(e:any) => {addCountry(values);}}
      />

      function addCountry(values: any) {
        console.log('addCountry', values);
        values.countriesIds.push(values.selectedCountryToAdd.id);

        // used only for refresh
        setSelectedEntity(values.selectedCountryToAdd);
        if (selectedEntity !== null)
        {
          values.selectedCountryToAdd = selectedEntity;
        }
        return;
      }

          <Formik
            initialValues={toggler.data}
            onSubmit={handleSave}
            // validationSchema={validations}
            validate={(values) => {console.log(values);}}
          >
     

    Formik fournit un composant qui affichera une erreur si l'attribut ne correspond pas à l'entrée. Ceci est beaucoup plus propre e nous évite de réécrire des conditions pour chaque champ de formulaire.


      {formFields.map(formField => {
        return (
          <>
            <ErrorMessage name={formField.name}>
              {msg => <div className="error error-message">{msg}</div>}
            </ErrorMessage>
          </>
        );
      })}
     

    Customisation de Formik

    L'entrée children, dans React, fait référence à la boîte générique dont le contenu est inconnu jusqu'à ce qu'il soit transmis par le composant parent.

    Exemple pour afficher une photo avec une description


    myPhoto:
    return (
      <div>
        <div className="myImage">
          <img src={this.props.src} width={this.props.width} />
        </div>
        {this.props.children}
      </div>
    )

    return (
      <div className="App">
        <Photo src={src} width={width}>
          <div className="source">
            Source : Unsplash, captured by @mcramblett
          </div>
        </Photo>
      </div>
    )
     

    réf: Customisation de Formik,
    https://github.com/TheDiamondDoge/management_board_react_gui/blob/master/pmboard-gui-bp3/src/components/formik-custom-field/formik-custom-field.js
    réf: react children
    https://medium.com/javascript-in-plain-english/how-to-use-props-children-in-react-7d6ab5836c9d
    réf: Formik
    https://medium.com/@rossbulat/react-forms-with-formik-hocs-and-functional-components-df394afd2039
    réf: Validation
    https://hackernoon.com/react-form-validation-with-formik-and-yup-8b76bda62e10
    réf: fieldarray
    https://jaredpalmer.com/formik/docs/api/fieldarray
    réf: setFieldValue
    https://github.com/jaredpalmer/formik/issues/529
    réf: sorted list
    https://github.com/jaredpalmer/formik/issues/11

    6.5. Validations Yup

    par défaut yup n'accepte pas les valeurs null, pour les accepter il faut ajouter nullable(true) dans la partie otherwise


      const validations = Yup.object().shape({
        clProductName: Yup.string()
          .required('It is required.')
          .max(300, 'Max 50 allowed.'),
        mapTo: Yup.string().required(),
        hubClModel: Yup.object().when('mapTo', {
          is: val => val === 'existing',
          then: Yup.object().required(),
          otherwise: Yup.object()
            .nullable(true)
            .notRequired()
        })
      });
      ...
      <Formik
        initialValues={toggler.data}
        onSubmit={handleSave}
        validationSchema={validations}
      >
     

    pour une validation reposant sur plusieurs champs:


     const passwordValidation = yup.object().shape({
      password: yup
        .string()
        .trim()
        .min(6, "Password needs to be at least 6 characters")
        .max(20, "Password is longer than 26 characters")
        .required("Please enter your password"),
      confirmPassword: yup
        .string()
        .trim()
        .required("Please confirm your password")
        .oneOf([yup.ref("password"), null], "Passwords do not match")
    });
     

    pour accèder à tous les champs du formulaire, avec une fonction non fléchée.


     const passwordValidation = yup.object().shape({
      confirmPassword: yup
        .string()
        .required('Please confirm your password')
        .test(
          'checkDuplicate',
          'Passwords do not match'),
          async function(value: any) {
            // tslint:disable-next-line: no-invalid-this
            const { password } = this.parent;
            return (password === value);
          }
     

    pour une validation asynchrone :


    const validationSchema = Yup.object().shape({
        username:
            Yup.string()
                .test('checkDuplUsername', 'same name exists', function (value) {
                    return new Promise((resolve, reject) => {
                        kn.http({
                            url: `/v1/users/${value}`,
                            method: 'head',
                        }).then(() => {
                            // exists
                            resolve(false)
                        }).catch(() => {
                            // note exists
                            resolve(true)
                        })
                    })
                })
    })
     

    pour une validation asynchrone avec axios:


        catalogueCheckDuplicate: Yup.object()
        .required('It is required.')
        .test('checkDuplFrameBrand', 'same frame brand exist', (value) =>
          new Promise((resolve, reject) => {
            console.log(Yup.ref('brandDescription'));
            baseApi
            .get<any[]>(
              `/framebrandmapping/catalogueframebrand`,
              { brandCode: value.brandCode,
                brandDescription: value.format }
            )
            .then((response: AxiosResponse<ApiResponse<any[]>>) => {
                console.log(response.data.data.length);
                if (response.data.data.length === 0) {
                resolve(false);
                }
                else {
                    resolve(true);
                }
              })
            .promise.catch(() => {
                console.log('ko');
                resolve(true);
            });
          })
        ),
     

    réf: yup.reach with multiple field
    https://github.com/jquense/yup/issues/570
    réf: yup async validation
    https://stackoverflow.com/questions/55811114/async-validation-with-formik-yup-and-react
    réf: yup with formik
    https://heartbeat.fritz.ai/build-and-validate-forms-in-react-native-using-formik-and-yup-6489e2dff6a2

    6.6. jsonserver

    npm install json-server


    data/country.json :
    {
      "country": [
        {
          "id": "af75c433-9446-418e-8e51-56a94dbd5366",
          "countryName": "testPL2",
          "type": "Ocuco.Features.CatalogManagement.Services.ViewModels.Country.CountryResponse",
          "createdDate": "2019-04-26T04:40:35.569462",
          "lastUpdatedDate": "2019-09-27T14:34:38.214719",
          "links": {}
        }
      ]
    }
     

    server.js


    const country = require('./data/country.json');

    const dbObj = {
      ...authenticationTokens,
      ...country,
      ...processingdetails
    };

    /** CONFIGURATION **/
    const server = jsonServer.create();

    const getDbInitialState = () => lodash.cloneDeep(dbObj);

    const router = jsonServer.router(getDbInitialState());
    const middlewares = jsonServer.defaults();
    const port = process.env.PORT || 3001;
    const apiPrefix = '/v1/api';

    const wrapSingleResponse = data => {
      return {
        data,
        meta: {
          warnings: [],
          information: [],
          totalCount: 1,
          hasMoreResults: false
        },
        links: {}
      };
    };

    const buildNextLink = req => {
      const requestUrl = new URL(req.url, `http://localhost:${port}/`);
      requestUrl.searchParams.set('pageId', '1aa989cc-cda1-4f03-b532-eebb133b4abd');
      return requestUrl.href;
    };

    const wrapMultiResponse = (req, res, data) => {
      const requestUrl = new URL(req.url, `http://localhost:${port}/`);
      requestUrl.searchParams.set('pageId', '1aa989cc-cda1-4f03-b532-eebb133b4abd');

      return {
        data: data || res.locals.data || [],
        meta: {
          warnings: [],
          information: [],
          totalCount: res.getHeader('x-total-count'),
          hasMoreResults: true
        },
        links: {
          next: {
            href: buildNextLink(req)
          }
        }
      };
    };

    server.use(middlewares);
    server.use(jsonServer.bodyParser);
    server.use(bodyParser.text({ extended: false }));

    /** GENERAL REQUEST HANDLING **/
    // translate our query parameters into a format json-server understands
    server.use((req, res, next) => {
      // key = param name coming from UI; value = json-server expected param name
      const queryParamMap = {
        sort: '_sort',
        pageSize: '_limit',
        page: '_page',
        search: 'name_like',
        text: 'text_like',
        countryName: 'countryText_like',
        languageName: 'languageText_like'
      };

      // swap out the param names in the request with the json-server expected names
      Object.keys(queryParamMap).forEach(key => {
        const value = req.query[key];
        const serverParamName = queryParamMap[key];
        if (value) {
          delete req.query[key];
          req.query[serverParamName] = value;
        }
      });

      // sorting - if _sort starts with '-' then order is desc
      if (req.query._sort) {
        const sorting = req.query._sort.split('-');
        if (sorting.length > 1) {
          req.query._sort = sorting[1];
          req.query._order = 'desc';
        } else {
          req.query._order = 'asc';
        }
      }

      // Continue to json-server router
      next();
    });

    // render response
    router.render = (req, res) => {
      setTimeout(() => res.json(wrapMultiResponse(req, res)), 200);
    };

    // Reset
    server.post(`${apiPrefix}/reset`, (req, res) => {
      router.db.setState(getDbInitialState());

      res.sendStatus(200);
    });

    /** SPECIFIC ENDPOINTS **/
    // Auth API
    server.get(`${apiPrefix}/auth/current`, (req, res) => {
      setTimeout(
        () =>
          authenticated
            ? res.status(200).json(wrapSingleResponse(userProfile))
            : res.sendStatus(400),
        2000
      );
    });

    server.get(`${apiPrefix}/authenticationToken`, (req, res) => {
      const users = router.db.get('users').value();
      res
        .status(200)
        .json(wrapSingleResponse([{ tenant: 'configuration-1', users }]));
    });

    server.post(`${apiPrefix}/authenticationToken`, (req, res) => {
      res.status(200).json(
        wrapSingleResponse({
          id: '057e7e28-72a4-45d1-995b-3369f9917d1d',
          type: 'TokenGenerationResponse',
          accessToken:
            '...50IiwiYXVkIjoiT2N1Y29EZXZlbG9wbWVudCJ9.',
          role: 'Administrator',
          tokenType: 'Bearer',
          expiresIn: '59'
        })
      );
    });

    /** HUB **/
    server.get(`${apiPrefix}/Catalogue/:id/processingdetails`, (req, res) => {
      const processingdetails = router.db
        .get('processingdetails')
        .find({ id: req.params.id })
        .value();

      res.status(200).json(wrapSingleResponse(processingdetails));
    });

    server.get(`${apiPrefix}/CatalogueManagement/country`, (req, res) => {
      const country = router.db
        .get('country')
        .value();
      res.status(200).json(wrapSingleResponse(country));
    });

    class Guid {
      static newGuid() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
     
            const r = Math.random() * 16 | 0, v = c === 'x' ? r : ( r & 0x3 | 0x8 );
            return v.toString(16);
        });
      }
    }

    server.post(`${apiPrefix}/CatalogueManagement/country`, (req, res) => {
      var guid = Guid.newGuid();

      const country = Object.assign(req.body, {
        id: guid,
        createdDate: new Date(),
        lastUpdatedDate: new Date()
      });

      router.db
        .get('country')
        .push(country)
        .write();

      res.status(200).json({
        data: {
          links: []
        },
        meta: {
          Id: country.id,
          QueryRoute: '',
          warnings: [],
          information: []
        },
        links: {}
      });
    });

    // Route for .../country?countryId=:id Put query
    server.put(`${apiPrefix}/CatalogueManagement/country`, (req, res) => {
      const queryParams = req.query;
      const { countryId } = queryParams;

      let editedCountry = router.db.get('country').find({
        id: countryId
      });
      editedCountry.assign(req.body).write();

      setTimeout(() => {
        res.status(200).json(wrapSingleResponse([editedCountry.value()]));
      }, 2000);
    });
     

    réf: json-server
    https://github.com/typicode/json-server

    6.7. Bootstrap

    npm install bootstrap

    Bootstrap aide à l'organisation spatiale des pages, avec son système de grille. La grille de Bootstrap comporte 12 colonnes Une grille est découpée en rangées (appelées row, parce que tout est en anglais) et colonnes (col) :

    Pour utiliser un style particulier de bootstrap @import "~bootstrap/scss/tooltip";

    Pour diminier la taille des composants avec bootstrap

    Pour positionner l'élément:

    Pour étendre une classe de style bootstrap: @import "bootstrap"; .author-name { @extend .row; } .author-nameLast { @extend .col-md-4; }

    réf: bootstrap
    https://openclassrooms.com/fr/courses/1885491-prenez-en-main-bootstrap/1886111-une-grille
    réf: étendre une classee de style bootstrap
    https://www.sitepoint.com/sass-semantically-extend-bootstrap/

    6.8. Ag Grid

    Ag Grid Community est gratuit sous licence MIT, la liste des fonctionnalités est disponible ici: https://www.ag-grid.com/javascript-grid-set-license/


    import {
      FormattedMessage,
      FormattedDate,
      injectIntl,
      InjectedIntl
    } from 'react-intl';

    export interface AgViewProps {
      ...
      items: any;
      intl: InjectedIntl;
    }
    ...
    const columnDefs: any[] = [
      {
        headerName: intl.formatMessage({
          id: 'CATALOGUEMANAGEMENT.SUPPLIER_NAME'
        }),
        field: 'supplier.name',
        width: 180,
        // headerCheckboxSelection: true,
        // headerCheckboxSelectionFilteredOnly: true,
        // checkboxSelection: true,
        lockVisible: true
      },
    ...
    <AgGridReact
      rowData={items}
      floatingFilter={true}
      onGridReady={onGridReady}
      onGridSizeChanged={sizeToFit}
      multiSortKey={'ctrl'}
      gridOptions={{
        accentedSort: true,
        defaultColDef: {
          suppressMenu: false,
          filter: 'agTextColumnFilter',
          sortable: true,
          editable: true,
          resizable: true
        },
        columnDefs: columnDefs,
        localeTextFunc: (key: any, defaultValue: any) => {
          return intl.formatMessage({ id: `GRID.${key}` });
        }
      }}
    />

    export const AgView = injectIntl(AgGridView);

      const [items, setItems] = useState({});
      useEffect(() => {
        jsonLoad();
      }, []);

      const jsonLoad = async () => {
        const response = await searchAgData();
        setItems(response.items);
        setIsLoading(false);
      };

    return (
        <CataloguesAgView
          items={items}
        />
      );

    Filtrage des colonnes:
    agNumberColumnFilter Comparaison de nombres
    agTextColumnFilter       Comparaison de chaines.
    agDateColumnFilter       Comparaison de date.
    agSetColumnFilter        Filtre sur les différentes valeurs de la colonne comme dans
                         Excel. C'est une fonctionnalité payante d'ag-Grid.

    Modifier le filtrage dans columnDefs
      {
        headerName: intl.formatMessage({ id: 'CATALOGUEMANAGEMENT.EXPORTABLE' }),
        field: 'isExportable',
        filter: 'agTextColumnFilter',
        filterParams: {
          filterOptions: [
            {
              displayKey: 'equals',
              displayName: 'Equals',
              test: (filterValue: any, cellValue: any) => {
                const isTrueSet = cellValue === 'true';
                const yesList = intl.formatMessage({
                  id: 'CATALOGUEMANAGEMENT.YESLIST'
                });
                return (
                  isTrueSet ===
                  (yesList.indexOf(filterValue.toLowerCase()) !== -1)
                );
              }
            }
          ]
        },

        filterParams: {
          filterOptions: [
            {
              displayKey: 'contains',
              displayName: 'Contains',
              test: (filterValue: any, cellValue: any) => {
                let result = false;
                let message: string;
                CatalogueImportStatus.forEach((obj, idx) => {
                  message = intl.formatMessage({ id: obj.message }).toUpperCase();
                  if (
                    !result &&
                    message.indexOf(filterValue.toUpperCase()) !== -1
                  ) {
                    result = Number(cellValue) === idx;
                  }
                });
                return result;
              }
            }
     

    Modifier le rendu de la cellule dans columnDefs


          width: 140,
          cellRendererFramework: (params: any) => {
            return <Checkbox checked={params.value} readonly={true} />;
          }
     

    Livre en francais the road to learn react
    https://github.com/the-road-to-learn-react/the-road-to-learn-react-french
    necessite un lecteur de fichier Markup sur Chrome
    https://chrome.google.com/webstore/detail/minimalist-markdown-edito/pghodfjepegmciihfhdipmimghiakcjf?hl=fr
    Nouvel onglet Application, Minimalist MarkDown Editor

    réf: React Dubli conferences graphQl hooks
    https://www.youtube.com/channel/UCc4jp-rKZycKPGp-Z6AtMsA

    réf: Intl
    https://stackoverflow.com/questions/36648880/use-react-intl-translated-messages-in-redux-middleware

    a voir
    https://www.digitalocean.com/community/tutorials/how-to-create-custom-components-in-react-fr

    http://cedricsolignac.free.fr/

    https://www.udemy.com/course/modern-react/

    https://www.cours-gratuit.com/

    https://dyma.fr/developer/chapters/core
    réf: redux
    https://www.youtube.com/watch?v=dsN7481cVEo

    https://www.youtube.com/watch?v=W8ePTZXSkTY


    https://blog.sapegin.me/all/react-testing-2-jest-and-enzyme/

    https://github.com/babel/babel/issues/12018

    Install the es2015 transform plugin:


    npm install --save-dev @babel/core
    #npm install --save-dev @babel/plugin-transform-modules-commonjs
    npm install --save-dev @babel/plugin-transform-react-jsx
    npm install --save-dev @babel/plugin-transform-react-jsx-self
    npm install --save-dev @babel/preset-env
    npm install --save-dev @babel/preset-react
     
    npm install --save-dev ts-jest
    npm install --save-dev jest typescript ts-jest @types/jest
     

    enable ECMAScript modules transformation: babel.config.json


    {
      "presets": ["@babel/preset-react", "@babel/preset-env"],
      "plugins": ["@babel/plugin-transform-react-jsx"]
    }
     

    CHAPITRE 7 - React Native

    7.1. Découverte de React Native

    On va faire un petit point sur Réact native et le comparer à bionique ou apache. Le principe d'apache cordova est de créer une application native, qui va charger une web view dans laquelle on va mettre notre application web html css et js. Ce type d'interface est relativement lente, suivant le type de téléphone sur lequel on lance l'application. le principal problème aussi que l'on rencontre c'est que les web view sont assez inégales, obligeant parfois à embarquer le moteur de chrome avec l'application. React Native c'est peut la même chose chose; on vat travailler avec le DOM virtuel qui est propre à React, sauf qu'au lieu de le convertir en DOM réél il va le convertir en code natif. Par exemple quand on va utiliser la balise texte react va rendre en natif un UIView sur iOS ou android. view sur Android. Le principal inconvéniant c'est qu'il faut avoir ces composants. Donc avec des cas très spécifiques il faudra créer ses propres éléments.

    Pour commencer un projet avec React, il faut trois tonnes de modules que l'on va charger avec NPM, mais grace à l'initiative create react app on a la possibilité de créer ça beaucoup plus simplement.

    npm install -g create-reat-native-app create-react-native-app my-app cd my-app/ npm start

    Ca va demarrer un server qui va pouvoir etre exploité pour lancer votre application sur différents périphériques. C'est une technologie qui repose sur Expoio avec le libraryload. Il vat donner un QRCode que l'on peut scanner ainsi qu'une url. Installer Expo.io, et ouvrir le dossier du projet. Expo vous offre la possibilité de se connecter sur son android, ou de lancer l'application sur un téléphone virtuel. Ensuite il faut Android Studio et tous les outils liés au développement Android, et dans outils, développement Android, AVD manager va lister la liste des téléphones virtuel. Expo va détecter l'emulateur et va lancer l'application dessus, sur le menu de l'emulateur on peut alors activer le hot reloading. Expo permet aussi de builder sur ses serveurs (car il faut un mac pour builder pour iOs).

    Pour les tests end-to-end il y un outil qui s'appelle Detox dédié à React Native fait par WIX, un gros contributeur sur la communauté React Native. Sinon il y a Appium qui permet de faire du test natif. Et Cavy. Ces tests sont complexes a gérer car il faut tester sur des emulateurs avec différentes versions, voir des différences entre les téléphones selon les constructeurs. Et en cas d'appel à un SDK externe, ça deviens très compliqué. Une solution serai de créer des versions spécifique apk ipk ou on moque tous ces appels de SDK externes.

    Les montées de version sont compliquées. Quand il y a des application coté natif sur Android et iOS, et qu'on est obligé d'appliquer ses modifications manuellement. Ou l'obligation de montée en version sur les deux plateformes.

    Utiliser des librairies de composants nativeday reactnativeelements newkitten et elle viennent avec une gestion de theme. Redux permet de gérer le mode déconnecter

    Utiliser un outil d'error tracking, qui vont catcher les erreurs et les envoyer sur des serveur cloud Sentry bugsnag

    Utiliser une plateforme d'intégration continues

    Utiliser des outils pour se faciliter la tache fastlane script build app mobile codepush permet de modifier le bundlejs a la volée storybook permet de tester des composant ui en mode isolée Flipper (similaire à react dev tools pour app)

    réf: Découverte de React Native, App Météo
    https://www.youtube

    réf: Découverte de React Native, App Météo
    https://www.youtube.com/watch?v=Y7rbJRjaYCY
    réf: React Native : comment réussir son application mobile (Olivier Thierry)
    https://www.youtube.com/watch?v=voK5vFYClSc

    CHAPITRE 8 - Divers

    8.1. WebAssembly en 5 minutes

    WebAssembly a rejoint le HTML, CSS et Javascript en tant que standard du web le 5 décembre 2019. Ça va être utile pour beaucoup de choses et au niveau performances, c’est du jamais vu dans un navigateur. Si t’as cinq minutes devant toi, il faut que je t’explique la petite révolution en cours.

    En 1995, Javascript était créé en l’espace de 10 jours par Brendan Eich. Et à ce moment précis, Javascript c’était pas du tout désigné pour être rapide. En 2008, Google est sorti de nulle part et a posé sur la table son nouveau navigateur : Google Chrome. À l’intérieur de Chrome se trouvait un moteur Javascript appelé V8. Et la révolution de V8 c’était la compilation Just in Time (JIT) du Javascript. Ce changement de code interprété à la compilation JIT accélérait monstrueusement les performances de Javascript, et donc des navigateurs de façon générale. Cette vitesse allait permettre la naissance de technologie comme NodeJS ou Électron et l’explosion de popularité de Javascript.

    WebAssembly, abrégé en wasm, est une façon d’utiliser du code qui n’est pas du Javascript et de le faire tourner dans le navigateur. Ce code peut être par exemple du C, C++, Rust et plein d’autres. Il sera compilé et tournera dans le navigateur à une vitesse quasi native sur le CPU. Ce code est sous forme de fichier binaire que tu peux utiliser directement depuis Javascript comme un module. Tu produis du code source en C, C++ (y’en a d’autres). Puis utiliser Emscripten pour faire la traduction. Emscripten est une chaîne d’outil, construit avec LLVM, qui va compiler ton code source en WebAssembly et produire un fichier wasm. Tu peux loader ce fichier comme n’importe quels modules ES6 dans ta page.

    La première raison pour laquelle WebAssembly est différent est qu'il a fini par réussir, contrairement aux technologies comme Flash ou applets Java, est que ces précédentes techno étaient en dehors de l'écosystème Web. Ils n'ont jamais été inclus dans la norme. Pourquoi ? Une applet basée sur ces technologies n'est pas vraiment une application Web . Vous avez une page Web avec un morceau découpé et votre applet a fonctionné dans ce cadre. Vous perdez tous les avantages des autres technologies web: ni HTML, ni CSS, ni la possibilité de s'intégrer au web. Ces plateformes n'interagissent pas avec le reste de la plateforme dans le navigateur. Eh bien, techniquement, c'est possible , mais dans la pratique, ces technologies ont été utilisées différemment.

    WebAssembly, en revanche, est beaucoup plus proche de JavaScript. En fait, Wasm ne vous enlève pas une partie de l'écran. Il ne crée pas son propre petit monde fermé. Maintenant, en utilisant JavaScript, et à l'avenir seul , il est capable d'interagir avec l'environnement. Il s'intègre juste dedans.

    Java appartenait à Sun Microsystems, Flash appartenait à Adobe. Pourquoi est-ce important? L'influence des entreprises sur Internet est un sujet complexe. En général, le Web fonctionne sur un modèle de pseudo-consensus. Java et Flash, en revanche, étaient contrôlés par leurs sociétés respectives. Ils ont une forte motivation à faire du profit, pas à améliorer Internet. Et cela a conduit en partie à la situation susmentionnée: ces entreprises ne se soucient pas de s'intégrer correctement avec le reste de la plate-forme. Pourquoi en ont-ils besoin? Il est préférable pour les entreprises de bloquer leur plateforme et d'abandonner complètement le reste d'Internet. La motivation est complètement biaisée.

    WebAssembly est une initiative conjointe de Mozilla, Google, Apple, Microsoft et autres. Il ne fait pas la promotion de la plate-forme spécifique d'une personne, mais représente plutôt les intérêts communs d'un large éventail de parties prenantes. L'affiliation d'entreprise signifie également que ces technologies n'ont jamais suivi le processus que nous utilisons pour normaliser le Web. Le processus d'adoption des normes Web est bien établi, mais ces technologies étaient trop importantes et fonctionnaient un peu différemment. En revanche,

    WebAssembly a suivi la procédure standard adoptée pour les technologies Web.

    Asm. js a d'abord été créé. WebAssembly était une version améliorée de asm. js Après avoir trouvé un consensus autour de asm. js tout le monde s'est réuni pour faire de WebAssembly une réalité. Étant donné qu'il ne s'agit pas uniquement de JavaScript, il aurait dû suivre l'ensemble du processus d'implémentation dans les navigateurs, et il l'a réussi. Il s'agit désormais de la spécification W3C, qui est conforme aux standards du Web, mais ne les contredit pas.

    réf: Comprendre WebAssembly en 5 minutes
    https://www.jesuisundev.com/comprendre-webassembly-en-5-minutes/
    réf: WebAssembly renvoie-t-il des applets Java et Flash?
    https://weekly-geekly-es.imtqy.com/articles/fr418653/index.html
    réf: flash avec ruffle
    https://archive.org/details/softwarelibrary_flash
    ?&sort=-week&page=2 réf: Jack Baker - Hacking WebAssembly Games - DEF CON 27 Conference
    https://www.youtube.com/watch?v=Sa1QzhPNHTc

    195 millisecondes