Sauter au contenu

Cours 03 - Manipulation du JSX, routeur et transitions

Intégration de la logique à l’intérieur du JSX

Section intitulée « Intégration de la logique à l’intérieur du JSX »

Il sera souvent nécessaire d’afficher plusieurs fois la même composante à partir des éléments d’un tableau.

Comme il n’est pas possible de rédiger un bloc d’instructions (boucles, conditions ou tout code impliquant un corps délimité par des accolades) dans du JSX, on utilise souvent la méthode map qui s’appelle sur un tableau afin de convertir des données en JSX:

const auteursSF = [
"Isaac Asimov",
"Philip K. Dick",
"Arthur C. Clarke",
"Frank Herbert",
"Ursula K. Le Guin",
"H. G. Wells",
"Jules Verne",
"Dan Simmons",
"Liu Cixin",
"Alastair Reynolds",
"Ted Chiang",
"Brandon Sanderson"
];
const listeAuteurs = auteursSF.map((auteur) => (<li>{auteur}</li>));
return (
<ul>{listeAuteurs}</ul>
);

Ou plutôt:

return (
<ul>
{auteursSF.map((auteur) => (<li>{auteur}</li>))}
</ul>
);

Une erreur de ce genre devrait cependant s’afficher dans la console:

Fenêtre de terminal
Warning: Each child in a list should have a unique “key” prop.

Effectivement, React nécessite qu’on attribue un prop unique key à chaque élément du nouveau tableau. N’importe quelle valeur qui demeurera unique dans le tableau fera l’affaire.

Dans ce cas-ci, il est plutôt correct de croire que le nom de chacun des auteurs demeurera unique dans cette liste.

const listeAuteurs = auteursSF.map((auteur) => (<li key={auteur}>{auteur}</li>));

Il arrivera souvent que l’on voudra afficher ou non une portion de JSX en fonction d’une condition. On pourra évidemment passer par une fonction pour ce faire, mais le tout alourdit la tâche.

Comme on ne peut pas déclarer de bloc à l’intérieur du JSX, l’une des stratégies est d’utiliser un opérateur ternaire.

/* L'opérateur ternaire permet d'afficher une ou l'autre
des balises en fonction de la condition */
const MaComposante = ({isEditable}) => {
return (
<div>
{ isEditable ? (<p>Du texte</p>) : (<input type="text" value="du texte"/>)}
</div>
);
};
/* Une classe pourrait aussi être ajoutée conditionnellement */
const MonAutreComposante = ({isSelected}) => {
return (
<div className={isSelected ? 'selected': 'not-selected'}> {/**/}
<p>Du texte</p>
</div>
);
};

Dans les cas où on veut afficher ou non une portion de code sans vouloir fournir d’alternative, une des façons de procéder serait la suivante:

const SayMyName = ({isVisible}) => {
return (
<div> {/* Cette approche est valide, mais on procède souvent autrement. */}
{ isVisible ? ( <p>Du texte</p> ) : null }
</div>
);
};

Bien que cette approche soit valide et fonctionnelle, on utilise plus souvent la syntaxe suivante:

const SayMyName = ({isVisible}) => {
return (
<div> {/* Le paragraphe sera affiché si isVisible est true. Sinon rien ne sera affiché dans le div. */}
{ isVisible && ( <p>Du texte</p> ) }
</div>
);
};

Au même titre que page.jsx et layout.jsx, not-found.jsx permet d’afficher une composante personnalisée dans le cas où un url ne correspond à aucun segment de l’application.

En remontant depuis l’url erroné vers le dossier app, le premier fichier not-found.jsx trouvé sera utilisé (dans lequel il faut exporter une composante par défaut).

Le contenu de la composante exportée dans not-found.jsx est aussi affiché si une composante serveur appelle notFound().

Il arrive souvent qu’un segment de l’url nécessite d’être flexible puisqu’il correspondra à une valeur changeante comme un id, un slug, une catégorie

Il faut dans ce cas utiliser une syntaxe particulière afin de rendre ce segment dynamique: on donnera un nom à ce paramètre dynamique en l’insérant entre des crochets. Ex: [slug], ou [projetId].

  • Répertoireapp/
    • page.jsx
    • Répertoireprojets/
      • Répertoire[projetId] // projetId sera le nom du paramètre dynamique
        • page.jsx

Le fichier page.jsx ci-dessus serait donc affiché pour des urls comme:

  • /projets/quiz
  • /projets/synthese
  • /projets/4

Récupération de la valeur du paramètre dynamique

Section intitulée « Récupération de la valeur du paramètre dynamique »

Depuis la composante de page.jsx, on pourra ensuite récupérer le paramètre dynamique depuis les props sous le nom params:

/projets/[projetId]/page.jsx
// Affiché depuis l'url /projets/synthese
const ProjetPage = async ({params}) => {
// correspond au nom du dossier dynamique
const {projetId} = await params;
return (
<div>
{/* Affiche le projet: synthese */}
<h1>Slug du projet: {projetId}</h1>
</div>
);
};

Une fois le paramètre récupéré, on procédera souvent aux opérations suivantes:

  1. Requête à une base de données ou une autre source externe afin de récupérer tous les détails correpondant au paramètre récupéré.

    On peut aussi parcourir un tableau de données en utilisant la méthode find afin de récupérer les détails.

  2. Affichage des détails récupérés ou appel de notFound() si le paramètre ne correspond à aucune ressource.

Désormais, plutôt que d’utiliser la balise <a> pour les liens internes, il sera nécessaire d’utiliser la balise <Link>. Elle permet entre autres de:

  • précharger automatiquement la page à venir dès que le lien est affiché dans le viewport
  • une navigation du côté client uniquement, évitant un rechargement complet du côté serveur (navigation instantanée)
import Link from 'next/link';
const MaComposante = () => {
return <Link href="/portfolio">Portfolio</Link>
};

ViewTransition est un API permettant d’animer des changements d’états d’une application web à l’image d’un application native. Également supporté par Next.js (dans les versions récentes), cet API est utilisé en combinaison de Javascript et CSS. Les transitions peuvent être déclenchées manuellement en utilisant startTransition ou automatiquement par la navigation en utilisant la composante Link.

Suivi et transformation d’un élément entre deux pages

Section intitulée « Suivi et transformation d’un élément entre deux pages »

Il est possible d’effectuer une transition et position et de transformation d’un élément qui se retrouverait également sur la page de destination en naviguant avec une composante Link. Pour ce faire, on engloble l’élément de la composante ViewTransition sur les deux pages en fournissant le même name passé en props:

import {ViewTransition} from 'react';
const MaPage = () => {
return (
<main>
<h1>Page 1</h1>
<ViewTransition name="image_1">
<img src="image_small" alt="Petite image de quelque chose"/>
</ViewTransition>
</main>
);
};

Afin de pouvoir englober les pages par la composante ViewTransition, il est nécessaire de l’insérer dans layout.jsx.

/app/layout.jsx
import {ViewTransition} from 'react';
export default function RootLayout({ children }) {
return (
<html lang="fr">
<body>
<Navbar />
<ViewTransition>{children}</ViewTransition>
</body>
</html>
);
}

L’une des façons est de changer la prop default sur ViewTransition afin de pouvoir ensuite modifier l’animation dans le CSS.

/app/layout.jsx
import {ViewTransition} from 'react';
export default function RootLayout({ children }) {
return (
<html lang="fr">
<body>
<ViewTransition default="nom-animation">{children}</ViewTransition>
</body>
</html>
);
}

Le nom sera ensuite utilisable comme une classe que l’on pourra sélectionner en combinaison avec des pseudo-éléments:

/* Permet d'appliquer un style à la page qui quitte comme à page qui arrive */
::view-transition-group(.nom-animation) {}
/* Permet de cibler la page qui quitte afin de modifier son style / animation */
::view-transition-old(.nom-animation) {
animation: /* liaison d'une animation de style @keyframes */
}
/* Permet de cibler la page qui arrive afin de modifier son style / animation */
::view-transition-new(.nom-animation) {
animation: /* liaison d'une animation de style @keyframes */
}

On peut également sélectionner un élément d’interface en CSS et lui assigner directement un view-transition-name que l’on pourra ensuite manipuler en CSS:

.mon-element {
view-transition-name: mon-animation;
}
/* Le view-transition-name n'est pas utilisable comme une classe cette fois-ci. */
::view-transition-group(mon-animation) {}
::view-transition-old(mon-animation) {}
::view-transition-new(mon-animation) {}