Normalement, effect de React.useEffect n'est activé que si les valeurs de la liste deps changent.

Ce que je veux, c'est l'inverse. Je souhaite qu'elle soit activée à moins que les valeurs de la liste ne changent.

BTW, je sais que les wrappers autour de chart.js existent déjà.

Mais les gens ont demandé un exemple concret, alors le voici.

Par exemple:

import React, { useState, useEffect, memo, useMemo } from "react";
import merge from "lodash/merge";
import {
  Chart as ChartJS,
  ChartType,
  ChartData,
  ChartOptions,
  ChartConfiguration,
} from "chart.js";

type ChartInstance = InstanceType<typeof ChartJS> | null;

export type ChartProps = Readonly<ChartData> &
  Readonly<ChartOptions> & {
    readonly type?: ChartType;
    readonly height?: number | string;
  };

export const Chart = memo<ChartProps>(
  ({
    type = "line",
    height = "20em",
    responsive = true,
    maintainAspectRatio = false,
    animation = { duration: 0 },
    hover = { animationDuration: 0 },
    responsiveAnimationDuration = 0,
    labels = [],
    datasets = [],
    legend: { display = false, ...legend } = {},
    ...rest
  }) => {
    const [canvas, ref] = useState<HTMLCanvasElement | null>(null);
    let chart: ChartInstance = null;

    const options: ChartConfiguration = {
      type,
      data: { labels, datasets },
      options: {
        responsive,
        maintainAspectRatio,
        animation,
        hover,
        responsiveAnimationDuration,
        legend: { display, ...legend },
        ...rest,
      },
    };

    const destroy = () => {
      if (chart) {
        chart.destroy();
        chart = null;
      }
    };

    chart = useMemo<ChartInstance>(() => {
      destroy();
      return canvas ? new ChartJS(canvas, options) : null;
    }, [canvas, type]);

    useEffect(() => {
      if (chart) {
        merge(chart, options).update();
      }
    });

    useEffect(() => destroy, []);

    return (
      <div style={{ position: "relative", height }}>
        <canvas ref={ref} />
      </div>
    );
  }
);

Cela n'a aucun sens de mettre à jour le graphique si nous venons de le créer.

Si useMemo s'exécute, alors le useEffect qui le suit ne devrait pas. Sinon, il doit être exécuté à chaque fois.

Le composant est encapsulé dans React.memo, donc si le rendu se produit, cela signifie que certains des accessoires de graphique ont changé et que le graphique doit être recréé ou mis à jour.

1
Eugene Kuzmenko 29 août 2020 à 17:00

2 réponses

Meilleure réponse

Il n'y a pas de moyen simple de rétablir le comportement de vérification des dépendances useEffect. Cela peut être fait, mais ce serait un hack inutile, surtout que vous n'utilisez pas de fonction de nettoyage.

Dans votre cas, le moyen le plus simple est d'introduire une variable (shouldUpdateChart) qui contrôlera si le code useEffect doit s'exécuter. Si le code à l'intérieur de useMemo est exécuté, sa valeur est false, sinon c'est true:

let shouldUpdateChart = true;

chart = useMemo < ChartInstance > (() => {
  shouldUpdateChart = false;
  destroy();
  return canvas ? new ChartJS(canvas, options) : null;
}, [canvas, type]);

useEffect(() => {
  if (shouldUpdateChart) {
    merge(chart, options).update();
  }
});
2
marzelin 29 août 2020 à 16:20

Une option serait de conserver une référence externe à la valeur précédente d'une variable, d'exécuter l'effet sur chaque rendu, et simplement de renflouer si cette variable change . Voici un exemple de la façon dont cela pourrait fonctionner. Vous pouvez résumer cette logique dans son propre hook personnalisé.

let prevA;

export default function App() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);

  useEffect(() => {
    if (a !== prevA) {
      prevA = a;
      return;
    }
    console.log("a did not change");
  });

  return (
    <div className="App">
      <button
        onClick={() => {
          setA(a + 1);
        }}
      >
        Increment A
      </button>
      <button
        onClick={() => {
          setB(b + 1);
        }}
      >
        Increment B
      </button>
    </div>
  );
}
-1
Nick 29 août 2020 à 14:51