2

I have an external function that changes a state inside a component thru ref and ImperativeHandler hook

const [state, setState] = useState(0);

// inside component
const testFunc = () => {
   setState(1)
}

// outside of component
function *generator {
    yield ...
    yield testFunc()
    // next yield should wait till state is updated, only then it can be executed 
    yield ...
}

but I can't figured out how to track that state has been chaged, since setState hook has no callback option. How can I track the moment when the state changed in the outside function such as my generator example?

Andrew
  • 695
  • 1
  • 8
  • 21
  • Your updated question will not work. state has no value in useState. Anyways, you should check the post that I've linked in my comment under my post. – Bhojendra Rauniyar May 26 '20 at 16:51

4 Answers4

1

Since you are using hooks and the updater function doesn't receive a 2nd argument like the class updater version, you might want to implement an updater with a 2nd argument on your own:

function useStateWithCallback(initState) {
  const cbRef = useRef(null);

  const [state, setState] = useState(initState);

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

  const updater = (value, callback) => {
    cbRef.current = typeof callback === "function" ? callback : null;
    setState(value);
  };

  return [state, updater];
}
  • We create our own useState with the same signature, a function that accept an initial state and returns a tuple of the current state and an update function, but this time the updater function takes 2 arguments, the next state and a callback.

  • We need a way to store the callback passed to our updater, we could store it inside a state with useState but each update will trigger a render so we are storing it in a ref which is persistent across renders but mutable and won't trigger re-renders when updated. We start with a null value.

  • We store and handle the actual state of the value with useState

  • We sync an effect to this state value and calling the callback stored in our ref. We do that conditionally as the effect will run on the first render before any state been updated (this is why we start the ref with null).

  • We create our own updater function which store the callback in our ref (while validating that its indeed a function) and trigger the actual state update.

  • We return our tuple with the real state and our custom updater

Running example:

const {useState, useRef, useEffect} = React;

function useStateWithCallback(initState) {
  const cbRef = useRef(null);

  const [state, setState] = useState(initState);

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

  const updater = (value, callback) => {
    cbRef.current = typeof callback === "function" ? callback : null;
    setState(value);
  };

  return [state, updater];
}

function App() {
  const [count, setCount] = useStateWithCallback(0);

  const myCallback = currentState => console.log("currentState", currentState);

  const onClick = () => {
    setCount(c => c + 1, myCallback);
  };

  return <button onClick={onClick}>{count}</button>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(
    <App />,
  rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root" />
Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
  • thanks for the explanation, but I still don't fully get how your `updater` works. Which part should I rewrite if I want to wrap setState hook into `new Promise` and `resolve()` it on callback, so I could use `then()` and not to repeat every time `return new Promise(resolve => setState({ some_state }, resolve)` ? – Andrew May 28 '20 at 10:08
0

You can create a special case for this promise:

// inside imperative handle
specialStateSetter: () => {
  // your logic/tracker here
  setState({ value: 0 })
}
Incepter
  • 2,711
  • 15
  • 33
0

since setState has no callback option

setState can be passed a second argument, which is a callback function executed after state has been updated.

ref : https://fr.reactjs.org/docs/react-component.html#setstate

aquinq
  • 1,318
  • 7
  • 18
0

since setState has no callback option How can I track the moment when the state changed

You're wrong. There's callback for setState:

setState(() => ({ value: 0 }), callback)
Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231