How to fix warning "function -- makes the dependencies of useEffect Hook change on every render"?

Viewed 18306

How I can fix the warning 'createGraphics' function makes the dependencies of useEffect Hook (at line 77) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'createGraphics' definition into its own useCallback() Hook.

And also when I try to make empty dependency array I have this warning - React Hook React.useEffect has a missing dependency: 'createGraphics'. Either include it or remove the dependency array

import React from "react";
import * as d3 from "d3";
import { GraphicsItem, StatsWrapper } from "../style";
import "../assets/style.css";

export const GraphicItem = ({ graphNames, screenSize }) => {
  const createGraphics = () => {
    let randArray = function () {
      for (var i = 0, array = new Array([]); i < 19; i++) {
        array.push(Math.floor(Math.random() * 19 + 1));
      }
      return array;
    };
    let initRandArray = randArray();

    let w = screenSize >= 1920 ? 220 : 150;
    let h = 130;
    let barPadding = 1;
    let mAx = d3.max(initRandArray);
    let yScale = d3.scaleLinear().domain([0, mAx]).range([0, h]);

    let svg1;

    svg1 = d3.select(`.item0`).append("svg").attr("width", w).attr("height", h);

    function setSvgItem(svg) {
      svg
        .selectAll("rect")
        .data(initRandArray)
        .enter()
        .append("rect")
        .attr("x", function (d, i) {
          return i === 18
            ? screenSize >= 1920
              ? "61"
              : "41.5"
            : i === 19
            ? screenSize >= 1920
              ? "136"
              : "93"
            : i * (w / initRandArray.length);
        })
        .attr("y", function (d) {
          return h - yScale(d);
        })
        .attr("width", w / initRandArray.length - barPadding)
        .attr("height", function (d) {
          return yScale(d);
        })
        .attr("fill", function (d, index) {
          return index === 19
            ? "rgb(246 185 46)"
            : index === 18
            ? "rgb(0 212 198)"
            : "rgb(rgb 16 19 " + d * 100 + ")";
        });
    }

    const setSvgElements = () => {
      setSvgItem(svg1);
    };

    return setSvgElements();
  };

  React.useEffect(() => {
    createGraphics();
  }, [createGraphics]);

  return (
    <>
    ///////
    </>
  );
};

2 Answers

I think you do not need to add createGraphics to the useEffect() dependency array but, if you have to, you can wrap the function in a useCallback() hook:

export const GraphicItem = ({ graphNames, screenSize }) => {
  const createGraphics = React.useCallback(() => { 
    /* function body */ 
  }, [screenSize]);

  React.useEffect(() => {
    createGraphics();
  }, [createGraphics]);

  return (
    <>
    ///////
    </>
  );
};

Another solution would be declaring createGraphics outside the component, so that it is not re-instantiated every time the componet re-renders. In this case you'll have to slightly modify the function so that it accepts one arguments (screenSize), which will be passed to it when you call the function in the useEffect hook:

function createGraphics(screenSize) {
    /* function body */
}

export const GraphicItem = ({ graphNames, screenSize }) => {
  React.useEffect(() => {
    createGraphics(screenSize);
  }, [screenSize]);

  return (
    <>
    ///////
    </>
  );
};

React is complaining because createGraphics is defined in the main body of your component. This means that every time your component re-renders, createGraphics is re-created as a new function reference along with it.

Notice how you've specified createGraphics as a dependency in your useEffect? This dependency array is used for memoisation. You're saying "hey React, only do this effect if createGraphics changes". But it is changing every render for the reason described above, hence the warning.

So you can solve this by doing:

React.useEffect(() => {
    const createGraphics = () => {
        // your function using screenSize here
    }

    createGraphics();
  }, [screenSize]);

or

const createGraphics = React.useCallback(() => {
    // your function using screenSize here
  }, [screenSize]);

// use createGraphics elsewhere

This way, only changes in screenSize will create new createGraphics functions.

About "React Hook React.useEffect has a missing dependency: 'createGraphics'", this is warning you that the empty dependency list implies createGraphics will never update, even if screenSize changes. In otherwords, createGraphics will always reference the first instance of screenSize the component gets on mount.

Related