0

I have a simple counter child Component.

import React, {useState} from 'react'
import classes from './counter.module.css'

const Counter = (props) => {

    const [number, setNumber] = useState(0);

    const decrementHandler = () => {
        setNumber((prevState) => {
            if(prevState === 0) {
                return 0
            }
            return prevState - 1;
        })
        props.onCounterChange(number);
        
    }

    

    const incrementHandler = () => {
        setNumber((prevState) => {
            return prevState + 1;
        })
        props.onCounterChange(number);
    }



    
    return (
        <>
            <div className={`${classes['counter-wrap']}`}>
                <button className={`${classes['counter-button']} ${classes['decrement']}`} onClick={decrementHandler}>-</button>
                <span className={classes['number']}>{number}</span>
                <button className={`${classes['counter-button']} ${classes['increment']}`} onClick={incrementHandler}>+</button>
            </div>
        </>
    );
    
});

export default Counter;

I want to pass the counter value number to the parent component using props.onCounterChange() function. Since State is asynchronous, I am not receiving the latest state value to the parent. How do I pass the counter value to the Parent Component as soon as the state changes?

Rejin Jose
  • 41
  • 6

1 Answers1

0

Unlike with classes, React doesn't have a callback as a second arg on the setter. So you could call the handler inside the setter callbacks though technically the state hasn't changed yet:

const decrementHandler = () => {
  setNumber((prevState) => {
    if (prevState === 0) {
      return 0
    }
    props.onCounterChange(prevState - 1);
    return prevState - 1;
  })
}

const incrementHandler = () => {
  setNumber((prevState) => {
    props.onCounterChange(prevState + 1);
    return prevState + 1;
  })
}

Or you could call it after the state has changed with a useEffect, note this will be called when the component inits with 0 too:

useEffect(() => {
  props.onCounterChange(number);
}, [number])

Careful not to get a stale reference but make sure onCounterChange is constant too (useCallback):

useEffect(() => {
  props.onCounterChange(number);
}, [props.onCounterChange, number])

Or tunnel the callback in, ugly but doesn't depend on the user creating a constant reference to onCounterChange (arrow functions are new every time):

const onCounterChangeRef = useRef(props.onCounterChange);
onCounterChangeRef.current = props.onCounterChange

useEffect(() => {
  onCounterChangeRef.current(number);
}, [number])
Dominic
  • 62,658
  • 20
  • 139
  • 163
  • 1
    Hello @Dominic, Thank you so much for this. Actually the last method **tunnel the callback ** helped me in another scenario. – Rejin Jose Jul 16 '21 at 13:24