Balasubramanian
Balasubramanian

Reputation: 5510

Not getting updated hooks value inside function

Not getting updated value of state in function which gets triggered by eventListener. Even tried with background timer. What would be the problem?

displayIncomingCall() // this function gets called from UI.
const callApp = () => {
  const [calls, setCalls] = useState({});

  const addCall = (key, value) => {
    setCalls({ ...calls, [key]: value });
  };

  const displayIncomingCall = number => {
    const callUUID = getCurrentCallId();
    addCall(callUUID, number);
    ...
  };

  const onAnswerCall = () => {
    console.log('=== onAnswerCall ::: ===', calls); <--- always initial value. Not getting updated one.
  }

  useEffect(() => {
    console.log('=== useEffect ::: ===', calls); // getting updated value
  }, [calls]);

  useEffect(() => {
    RNCallKeep.addEventListener('answerCall', onAnswerCall);
    return () => {
      RNCallKeep.removeEventListener('answerCall', onAnswerCall);
    };
  }, []);

  console.log('=== parent ::: ===', calls); // getting updated value

  return (...)
}

Upvotes: 1

Views: 130

Answers (3)

tobiasfried
tobiasfried

Reputation: 1842

Your event listener callback is closed over the initial state of calls. Add it as a dependency to the effect, and you should register a new listener (and remove old ones) each time its value changes.

EDIT: In reality, you should move the entire onAnswerCall function into the effect, since it is only used there:

useEffect(() => {
  const onAnswerCall = () => {
    console.log('=== onAnswerCall ::: ===', calls);
  };

  RNCallKeep.addEventListener('answerCall', onAnswerCall);
  return () => {
    RNCallKeep.removeEventListener('answerCall', onAnswerCall);
  };
}, [calls]);

Upvotes: 1

Christos Lytras
Christos Lytras

Reputation: 37288

React hooks state does not persist any state data, you have to use useCallback and the previous state privided in the set* state functions to keep up with your state:

const callApp = () => {
  const [calls, setCalls] = useState({});

  const addCall = (key, value) => {
    // Use previous state provided to update the state
    setCalls(calls => ({ ...calls, [key]: value }));
  };

  const displayIncomingCall = number => {
    const callUUID = getCurrentCallId();
    addCall(callUUID, number);
    ...
  };

  //  Use useCallback to keep up  with the  state
  const onAnswerCall = useCallback(() => {
    console.log('=== onAnswerCall ::: ===', calls); <--- Will get updated
  }, [calls]);

  useEffect(() => {
    console.log('=== useEffect ::: ===', calls); // getting updated value
  }, [calls]);

  useEffect(() => {
    RNCallKeep.addEventListener('answerCall', onAnswerCall);
    return () => {
      RNCallKeep.removeEventListener('answerCall', onAnswerCall);
    };
  }, [onAnswerCall]);

  console.log('=== parent ::: ===', calls); // getting updated value

  return (...)
}

Upvotes: 1

CertainPerformance
CertainPerformance

Reputation: 370619

onAnswerCall is only referenced in the useEffect callback, and the useEffect callback has an empty dependency array, so it only runs on the initial mount. Whenever onAnswerCall is called, the binding of calls that it can see is in an old closure.

Use a ref instead of state:

const callsRef = useRef({});
const addCall = (key, value) => {
  callsRef.current[key] = value;
};
const onAnswerCall = () => {
  console.log('=== onAnswerCall ::: ===', callsRef.current);
}

If calls needs to be in state so that its change results in a re-render, then you can either use both state and a ref, and assign to the ref when you call setCalls, or you can remove the dependency array from useEffect, thus adding and removing the listeners to RNCallKeep on each render.

Upvotes: 1

Related Questions