Search code examples
javascriptreactjsreact-nativeuse-effectuse-state

useEffect has a setState in it and is console.logging null


I will try to word this in the best way I can...

When I send a function through a prop to a child and then send it again to another child, then use the on click to activate it in the 'grandparent' function. When I console.log in that original function in the grandparent that console.logs a state, it prints undefined, yet when I am within that grandparent and try to activate that function, it will log the state correctly. If anyone can help me a little bit more in depth that would be great, we can call!

import React, { useEffect } from 'react';

import Row from '../row/row';

import './body.css';

import { nanoid } from 'nanoid';

export default function Body () {
    
    const [boxMain, setBoxMain] = React.useState(null)

    const [rows, setRows] = React.useState(null)

    const ref = React.useRef(null)

    function changeBox (event) {
        console.log(event);
        console.log(boxMain);
    }

    React.useEffect(() => {

        /* Describes array with all information */
        const sideBoxes = 40; 
        
        const heightContainer = ref.current.offsetHeight
        const widthContainer = ref.current.offsetWidth;

        const numRows = Math.floor(heightContainer / sideBoxes) - 1
        const numBoxes = Math.floor(widthContainer / sideBoxes)

        /* Beginning of array birth */

        let main = Array(numRows).fill().map(() => new Array(numBoxes).fill({
            id: "",
            water: false,
            land: false,
            air: false,
        }));
        /* End of array birth */
        
        const rows = []
        for (let i = 0; i < numRows; i++) {
            const id = nanoid();
            rows.push(
                <Row
                key={id}
                id={id}
                rowNumber={i}
                numBoxes={numBoxes}
                sideBoxes={sideBoxes}
                changeBox={changeBox}
                main={main}
                />
                )
            }

            setRows(rows)
            setBoxMain(main)

        }, [])

    
    return (
        <div>
            <div onClick={() => changeBox("test")}>
                TEST
            </div>
            <div ref={ref} className='body'>
                {rows}
            </div>
        </div>
    )
}

For examples here onClick={() => changeBox("test") the function works and logs "boxMain" correctly. But when I pass changeBox={changeBox} into ...

import React, { useEffect } from "react";

import Box from "../box/box";

import "./row.css";

import { nanoid } from 'nanoid';

export default function Row (props) {
    
    const ref = React.useRef(null)
    const [boxes, setBoxes] = React.useState()

    useEffect(() => {
        
        const tempBoxes = []

        for (let i = 0; i < props.numBoxes; i++) {

            const id = nanoid()
            
            tempBoxes.push(
                <Box 
                    rowNumber={props.rowNumber}
                    columnNumber={i}
                    key={id} 
                    id={id}
                    side={props.sideBoxes}
                    changeBox={props.changeBox}
                    main={props.main}
                />
            )
        }

        setBoxes(tempBoxes)

    }, [])
    
    
    return (
        <div ref={ref} className="row-main">
            {boxes}
        </div>
    )
}

Then pass changeBox={props.changeBox} to ...

import React from "react";

import "./box.css";

export default function Box (props) {
    
    React.useEffect(() => {
        props.main[props.rowNumber][props.columnNumber] = props.id
    }, [])

    const [detectChange, setDetectChange] = React.useState(0)
    const ref = React.useRef(null)
    const styles = {
            width: `${props.side}px`,
            height: `${props.side}px`,
        }


    return (
        <div
            ref={ref}
            className="box-main"
            key={props.id}
            id={props.id}
            rownumber={props.rowNumber}
            columnnumber={props.columnNumber}
            style={styles}
            onClick={() => props.changeBox([props.id, props.rowNumber, props.columnNumber])}
        >
            
        </div>
    )
}

I then have the onClick={() => props.changeBox([props.id, props.rowNumber, props.columnNumber])} and it returns to the original changeBox...

 function changeBox (event) {
        console.log(event);
        console.log(boxMain);
    }

but when I click the box it returns the event correctly but returns boxMain as null. When I click the onClick in the parent function although it console.logs everything correctly.

I know this is a ton of info but I know the fix has to be simple, or at least my method to do this should change.

Thank you for any feedback!! :)

Edit 1:

This is the output normally.

enter image description here

But when I simply add a space to the code and save it in VS Code (I guess some type of rerendering happens?) then it fixes to...

enter image description here

Although the IDs do change so I think everything refreshes in some way.


Solution

  • The useEffect hook of Body component runs only once because it does not have any dependency, thus changeBox callback passed to its children and grand children has the default state of boxMain, and it never updates.

    This is why calling changeBox inside Body component logs boxMain array correctly, while calling props.changeBox inside children components logs null.

    -------------- Solution ---------------------

    This is not the BEST solution, but it will give you an idea why it didn't work before, and how you can fix it.

    import React, { useEffect } from 'react';
    
    import Row from '../row/row';
    
    import './body.css';
    
    import { nanoid } from 'nanoid';
    
    export default function Body () {
    
    const [boxMain, setBoxMain] = React.useState(null)
    
    const [rows, setRows] = React.useState(null)
    const [rowsData, setRowsData] = React.useState(null)
    
    const ref = React.useRef(null)
    
    function changeBox (event) {
        console.log(event);
        console.log(boxMain);
    }
    
    React.useEffect(() => {
    
        /* Describes array with all information */
        const sideBoxes = 40; 
        
        const heightContainer = ref.current.offsetHeight
        const widthContainer = ref.current.offsetWidth;
    
        const numRows = Math.floor(heightContainer / sideBoxes) - 1
        const numBoxes = Math.floor(widthContainer / sideBoxes)
    
        /* Beginning of array birth */
    
        let main = Array(numRows).fill().map(() => new Array(numBoxes).fill({
            id: "",
            water: false,
            land: false,
            air: false,
        }));
        /* End of array birth */
        
        const rowsData = []
        for (let i = 0; i < numRows; i++) {
            const id = nanoid();
            rowsData.push({
                key: id,
                id,
                rowNumber: id,
                numBoxes,
                sideBoxes,
            })
        }
    
        setRowsData(rowsData)
        setBoxMain(main)
    
    }, [])
    
    React.useEffect(() => {
        const rows = []
        for (let i = 0; i < rowsData?.length; i++) {
            const id = nanoid();
            const data = rowsData[i];
            rows.push(
                <Row
                    {...data}
                    changeBox={changeBox}
                    main={boxMain}
                />
            )
        }
        setRows(rows)
    }, [rowsData, boxMain, changeBox])
    
    
    return (
        <div>
            <div onClick={() => changeBox("test")}>
                TEST
            </div>
            <div ref={ref} className='body'>
                {rows}
            </div>
        </div>
    )
    

    }