I am trying to detect a variable change in react. The variable is a class property. That is set when an asynchronous event takes place.
You can see below the example of my class.
Inside method listenForIncomingMessage()
, something asynchronous happens and it sets the data property of the class.
class Message() {
static #instance: Message;
data!: any;
static getInstance(): Message {
if (!Message.#instance) {
Message.#instance = new Message();
}
return Message.#instance;
}
listenForIncomingMessage() {
// Something asynchronous is happening here
setTimeout(() => {
this.data = 'hello world';
}, 2000);
}
}
Then this class is initialized in main.tsx
like this and it is passed through a prop like this
const MainPage = () => {
const msg = Message.getInstance();
msg.listenForIncomingMessage();
return (
<div className="panel">
<Result message={ msg } />
</div>
);
};
ReactDOM.createRoot(
document.getElementById("root") as HTMLDivElement,
).render(<MainPage />);
Finally inside Result.tsx
, I need to detect the latest value for msg.data.
export default function Result({ msg }) {
let responseObj = useRef(msg.data);
useEffect(() => { <------ This is not triggered
console.log(msg.data);
}, [msg.data]);
)};
I could get the value from a callback, or event listener inside Result.tsx
but I am trying to see if there is a react hook solution.
Thank you.
2
Answers
The issue arises in your scenario because React won’t directly detect changes in plain class properties like
msg.data
, since these changes don’t trigger React’s state update. To solve this, you can useuseState
anduseEffect
to manage and monitor the changes in the data property.To detect changes in a class property (
msg.data
) using React hooks, you can:onDataChanged
) to notify whendata
changes.useState
in the component to hold the value ofdata
.useEffect
to subscribe to the change and update the state whendata
changes.Here’s a the solution:
This makes
msg.data
reactive in the component.Changes to an object property are not tracked by React, and don’t cause it re-render the parent and children.
You can wrap the instance in Proxy with a set() handler to catch changes to the
data
property, and set a state accordingly.In addition, since listening to external events is a side effect, this should happen inside a
useEffect
block. This will prevent an infinite loop of trigger listen -> set state -> re-render -> trigger listen -> …If you need to use the proxied instance for other things, you can wrap it in
useMemo
. The listen to changes, however, still needs to happen insideuseEffect
: