Why can't I see parent state from child component event handler?

Viewed 126

I have two components: Meal and Ingredient. Ingredient is always a child of Meal. I pass the function below to each Ingredient as a prop, e.g. <Ingredient deleteIngFunc={handleDeleteIng} />.

What the button in Ingredient is clicked, Ingredient fires prop.deleteIngFunc but when I console.debug the ingsArrayCopy from the the function, it is empty! It shouldn't be empty because the state is obviously there when the page renders (i.e. the ingredients are displayed on the page, so the state is obviously not empty)

Meal.js

import React, {useState, useEffect} from 'react';
import Ingredient from './Ingredient';

function Meal(props){
    const [ingArray, setIngArray] = useState([]);

    function handleDeleteIng(e){
        let ingsArrayCopy = [...ingArray];
        console.debug(ingsArrayCopy); // empty array??
    }

    async function initIngs(){
        let ings = props.ings.map(
            ing => (
                <Ingredient title={ing.title} id={ing.id} deleteIngFunc={handleDeleteIng} />
            )
        )

        return await setIngArray(prev => ings)
    }

    function updateIngs(newIngObject){
        let oldIngState = [...ingArray];

        let newIng = <Ingredient key={newIngObject.id} id={newIngObject.id} title={newIngObject.title} />

        let newIngState = oldIngState.concat([newIng]);

        setIngArray(prev => newIngState);
    }


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

    return (
        <ul>
            {ingArray}
        </ul>
    );
}

export default Meal;

Ingredient.js

import React, {useState, useEffect} from 'react';
import CancelIcon from '@material-ui/icons/Cancel';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';

function Ingredient(props){

    function handleDeletion(e){
        e.preventDefault()
        props.deleteIngFunc(e)
    }

    return (
        <li
            key={props.id}
            className="ingredient-listitem"
        >
            <Typography className="ingredient-text">{props.title}</Typography>

            <IconButton onClick={handleDeletion}>
                <CancelIcon></CancelIcon>
            </IconButton>
        </li>
    )
}

export default Ingredient;
1 Answers

No, the value of the state cannot be access inside of a regular function. It can only be access through the following react hooks useEffect() useCallback() useMemo() and on the return statement of your functional component.

For Example:

function App() {
  const [state, setState] = useState('initial Value')

  function someFunction(){
    // It's not recommended to access the state inside of this function
    // cause the value of the state will always be ('initial Value')
    // even if the state were updated
    console.log(state)
   // Why ?
   // Because regular function like this cannot have dependency array
   // And thus without dependency array function will not know that state has changed
  }

  useEffect(()=>{
    // It's good if you access the value of the state here
    // It will be assured it will always have the updated value
    console.log(state)
  },[state]) 


  return (
    // You can also access the value of the state inside return statement        
    <>
    {console.log(state)}
    <SomeComponent props={state}/>
    </>
  )
}

Additionally: You can set the state on a regular function but you cannot access or read the state on a regular function

I'd included a codesandbox link for a demonstration that are specific to your problem

https://codesandbox.io/s/accessingstate-03hud?file=/src/App.js

Related