[reactjs] How to use callback with useState hook in react

I am using functional component with hooks. I need to update state in parent from a child. I am using a prop function in Parent. All works fine except my prop function is getting the previous state and not the current state. My prop function gets executed before useState hook setting current state. How can I can I wait for my call back function to be executed after useState call. I am looking for something like setState(state,callback) from class based components.

Here is the code snippet:

function Parent() {
  const [Name, setName] = useState("");
  getChildChange = getChildChange.bind(this);
  function getChildChange(value) {
    setName(value);
  }

  return <div> {Name} :
    <Child getChildChange={getChildChange} ></Child>
  </div>
}

function Child(props) {
  const [Name, setName] = useState("");
  handleChange = handleChange.bind(this);

  function handleChange(ele) {
    setName(ele.target.value);
    props.getChildChange(collectState());
  }

  function collectState() {
    return Name;
  }

  return (<div>
    <input onChange={handleChange} value={Name}></input>
  </div>);
} 

This question is related to reactjs react-hooks

The answer is


You can use useEffect/useLayoutEffect to achieve this:

const SomeComponent = () => {
  const [count, setCount] = React.useState(0)

  React.useEffect(() => {
    if (count > 1) {
      document.title = 'Threshold of over 1 reached.';
    } else {
      document.title = 'No threshold reached.';
    }
  }, [count]);

  return (
    <div>
      <p>{count}</p>

      <button type="button" onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
};

More about it over here.

If you are looking for an out of the box solution, check out this custom hook that works like useState but accepts as second parameter a callback function:

// npm install use-state-with-callback

import useStateWithCallback from 'use-state-with-callback';

const SomeOtherComponent = () => {
  const [count, setCount] = useStateWithCallback(0, count => {
    if (count > 1) {
      document.title = 'Threshold of over 1 reached.';
    } else {
      document.title = 'No threshold reached.';
    }
  });

  return (
    <div>
      <p>{count}</p>

      <button type="button" onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
};

you can utilize useCallback hook to do this.

function Parent() {
  const [name, setName] = useState("");
  const getChildChange = useCallback( (updatedName) => {
    setName(updatedName);
  }, []);

  return <div> {name} :
    <Child getChildChange={getChildChange} ></Child>
  </div>
}

function Child(props) {
  const [name, setName] = useState("");

  function handleChange(ele) {
    setName(ele.target.value);
    props.getChildChange(ele.target.value);
  }

  function collectState() {
    return name;
  }

  return (<div>
    <input onChange={handleChange} value={name}></input>
  </div>);
}

Another way to achieve this:

_x000D_
_x000D_
const [Name, setName] = useState({val:"", callback: null});_x000D_
React.useEffect(()=>{_x000D_
  console.log(Name)_x000D_
  const {callback} = Name;_x000D_
  callback && callback();_x000D_
}, [Name]);_x000D_
setName({val:'foo', callback: ()=>setName({val: 'then bar'})})
_x000D_
_x000D_
_x000D_


You can use like below -

this.setState(() => ({  subChartType1: value   }), () => this.props.dispatch(setChartData(null)));

_x000D_
_x000D_
function Parent() {_x000D_
  const [Name, setName] = useState("");_x000D_
  getChildChange = getChildChange.bind(this);_x000D_
  function getChildChange(value) {_x000D_
    setName(value);_x000D_
  }_x000D_
_x000D_
  return <div> {Name} :_x000D_
    <Child getChildChange={getChildChange} ></Child>_x000D_
  </div>_x000D_
}_x000D_
_x000D_
function Child(props) {_x000D_
  const [Name, setName] = useState("");_x000D_
  handleChange = handleChange.bind(this);_x000D_
  collectState = collectState.bind(this);_x000D_
  _x000D_
  function handleChange(ele) {_x000D_
    setName(ele.target.value);_x000D_
  }_x000D_
_x000D_
  function collectState() {_x000D_
    return Name;_x000D_
  }_x000D_
  _x000D_
   useEffect(() => {_x000D_
    props.getChildChange(collectState());_x000D_
   });_x000D_
_x000D_
  return (<div>_x000D_
    <input onChange={handleChange} value={Name}></input>_x000D_
  </div>);_x000D_
} 
_x000D_
_x000D_
_x000D_

useEffect act as componentDidMount, componentDidUpdate, so after updating state it will work


With React16.x, if you want to invoke a callback function on state change using useState hook, you can use the useEffect hook attached to the state change.

import React, { useEffect } from 'react';

useEffect(() => {
  props.getChildChange(name); // using camelCase for variable name is recommended.
}, [name]); // this will call getChildChange when ever name changes.

Actually, you should avoid using this when using react hooks. It causes side effects. That's why react team create react hooks.

If you remove codes that tries to bind this, you can just simply pass setName of Parent to Child and call it in handleChange. Cleaner code!

function Parent() {
  const [Name, setName] = useState("");

  return <div> {Name} :
    <Child setName={setName} ></Child>
  </div>
}

function Child(props) {
  const [Name, setName] = useState("");

  function handleChange(ele) {
    setName(ele.target.value);
    props.setName(ele.target.value);
  }

  return (<div>
    <input onChange={handleChange} value={Name}></input>
  </div>);
} 

Moreover, you don't have to create two copies of Name(one in Parent and the other one in Child). Stick to "Single Source of Truth" principle, Child doesn't have to own the state Name but receive it from Parent. Cleanerer node!

function Parent() {
  const [Name, setName] = useState("");

  return <div> {Name} :
    <Child setName={setName} Name={Name}></Child>
  </div>
}

function Child(props) {    
  function handleChange(ele) {
    props.setName(ele.target.value);
  }

  return (<div>
    <input onChange={handleChange} value={props.Name}></input>
  </div>);
} 

we can write customise function which will call the callBack function if any changes in the state

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

const useStateCallbackWrapper = (initilValue, callBack) => {
  const [state, setState] = useState(initilValue);
  useEffect(() => callBack(state), [state]);
  return [state, setState];
};

const callBack = state => {
  console.log("---------------", state);
};
function App() {
  const [count, setCount] = useStateCallbackWrapper(0, callBack);
  return (
    <div className="App">
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

`


setState(updater, callback) for useState

Following implementation comes really close to the original setState callback from classes.

Additions made to Robin's solution:

  1. Callback execution is omitted on initial render (we want to call it only on state updates)
  2. Callback can be dynamic for each setState invocation, like with classes

Usage

const App = () => {
  const [state, setState] = useStateCallback(0); // same API as useState + setState with cb

  const handleClick = () => {
    setState(
      prev => prev + 1,
      // 2nd argument is callback , `s` is *updated* state
      s => console.log("I am called after setState, state:", s)
    );
  };

  return <button onClick={handleClick}>Increment</button>;
}

useStateCallback

function useStateCallback(initialState) {
  const [state, setState] = useState(initialState);
  const cbRef = useRef(null); // mutable ref to store current callback

  const setStateCallback = useCallback((state, cb) => {
    cbRef.current = cb; // store passed callback to ref
    setState(state);
  }, []);

  useEffect(() => {
    // cb.current is `null` on initial render, so we only execute cb on state *updates*
    if (cbRef.current) {
      cbRef.current(state);
      cbRef.current = null; // reset callback after execution
    }
  }, [state]);

  return [state, setStateCallback];
}

Further info: React Hooks FAQ: Is there something like instance variables?

Working example

_x000D_
_x000D_
const App = () => {
  const [state, setState] = useStateCallback(0);

  const handleClick = () =>
    setState(
      prev => prev + 1,
      // important: use `s`, not the stale/old closure value `state`
      s => console.log("I am called after setState, state:", s)
    );

  return (
    <div>
      <p>Hello Comp. State: {state} </p>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}

function useStateCallback(initialState) {
  const [state, setState] = useState(initialState);
  const cbRef = useRef(null);

  const setStateCallback = useCallback((state, cb) => {
    cbRef.current = cb; 
    setState(state);
  }, []);

  useEffect(() => {
    if (cbRef.current) {
      cbRef.current(state);
      cbRef.current = null;
    }
  }, [state]);

  return [state, setStateCallback];
}

ReactDOM.render(<App />, document.getElementById("root"));
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>
_x000D_
_x000D_
_x000D_


Examples related to reactjs

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Template not provided using create-react-app How to resolve the error on 'react-native start' Element implicitly has an 'any' type because expression of type 'string' can't be used to index Invalid hook call. Hooks can only be called inside of the body of a function component How to style components using makeStyles and still have lifecycle methods in Material UI? React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release

Examples related to react-hooks

Invalid hook call. Hooks can only be called inside of the body of a function component React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? react hooks useEffect() cleanup for only componentWillUnmount? How to use callback with useState hook in react Push method in React Hooks (useState)? React Hooks useState() with Object useState set method not reflecting change immediately React hooks useState Array Can I set state inside a useEffect hook