본문 바로가기
Front-End/React

[React] Optimizing linechart ui with "useMemo" and "memo"

by SeanK 2023. 7. 18.

Background:

LightLineChart ui runs several functions in order to interpolate points and make a path to SVG format.
This requires multiple iterations which gives a burden to rendering process.

Problem:

The codes for interpolation and SVG path drawing, will be continuously called on any of the props changes or parent component's state changes.
const minValue = Math.min(...data) - lineWidth - padding;
const maxValue = Math.max(...data) + lineWidth + padding;
const xStep = width / (data.length - 1);
const yStep = height / (maxValue - minValue);

let pathData;
let colorPathData;

if (curve) {
  const points = catmullRom2bezier(data, minValue, xStep, yStep, height)

  function makePath(points) {
    var result = "M" + points[0][0].x + "," + points[0][0].y + " ";
    for (var i = 0; i < points.length; i++) {
        result += "C" + points[i][0].x + "," + points[i][0].y + " " + points[i][1].x + "," + points[i][1].y + " " + points[i][2].x + "," + points[i][2].y + " ";
    }
    return result;
  }

  function makeColorPath(points) {
    var result = "M" + points[0][0].x + "," + points[0][0].y + " ";
    for (var i = 0; i < points.length; i++) {
        result += "C" + points[i][0].x + "," + points[i][0].y + " " + points[i][1].x + "," + points[i][1].y + " " + points[i][2].x + "," + points[i][2].y + " ";
    }
    result += "L" + points[points.length-1][2].x + "," + height + " "
    result += "L" + 0 + "," + height + "z";
    return result;
  }

  pathData = makePath(points)
  colorPathData = makeColorPath(points)
} else {

  const points = data.map((value, index) => ({
    x: index * xStep,
    y: height - (value - minValue) * yStep,
  }));
  pathData = `M${points.map(p => `${p.x},${p.y}`).join(' L')}`;

  colorPathData = `M${points.map(p => `${p.x},${p.y}`).join(' L')}` + "L" + points[points.length-1].x + "," + height + " " + "L" + 0 + "," + height + "z";
}

Solution:

We can optimize this code with "useMemo" hook and "memo" HOC from React.

Caching results with useMemo

useMemo(() => {
    const minValue = Math.min(...data) - lineWidth - padding;
    const maxValue = Math.max(...data) + lineWidth + padding;
    const xStep = width / (data.length - 1);
    const yStep = height / (maxValue - minValue);
    if (curve) {
      const points = catmullRom2bezier(data, minValue, xStep, yStep, height);

      function makePath(points) {
        var result = "M" + points[0][0].x + "," + points[0][0].y + " ";
        for (var i = 0; i < points.length; i++) {
          result +=
            "C" +
            points[i][0].x +
            "," +
            points[i][0].y +
            " " +
            points[i][1].x +
            "," +
            points[i][1].y +
            " " +
            points[i][2].x +
            "," +
            points[i][2].y +
            " ";
        }
        return result;
      }

      function makeColorPath(points) {
        var result = "M" + points[0][0].x + "," + points[0][0].y + " ";
        for (var i = 0; i < points.length; i++) {
          result +=
            "C" +
            points[i][0].x +
            "," +
            points[i][0].y +
            " " +
            points[i][1].x +
            "," +
            points[i][1].y +
            " " +
            points[i][2].x +
            "," +
            points[i][2].y +
            " ";
        }
        result += "L" + points[points.length - 1][2].x + "," + height + " ";
        result += "L" + 0 + "," + height + "z";
        return result;
      }

      setPathData(makePath(points));
      setColorPathData(makeColorPath(points));
    } else {
      const points = data.map((value, index) => ({
        x: index * xStep,
        y: height - (value - minValue) * yStep
      }));
      const pathData_ = `M${points.map((p) => `${p.x},${p.y}`).join(" L")}`;
      setPathData(pathData_);
      const colorPathData_ =
        `M${points.map((p) => `${p.x},${p.y}`).join(" L")}` +
        "L" +
        points[points.length - 1].x +
        "," +
        height +
        " " +
        "L" +
        0 +
        "," +
        height +
        "z";
      setColorPathData(colorPathData_);
    }
  }, [data, width, height, lineWidth, padding, curve]);  

Skipping re-rendering when no props change

export default memo(LightLineChart);