2

I'm trying to find a way to imitate the callback function ofthis.setState({},() => {})but using hooks.

I managed to make a method that mimics the useState function but returns a callback with the result of the change with the updated data.

The reception of the callback returns after the state has been updated, as can be seen from the logs, the reception of the callback returns only after the console.log("App") appears, behavior similar to how it should do if it were used the this.state with acallback.

The problem is that inside the callback of the modified setState, even if it is called after updating the app, I can't see the update of the state, that is, inside it I don't see the updated state if I try to print it.

According to you there is a solution to be able to do something to see it updated.

const { useState, useRef, useEffect } = React;

function useStateCallback(initialState) {
  const [state, setState] = useState(initialState);
  const cbRef = useRef(null);
  useEffect(() => {
    if (cbRef.current) {
      cbRef.current(state);
      cbRef.current = null;
    }
  }, [state]);
  return [
    state,
    (value, cb) => {
      cbRef.current = typeof cb === "function" ? cb : null;
      setState(value);
    }
  ];
}

const App =() => {

  const [stateObj, setStateObj] = useStateCallback({
    name: "James",
    surname: "Bond",
    number: 7
  });
  const { name, surname, number } = stateObj;

  useEffect(() => {
    //console.log("componentDidMount", stateObj);
    //console.log("componentDidMount2", stateObj);
    //console.log("useEffect", stateObj);
  }, []);

  const change = () => {
    //console.log("componentDidMount", stateObj);
    setStateObj(
      previousValue => ({
        ...previousValue,
        name: "Arthur",
        surname: "Conan",
        number: previousValue.number + 14
      }),
      res => {
        console.log("Res:", res, stateObj);
      }
    );
    //console.log("componentDidMount2", stateObj);
  };

  console.log("App", stateObj);

  return (
    <div>
      Name: {name}
      <br />
      Surname: {surname}
      <br />
      Number: {number}
      <br />
      <button onClick={change}>Change</button>
      <button onClick={() => console.log(stateObj)}>Render</button>
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

<div id="root"></div>
Paul
  • 3,644
  • 9
  • 47
  • 113
  • 1
    All you need is `useEffect(() => { /* code here */ }, [stateObj]);`: https://codesandbox.io/s/ecstatic-sun-exww1 –  Feb 29 '20 at 14:27

2 Answers2

1

With hooks, you already get a callback when state has been updated: Your component function gets called again. This is a fundamental aspect of hooks, updating state makes React call your component function again.

Example:

const { useState } = React;

const Example = () => {
    const [counter, setCounter] = useState(0);
    console.log(`Example called, counter = ${counter}`);
    return (
        <div>
            {counter} <input type="button" value="+" onClick={() => setCounter(c => c + 1)} />
        </div>
    );
};

ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.11.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

There's no need for any other setter callback. The component is the callback.

Dan Abramov has a good article that really hammers this home here. It's in the context of explaining useEffect, but the foundation it lays should help.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

If I understood what you are trying to do correctly, I think you simply need to pass the new state value as an argument to the callback call. This way you don't need to keep it in a ref nor update it from useEffect:

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

    return [
        state,
        (value, callback) => {
            setState(value);

            if (typeof callback === "function") {
                callback(value);
            }
        }
    ];
}
Alvaro
  • 9,247
  • 8
  • 49
  • 76