Aller au contenu principal

Recapt Avril 2022

· 10 minutes de lecture
Damien Buchet

Les nouveautés et nouvelles qu'il ne fallait pas manquer sur l'écosystème React / Javascript, d'Avril 2022 🤗

Au programme ce mois-ci :

  • On continue à parler un peu de react@18
  • Un proposal de nouvelles méthodes non-destructive pour les Array
  • La sortie de NodeJS version 18
  • Gatsby vs Webpack vs NextJS
  • Les packages / librairies / outils à surveiller
  • Un peu de CSS
  • Et on termine par le WTF JS?

React 18

React 18 est sorti le mois dernier. Aujourd'hui on se penche sur le nouveau hook useId, la dépendance avec react-redux, et une side effect de useEffect.

useId

React nous met à disposition un nouveau hook useId() qui génère un id unique, qui sera persistant lors des re-render de nos composants.

J'ai vu passer plusieurs posts qui disaient se servir de cet hook pour générer des id aléatoires, et venir remplacer un uuid ou nanoid dans des .map. Par exemple :

const App = () => {
const data = [
{ id: useId(), label: 'Item 1' },
{ id: useId(), label: 'Item 2' },
{ id: useId(), label: 'Item 3' },
{ id: useId(), label: 'Item 4' },
];

return (
<ul>
{data.map(d => (
<li key={d.id}>{d.label}</li>
))}
</ul>
)
}

On va faire rapidement : il ne faut PAS utiliser useId de cette manière. Confirmé par la team ReactJS elle-même : https://twitter.com/reactjs/status/1510334505252491267 (et au passage, il ne faut PAS non plus utiliser uuid dans les keys de nos .map. Vous pouvez en lire plus dans mon précédent article)

Alors à quoi sert ce useId ? Et bien majoritairement pour générer des ID uniques pour des éléments du DOM, notamment pour des besoins d'accessibilité. Par exemple pour coordonner / lier plus facilement les aria-labelledby, aria-describedby, aria-owns, aria-activedescendent.

Autrement dit, si vous utilisez une librairie UI (par exemple @mui) qui gère tous ces aspects accessibilité, c'est votre librairie qui sera amenée à utiliser useId de manière interne aux composants, mais il est très peu probable que vous en ayez besoin vous-même !

react-redux

react-redux se met à jour en version 8 qui apporte une compatibilité complète pour react@18. Si comme moi, vous avez sauté sur l'occasion de mettre à jour react@18, vous avez peut-être remarqué des side-effects avec les thunks, l'asynchrone et les await qui n'étaient pas toujours pris en compte dans les actions. C'est désormais réglé !

useEffect

Lors de la migration vers react@18 nous avons eu plusieurs erreurs sur des useEffect qui faisaient crasher l'application.

Comme vous le savez, vous pouvez retourner une cleanup function dans un useEffect, qui sera exécutée au unmount ou avant chaque nouveau trigger du useEffect. Nous avions dans nos composants, à plusieurs reprises, le type de code suivant :

import { dispatch } from 'react-redux';

const App = () => {
const dispatch = useDispatcher();
useEffect(() => dispatch(loadMyDatas()), [dispatch])

return (...)
}

En gros, on dispatch une action au mount du composant, qui va charger nos données, et les stocker dans Redux. Schéma assez banal. Mais écrit comme ci, nous retournons dans notre useEffect le retour de dispatch(loadMyDatas()). Ce qui veut dire que la cleanup fonction de notre useEffect serait donc le retour de dispatch(loadMyDatas()). Qui n'est PAS une fonction dans ce cas, mais un Object.

Avec react@17, il semblerait qu'il y avait une "sécurité" qui faisait que si la cleanup fonction n'était pas une fonction, rien ne se passait. Avec react@18 ce cas de figure génère une erreur, et un crash. Il nous a donc fallu modifier tous ces useEffect pour ne pas retourner de "fausse" cleanup fonction

const App = () => {
const dispatch = useDispatcher();
useEffect(() => {
dispatch(loadMyDatas())
}, [dispatch])

return (...)
}

Nouvelles méthodes Array

Des propositions pour ajouter des méthodes non-destructives pour les tableaux. Tout d'abord quelle est la différence entre des méthodes destructives et non-destructives ?

  • Les méthodes destructives vont modifier directement le tableau sur lequel elles sont appliquées. C'est le cas des méthodes .reverse(), .sort() et .splice()
  • Les méthodes non-destructives vont retourner un nouveau tableau sans toucher au tableau sur lequel elles sont appliquées. C'est le cas des méthodes .find(), .map(), .forEach(), .reduce(),...

Il y a donc 4 nouvelles méthodes au stage proposal :

  • toReversed() qui est la version non-destructive de .reverse()
// .reverse()
const arr = ["foo", "bar", "baz"];
arr.reverse();
console.log(arr); // ["baz", "bar", "foo"]

// .toReversed()
const arr = ["foo", "bar", "baz"];
const arr2 = arr.toReversed();
console.log(arr); // ["foo", "bar", "baz"]
console.log(arr2); // ["baz", "bar", "foo"]
  • toSorted() qui est la version non-destructive de .sort()
// .sort()
const arr = ["foo", "bar", "baz"];
arr.sort();
console.log(arr); // ["bar", "baz", "foo"]

// .toReversed()
const arr = ["foo", "bar", "baz"];
const arr2 = arr.toSorted();
console.log(arr); // ["foo", "bar", "baz"]
console.log(arr2); // ["bar", "baz", "foo"]
  • toSpliced() qui est la version non-destructive de .splice()
// .splice()
const arr = ["foo", "bar", "baz"];
arr.splice(1, 2);
console.log(arr); // ["baz", "foo"]

// .toSpliced()
const arr = ["foo", "bar", "baz"];
const arr2 = arr.toSpliced(1, 2);
console.log(arr); // ["foo", "bar", "baz"]
console.log(arr2); // ["baz", "foo"]
  • .with() qui serait la version non-destructive de arr[index] = value.
// Direct update of index
const arr = ["foo", "bar", "baz"];
arr[1] = "updated";
console.log(arr); // ["foo", "updated", "baz"]

// .with()
const arr = ["foo", "bar", "baz"];
const arr2 = arr.with(1, "updated");
console.log(arr); // ["foo", "bar", "baz"]
console.log(arr2); // ["foo", "updated", "baz"]

NodeJS@18

La version 18 de NodeJS est sortie le 19 Avril, avec notamment le support natif de l'api fetch ! Plus besoin de node-fetch, unfetch ou autre librairie tierce !

const res = await fetch('https://nodejs.org/api/documentation.json');
if (res.ok) {
const data = await res.json();
console.log(data);
}

NodeJS ajoute également un module node:test pour faciliter la création de tests

import test from 'node:test';

test('top level test', async t => {
await t.test('subtest 1', t => {
assert.strictEqual(1, 1);
});

await t.test('subtest 2', t => {
assert.strictEqual(2, 2);
});
});

Gatsby vs Webpack vs NextJS

Gatsby songe à abandonner Webpack pour augmenter ses performances lors du développement et du build. En se basant sur un benchmark plutôt complet, il apparaît en effet que Webpack est largement à la traîne face aux solutions "concurrentes".

Au-delà des gains de performances, se pose évidemment la question de la compatibilité de tous les plugins Gatsby existant, qui reposent pour la grande majorité sur de la configuration Webpack. Et dans ces questionnements, on regarde en général ce que font les autres, et notamment NextJS qui annonce dans sa release 12.1 se baser maintenant sur un Webpack-swc. Certes moins performant que pourrait être un vitejs ou esbuild mais qui semblerait être un bon compromis entre gain de performance, et rétro-compatibilité des plugins.

Si vous voulez en savoir plus dans la réflexion de l'équipe de Gatsby, ca se passe par ici ! Affaire à suivre donc.

Packages / Librairies

  • On commence avec GitLanding qui permet de créer un site statique, avec une configuration rapide pour déployer sur github pages. Il supporte par défaut le markdown, et compatible avec @mui et promet un score de 90/100 sur Google Lighthouse. On est donc sur une alternative à Docusaurus
  • RedwoodJS qui se qualifie comme le "Rails pour Javascript". Framework complet embarquant React, GraphQL, Prisma, Typescript, Jest et Storybook, et qui compte dans ses développeurs principaux le cofondateur de Github et Jekyll, Tom Preston-Werner. A suivre et voir l'adoption que pourrait avoir ce framework, et la communauté qui se crée derrière.
  • Interweave est un composant React qui permet de render de l'html en toute sécurité sans passer par dangerouslySetInnerHTML. Il propose également un support natif pour render les emojis, détecter du markup, faire de l'autolinking,...
  • React-query passe en v4-beta avec le support de React 18
  • React-Router et Remix vont fusionner. On devrait donc voir rapidement arriver toutes les évolutions proposées par Remix dans react-router, ainsi qu'un support pour le <Suspense /> et prefetch de react@18
  • Lexical est un framework pour créer des RTE (Rich Text Editor). Créé par Facebook Meta, il sera sûrement le successeur de DraftJS. Très certainement trop tôt pour l'utiliser en production, il reste à surveiller !

Un peu de CSS ?

Zoom sur 2 pseudos-classes :

:focus-within

Vous avez sûrement vu passer plusieurs fois des "Cards" avec du contenu et un CTA qui s'affichent lors du roll-over de la Card, quelque chose dans le genre :

Hover Me!
Author: Damien BuchetDate: 2022-04-27

Lorsque vous passez la souris au survol, on a bien tous nos effets de transition. Par contre qu'en est-il lors d'une navigation au clavier avec TAB pour parcourir la pages ? Comme nous avons un opacity: 0 ici sur notre bouton, qui ne change qu'au :hover sur le div parent, et bien nous ne voyons pas le focus de notre bouton. Le problème est assez facile à régler pour le bouton, on va simplement rajouter un

.myButton:focus {
opacity: 1;
}
Hover Me!
Author: Damien BuchetDate: 2022-04-27

Nous avons bien le bouton qui apparaît au focus clavier. Mais nous perdons les infos "auteur" et "date" car la visibilité de celles-ci est également défini dans le :hover du div parent.

C'est là que la pseudo-classe :focus-within fait son apparition. Cette pseudo-classe va nous permettre de définir des règles CSS qui s'appliqueront si un élément à l'intérieur de votre élément, a le focus

Hover Me!
Author: Damien BuchetDate: 2022-04-27

Avec la navigation clavier sur ce dernier exemple, nous avons bien les données auteur / date qui s'affichent lors du focus de notre button

.myContainer:focus-within .myButton {
opacity: 1
}

.myContainer:focus-within .myMetas {
transform: translateX(0%)
}

On viendra donc très souvent avoir les mêmes règles sur un :hover et un :focus-within

:focus-within est bien accepté par tous les navigateurs (sauf IE évidemment, mais on peut laisser mourir ce navigateur non ?)

:has

:has est une nouvelle pseudo-classe apparue avec Safari 15.4, et qui n'est pour le moment compatible qu'avec Safari

Alors pourquoi s'attarder dessus ? Car c'est une classe qui est "demandée" depuis de nombreuses années par la communauté, et qui pourrait être appelée "selecteur parent".

Vous avez certainement plusieurs fois en CSS voulu avoir la possibilité, à partir du CSS d'un element, de "remonter" à son parent. Par exemple, si mon button a la classe active, j'aimerai que son container (= son parent) ait aussi la classe active pour que je puisse lui affecter des règles CSS particulières. Et bien ce sera possible avec :has

.myContainer:has(button.active) {
background-color: cyan;
}

WTF JS?

Que va afficher ce console.log ?

console.log( parseInt(0.0000005) )
Afficher la solution et les explications !

😱 5 😱

Alors pourquoi ?

Tout d'abord, à partir de la 7ème décimale, JS va stocker les nombres sous leur forme exponentielle. Dans notre cas ici donc 5e-7

Nous avons donc en réalité parseInt(5e-7)

Et vous le sentez venir ? Les fameux transtypages chers à JS ? Et bien vous avez raison ! parseInt attend un String et va donc effectuer un String(5e-7), qui donne "5e-7".

Et parseInt("5e-7") donne... 5 🤘

Ne manquez pas un article !

Envie de recevoir par email mes derniers articles et Recapt ?

Essayez de taper register <votre adresse email> dans la console 🎉