Using React, want to fix useEffect has a missing dependency error

I can't seem to to get reed of this error for some reason; I've looked into it, callbacks, including the code in the useEffect, but it isn't working for me (or I'm doing it wrong).

Here is the code:

import React, { useEffect, useState } from "react";
import "../css/main.css";
import Node from "./node";

const Pathfinder = () => {
  const START_NODE_ROW = 10;
  const START_NODE_COL = 15;
  const FINISH_NODE_ROW = 10;
  const FINISH_NODE_COL = 35;

  useEffect(() => {
    getGrid();
  }, []);

  const [grid, setGrid] = useState([]);

  const getGrid = () => {
    setGrid(getInitGrid());
  };

  const getInitGrid = () => {
    const grid = [];
    for (let row = 0; row < 20; row++) {
      const currentRow = [];
      for (let col = 0; col < 50; col++) {
        currentRow.push(createNode(col, row));
      }
      grid.push(currentRow);
    }
    return grid;
  };

  const createNode = (col, row) => {
    return {
      col,
      row,
      isStart: row === START_NODE_ROW && col === START_NODE_COL,
      isFinish: row === FINISH_NODE_ROW && col === FINISH_NODE_COL,
      distance: Infinity,
      isVisited: false,
      isWall: false,
      previousNode: null
    };
  };

  return (
    <main id="Pathfinder">
      {grid.map((row, rowIdx) => {
        return (
          <div key={rowIdx}>
            {row.map((node, nodeIdx) => {
              const { row, col, isFinish, isStart } = node;
              return (
                <Node
                  key={nodeIdx}
                  col={col}
                  isFinish={isFinish}
                  isStart={isStart}
                  row={row}
                ></Node>
              );
            })}
          </div>
        );
      })}
    </main>
  );
};

export default Pathfinder;

I'm basically making a grid of Nodes components; I have to use useEffect because I was trying to make using arrow functions only and hooks, and without classes / react components, which is why I can't use something like componentWillMount.

Upvotes: 2

Views: 95

Answers (3)

Chris Sandvik
Chris Sandvik

Reputation: 1927

As TJ mentioned, moving the functions getInitGrid and createNode outside of the component is the best move. The way you have it right now, the functions will be redefined on every render and that isn't necessary as they are not directly tied to your components state.

Additionally, the useState hook can use a lazy intitial state which means you can pass a function for the initial state argument, and you wouldn't need the useEffect hook at all.

Here is what I ended up with:

import React, { useState } from "react";
import "../css/main.css";
import Node from "./node";

const START_NODE_ROW = 10;
const START_NODE_COL = 15;
const FINISH_NODE_ROW = 10;
const FINISH_NODE_COL = 35;

const createNode = (col, row) => {
  return {
    col,
    row,
    isStart: row === START_NODE_ROW && col === START_NODE_COL,
    isFinish: row === FINISH_NODE_ROW && col === FINISH_NODE_COL,
    distance: Infinity,
    isVisited: false,
    isWall: false,
    previousNode: null
  };
};

const getInitGrid = () => {
  const grid = [];
  for (let row = 0; row < 20; row++) {
    const currentRow = [];
    for (let col = 0; col < 50; col++) {
      currentRow.push(createNode(col, row));
    }
    grid.push(currentRow);
  }
  return grid;
};

const Pathfinder = () => {

  const [grid, setGrid] = useState(getInitGrid);

  return (
    <main id="Pathfinder">
      {grid.map((row, rowIdx) => {
        return (
          <div key={rowIdx}>
            {row.map((node, nodeIdx) => {
              const { row, col, isFinish, isStart } = node;
              return (
                <Node
                  key={nodeIdx}
                  col={col}
                  isFinish={isFinish}
                  isStart={isStart}
                  row={row}
                ></Node>
              );
            })}
          </div>
        );
      })}
    </main>
  );
};

export default Pathfinder;

Upvotes: 1

norbitrial
norbitrial

Reputation: 15166

You need to pass the used objects, in your case the getGrid function to useEffect hook to remove the warning message.

Just like the following:

useEffect(() => {
   getGrid();
}, [getGrid]);

Additional suggestion from T.J. Crowder to use useCallback for getGrid. You can do that as the following:

const getGrid = useCallback(() => {
    setGrid(getInitGrid());
}, [getInitGrid]);

Read further here:

  1. useEffect hook
  2. useCallback hook

You need to do the same with getInitGrid in your code.

I hope this helps!

Upvotes: 3

T.J. Crowder
T.J. Crowder

Reputation: 1075337

Most of the content within Pathfinder is static and should be moved outside it into its module; that will also have the effect of dealing with the useEffect problem you're having. All of the START_NODE_ROW etc. consts, getInitGrid, and createNode are static, there's no need to recreate them every time. I'd also wrap getGrid in useCallback and probably reorder things slightly so that things are only used after they're defined, but that's primarily for the human reader, not the compiler/JavaScript engine:

import React, { useEffect, useState } from "react";
import "../css/main.css";
import Node from "./node";

const START_NODE_ROW = 10;
const START_NODE_COL = 15;
const FINISH_NODE_ROW = 10;
const FINISH_NODE_COL = 35;

const getInitGrid = () => {
  const grid = [];
  for (let row = 0; row < 20; row++) {
    const currentRow = [];
    for (let col = 0; col < 50; col++) {
      currentRow.push(createNode(col, row));
    }
    grid.push(currentRow);
  }
  return grid;
};

const createNode = (col, row) => {
  return {
    col,
    row,
    isStart: row === START_NODE_ROW && col === START_NODE_COL,
    isFinish: row === FINISH_NODE_ROW && col === FINISH_NODE_COL,
    distance: Infinity,
    isVisited: false,
    isWall: false,
    previousNode: null
  };
};

const Pathfinder = () => {

  const [grid, setGrid] = useState([]);

  const getGrid = useCallback(() => {
    setGrid(getInitGrid());
  }, []);

  useEffect(() => {
    getGrid();
  }, []);

  return (
    <main id="Pathfinder">
      {grid.map((row, rowIdx) => {
        return (
          <div key={rowIdx}>
            {row.map((node, nodeIdx) => {
              const { row, col, isFinish, isStart } = node;
              return (
                <Node
                  key={nodeIdx}
                  col={col}
                  isFinish={isFinish}
                  isStart={isStart}
                  row={row}
                ></Node>
              );
            })}
          </div>
        );
      })}
    </main>
  );
};

export default Pathfinder;

Separately, getGrid is tiny and used only once. For me, it's not worth breaking it out into its own function:

import React, { useEffect, useState } from "react";
import "../css/main.css";
import Node from "./node";

const START_NODE_ROW = 10;
const START_NODE_COL = 15;
const FINISH_NODE_ROW = 10;
const FINISH_NODE_COL = 35;

const getInitGrid = () => {
  const grid = [];
  for (let row = 0; row < 20; row++) {
    const currentRow = [];
    for (let col = 0; col < 50; col++) {
      currentRow.push(createNode(col, row));
    }
    grid.push(currentRow);
  }
  return grid;
};

const createNode = (col, row) => {
  return {
    col,
    row,
    isStart: row === START_NODE_ROW && col === START_NODE_COL,
    isFinish: row === FINISH_NODE_ROW && col === FINISH_NODE_COL,
    distance: Infinity,
    isVisited: false,
    isWall: false,
    previousNode: null
  };
};

const Pathfinder = () => {

  const [grid, setGrid] = useState([]);

  useEffect(() => {
    setGrid(getInitGrid());
  }, []);

  return (
    <main id="Pathfinder">
      {grid.map((row, rowIdx) => {
        return (
          <div key={rowIdx}>
            {row.map((node, nodeIdx) => {
              const { row, col, isFinish, isStart } = node;
              return (
                <Node
                  key={nodeIdx}
                  col={col}
                  isFinish={isFinish}
                  isStart={isStart}
                  row={row}
                ></Node>
              );
            })}
          </div>
        );
      })}
    </main>
  );
};

export default Pathfinder;

(You don't have to declare setGrid or getInitGrid as dependencies, since the first is a state setter function from React that is guaranteed to be stable, and the second is defined outside Pathfinder.)

Upvotes: 0

Related Questions