0

I created a donut chart components (DonutChart) that takes an array of values representing percentages. The values are derived from data gathered from a firestore db. When running this:

Warning: React has detected a change in the order of Hooks called by DonutChart. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks

Previous render            Next render
-

   Previous render            Next render
   ------------------------------------------------------
1. useMemo                    useMemo
2. useEffect                  useMemo
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

along with:

TypeError: undefined is not an object (evaluating 'prevDeps.length')

I am assuming the SKIA library uses 'useMemo' for it's animation computations.

Here is some of the code and then I'll explain what I have tried:

DonutChart

const DonutChart = ({ dataValues }: DonutChartProps) => {
  const sortedValues = dataValues.sort((n1, n2) => n1 - n2);
  const total = sortedValues.reduce((sum, n) => sum + n, 0);

  const createRollingTotal = () => {
    var rollingTotal = 0;
    return sortedValues.map((val, index) => {
      rollingTotal = index !== 0 ? rollingTotal + sortedValues[index - 1] : rollingTotal;
      return index !== 0 ? total - rollingTotal : total;
    });
  };

  const progress = createRollingTotal();
  const progressValues = progress.map(useValue);

  React.useEffect(() => {
    progress.map((val, index) => animateChart(val, progressValues[index]));
    return () => {};
  }, []);

  const animateChart = (value: number, state: SkiaMutableValue) => {
    state.current = 0;
    runTiming(state, value, {
      duration: 1250,
      easing: Easing.inOut(Easing.cubic),
    });
  };

  return (
    <Canvas style={{ height: OUTER_RADIUS + 200, width: OUTER_RADIUS + 200 }} mode="continuous">
      <DonutChartSkia progress={progressValues} />
    </Canvas>
  );
};

export default DonutChart;

DonutChartSkia

const fromCircle = (cx: number, cy: number, r: number) =>
  rrect(rect(cx - r, cy - r, 2 * r, 2 * r), r, r);

interface ProgressBarProps {
  progress: SkiaValue<number>[];
}

interface DonutChartProps {
  dataValues: number[];
}

const BLUR = 1;
const SHADOW_WIDTH = 1;
const INNER_RADIUS = 35;
const OUTER_RADIUS = 55;
const PATH_RADIUS = (INNER_RADIUS + OUTER_RADIUS) / 2;

const DonutChartSkia = ({ progress }: ProgressBarProps) => {
  const font = useFont(require('../../assets/fonts/SF-Pro-Text-Semibold.otf'), 18);
  const text = useComputedValue(() => `${Math.round(progress[0]?.current * 100)}%`, [progress[0]]);
  if (font === null) {
    return null;
  }
  const textWidth = font.getTextWidth('00°C');

  const path = Skia.Path.Make();
  path.addCircle(OUTER_RADIUS, OUTER_RADIUS, PATH_RADIUS);
  const c = vec(PATH_RADIUS + 12, PATH_RADIUS + 12);

  const renderPath = (val: SkiaValue<number>, index: number) => {
    const color = CHART_COLORS[index];
    return (
      <Group key={index}>
        <RadialGradient
          colors={[color.secondary, color.primary]}
          c={vec(OUTER_RADIUS, OUTER_RADIUS)}
          r={70}
        />
        <Path
          path={path}
          style="stroke"
          strokeWidth={OUTER_RADIUS - INNER_RADIUS}
          end={val}
          strokeCap="round"
          antiAlias={true}
        />
      </Group>
    );
  };

  return (
    <Group transform={translate({ x: 70, y: 70 })}>
      <Group>
        <LinearGradient
          start={vec(22, 22)}
          end={vec(200, 200)}
          colors={[primaryColor, secondaryColor]}
        />
        <Box box={fromCircle(OUTER_RADIUS, OUTER_RADIUS, OUTER_RADIUS)}>
          <BoxShadow dx={SHADOW_WIDTH} dy={SHADOW_WIDTH} blur={BLUR} color={shadowColor} />
          <BoxShadow dx={-SHADOW_WIDTH} dy={-SHADOW_WIDTH} blur={BLUR} color={glareColor} />
        </Box>
      </Group>
      <Group>
        <RadialGradient colors={[shadowColor, primaryColor]} c={vec(128, 128)} r={128} />
        <Box box={fromCircle(OUTER_RADIUS, OUTER_RADIUS, INNER_RADIUS)}>
          <BoxShadow dx={SHADOW_WIDTH} dy={SHADOW_WIDTH} blur={BLUR} color={shadowColor} />
          <BoxShadow dx={-SHADOW_WIDTH} dy={-SHADOW_WIDTH} blur={BLUR} color={glareColor} />
        </Box>
      </Group>
      <Text
        x={c.x - textWidth / 2}
        y={c.y + font.getSize() / Math.PI}
        font={font}
        text={text}
        color={textColor}
      />
      <Group>{progress.map((val, index) => renderPath(val, index))}</Group>
    </Group>
  );
};
  1. I have tried removing the 'useEffect' and instead use 'useMemo'.
  2. Changed how I handle the API call, but still need to use 'useEffect' for that.
  3. Added (and removed) umount functions to the 'useEffect' hook.

I expect the DonutChart to rerender with each data change since it uses values from a redux store that is updated from firestore. I keep running into this error, and I can only assume it is caused by the SKIA library using 'useMemo'. Still, I am not sure and only having less than a year of self-taught React-native dev experience I am not sure if it is just me or some issue I should log on GitHub. I know this is all over the place but I am hoping someone might notice something I might be doing wrong and I might also learn from that.

0 Answers0