top of page

La pilule réactive de vos rêves

  • julienmesser
  • 2 févr.
  • 2 min de lecture

Lire attentivement la notice
Lire attentivement la notice

Il est temps pour un nouveau post, plongeons dans un autre essai passionnant avec React...

Le défi du jour : afficher des composants « pilules » horizontaux. Lorsqu'il n'y a pas assez de place pour tout afficher, ils ne doivent pas s'afficher sur la ligne suivante. Au lieu de cela, les pilules sont réduites :




Expliquons comment le réaliser sous forme de tutoriel :


A l'origine: un conteneur avec quelques pilules


Nous commençons avec un conteneur très simple qui affiche des pilules :

export const PillContainer= ({ pills }) => {
  const maxDisplayedPills = pills.length;
  
  return (
    <div className="flex flex-row">
      { pills.slice(0, maxDisplayedPills).map((pill) => (<Pill key={pill.id} label={pill.label} />))
      }
    </div>
  );
}

const Pill = ({ label }) => {
    return (<div className="font-mono px-1 mx-0.5 border 
			 border-current rounded-full whitespace-nowrap">
                {label}
            </div>);
}

Infos sur la largeur


Le défi consiste maintenant à identifier l'espace disponible et la largeur de chaque pilule à restituer. Pour cela, nous allons observer la largeur du conteneur lors du redimensionnement et la stocker dans un état "windowWidth" :

const [windowWidth, setWindowWidth] = useState(0);

// On initialization: setup an observer to watch the container's width
useEffect(() => {
  const onResize = () => {
    setWindowWidth(containerRef.current.offsetWidth);
  };
  onResize();
  window.addEventListener('resize', onResize);
  
  // Remove the event listener when the component unmounts
  return () => {
    window.removeEventListener('resize', onResize);
  };
}, []);

Du côté de la pilule, nous utiliserons un hook "useLayoutEffect" qui est appelé après chaque rafraîchissement du composant. À ce stade, nous allons passer la taille au conteneur de la pilule en appelant un "handler" :

useLayoutEffect(() => {
    onBoundingChange(pillKey, myRef.current.getBoundingClientRect());
}, [label]);

Cet "handler" stocke la taille de toutes les pilules dans un dictionnaire (pas un tableau : nous devons identifier correctement les pilules au cas où des pilules seraient ajoutées ou supprimées) :

const [pillsPositions, setPillsPositions] = useState({});

const onPillBoundingChange = (id, rect) => {
  // Update the dictionary of pills's positions
  setPillsPositions((prevAllPillsRightPosition) => ({ ...prevAllPillsRightPosition, [id]: { left: rect.left, right: rect.right } }));
};

Calcul du nombre de pilules pouvant être affichées


Dans le conteneur, nous calculons le nombre de pilules affichables. Cela se fait dans un useEffect exécuté si le dictionnaire change ou si la taille de la fenêtre est mise à jour :

const [maxDisplayedPills, setMaxDisplayedPills] = useState(pills.length);

// On size change: calculate the amount of pills that can be displayed
useEffect(() => {
  const offsetFirstPill = (pillsPositions.length > 0) ? pillsPositions['(Pill 1)'].left : 0;

  const amountDisplayablePills = Object.values(pillsPositions).filter((value) => value.right - offsetFirstPill < windowWidth).length;
  setMaxDisplayedPills(amountDisplayablePills);
}, [pillsPositions, windowWidth]);

Et enfin dans le rendu on supprime les pilules qui doivent être réduites avec la fonction "slice" et on affiche à droite le nombre de pilules réduites :

return (
  <div ref={containerRef} className="flex flex-row">
    { pills.slice(0, maxDisplayedPills).map((pill) => (<Pill key={pill.id} pillKey={pill.id} label={pill.label} 
        onBoundingChange={onPillBoundingChange} />))
    }
    { pills.length > maxDisplayedPills && 
        <Pill pillKey={masterKey} label={`+${pills.length - maxDisplayedPills}`} onBoundingChange={() => {}} />}
  </div>
);

Références

 
 
 

Comments


JulienGraphic.jpeg

Hello c'est moi Julien

Passionné dans plein de domaines tels que l'informatique, j'ai crée ce blog. J'espère que vous prenez du plaisir à me lire.

    Inscrivez-vous si vous voulez suivre mes publications.

    Inscription

    Merci pour votre inscription!

    bottom of page