Why isn't useEffect being called on first render?

Viewed 39

It is my understanding that useEffect with the [] at the end is supposed to get called on first render. I am however calling the gridCell render from a .map function... is that what is causing it to not trigger the useEffect?

a little context: the idea is to be getting a 2d array of 81 GridCell DTOs from the back end and rendering them into our grid display. But when I send in a collection of blank cells with a populated cell in the first position; the useEffect function is not called and the data never gets set in the GridCell component and cellValue is undefined.

Have I missed something with react hooks that I should know about? I am new to react hook functional component development style, and I am coming from state driven component style. I would love to know the explanation behind what I have missed here or about the interaction with .map that I may have missed. Thank you.

import React, {useEffect, useState } from "react";
import ListGroup from 'react-bootstrap/ListGroup';
import Row from 'react-bootstrap/Row';
import GridCell from '../gridCell/gridCell';
import { blankCell, dummyCell} from "../../models/hint-grid";


const Grid = (props) => {
  const [gridCells, setGridCells] = useState([]);

  useEffect(() => {
    if(!props.gridData) {
      createGrid();
    } else {
      setGridCells(props.gridData)
    }
  }, []);

  const createGrid = () => {
    let newGrid = [];
    for (let i = 0; i < 9; i++){ 
      let cellRow = [];
      for(let j = 0; j< 9; j++) {
        cellRow.push(blankCell);
      }
      newGrid.push(cellRow);
    }
    newGrid[0][0] = dummyCell;
    setGridCells(newGrid);
  }

  return (
    <ListGroup>
      {
        ( gridCells.length === 9 ? gridCells.map(
          (gridRow, i) => 
            <Row key={`grid-row-${i}`}> 
              {gridRow.map((gridCell, j) => <GridCell key={`grid-cell-${i}-${j}`} cellData={gridCell}/>)} 
            </Row>
        ) : 
        <>
          {'loading...'}
        </>)
      }
    </ListGroup>
  )
}
export default Grid;
import  Button from "react-bootstrap/Button";
import React, { useEffect, useState } from "react";
import './gridCell.scss';

const GridCell = (props) => {
  const [cellValue, setCellValue] = useState('');
  const [isHint, setIsHint] = useState(false);

  useEffect = (() => {
    setCellValue(props.cellData.answer);
    setIsHint(props.cellData.across?.length > 0 || props.cellData.down?.length > 0 );
  }, []);

  const onCellClick = () => {
  }

  return (
    <Button title='title' className={`grid-cell ${isHint ? 'hint' : 'normal'}`} onClick={onCellClick}>
      <div>
        {cellValue}
      </div>
    </Button>
    
  )
}

export default GridCell;
1 Answers
useEffect = (() => {
  // ...
}, []);

This isn't calling useEffect, it's assigning to it. You need:

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

By the way... the thing you're using useEffect for (setting an initial state) can be done better. When you do it with a useEffect, there will be one render with the temporary values, and then you will rerender with the values you want. This double-render takes extra cpu time, and may result in a flash of the temporary values on the screen.

Instead, just do the computation outside of an effect, and pass the value you want as the initial value into useState:

const GridCell = (props) => {
  const [cellValue, setCellValue] = useState(props.cellData.answer);
  const [isHint, setIsHint] = useState(
    props.cellData.across?.length > 0 || props.cellData.down?.length > 0
  );

If the computation of the initial value is expensive, useState allows you to pass in a function. It will call that function exactly once to create the initial value, and then will skip it on subsequent renders:

const createGrid = () => {
  let newGrid = [];
  for (let i = 0; i < 9; i++){ 
    let cellRow = [];
    for(let j = 0; j< 9; j++) {
      cellRow.push(blankCell);
    }
    newGrid.push(cellRow);
  }
  newGrid[0][0] = dummyCell;
  return newGrid
}

const Grid = (props) => {
  const [gridCells, setGridCells] = useState(() => {
    if (!props.grid) {
      return createGrid();
    } else {
      return props.gridData
    }
  });
Related