-1

We have a React app which communicates with a third party library for phone integration. Whenever someone calls, the third-party library triggers a callback function inside the React app. That has been fine until now, but now this callback function needs to access the current state which seems to pose a problem. The state inside of this callback function, seems to always be at the initial value and never updates.

I have made a small sandbox here to describe the problem: https://codesandbox.io/s/vigorous-panini-0kge6?file=/src/App.js

In the sandbox, the counter value is updated correctly when I click "Internal increase". However, the same function has been added as a callback to ThirdPartyApi, which is called when I click "External increase". When I do that, the counter value reverts to whatever is the default in useState.

How can I make the third library be aware of state updates from inside React?

App.js:

import React, { useState, useEffect } from "react";
import ThirdPartyApi from "./third-party-api";
import "./styles.css";

let api = new ThirdPartyApi();

export default function App() {
  const [counter, setCounter] = useState(5);

  const increaseCounter = () => {
    setCounter(counter + 1);
    console.log(counter);
  };

  useEffect(() => {
    api.registerCallback(increaseCounter);
  }, []);

  return (
    <div className="App">
      <p>
        <button onClick={() => increaseCounter()}>Internal increase</button>
      </p>

      <p>
        <button onClick={() => api.triggerCallback()}>External increase</button>
      </p>
    </div>
  );
}

third-party-api.js:

export default class ThirdPartyApi {
  registerCallback(callback) {
    this.callback = callback;
  }

  triggerCallback() {
    this.callback();
  }
}
rasmusrim
  • 115
  • 1
  • 10

1 Answers1

2

You need to wrap increaseCounter() into a callback via React's useCallback. As it is, api.registerCallback() rerenders because of it, resetting counter.

You can learn more about this behavior here.

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

class ThirdPartyApi {
  registerCallback(callback) {
    this.callback = callback;
  }

  triggerCallback() {
    this.callback();
  }
}

let api = new ThirdPartyApi();

function App() {
  const [counter, setCounter] = useState(5);

  const increaseCounter = useCallback(() => {
    setCounter(counter + 1);
    console.log(counter);
  }, [counter]);

  useEffect(() => {
    api.registerCallback(increaseCounter);
  }, [increaseCounter]);

  return (
    <div className="App">
      <p>
        <button onClick={() => increaseCounter()}>Internal increase</button>
      </p>

      <p>
        <button onClick={() => api.triggerCallback()}>External increase</button>
      </p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);
Cody Bennett
  • 766
  • 5
  • 10