I’m trying to write some inter-frame-comunication hook and I’m not sure that the implementation is correct. Unfortunately, the react lifecycle topic seems very complex (example) and I couldn’t find a definite answer or recommendation about how to implement it correctly.
Here’s my attempt at writing the hook:
const frame = /*...*/;
let messageId = 0;
function usePostMessage(
eventName: string,
handler: (success: boolean) => void
) {
const [pendingMessageId, setPendingMessageId] = useState<number>();
const postMessage = useCallback(() => {
frame.postMessage(eventName);
setPendingMessageId(++messageId);
}, [eventName]);
useEvent(
"message",
useCallback(
(message) => {
if (
message.eventName === eventName &&
message.messageId === pendingMessageId
) {
handler(message.success);
setPendingMessageId(undefined);
}
},
[eventName, handler, pendingMessageId]
)
);
return { postMessage, pendingMessageId };
}
(I’m using useEvent
)
Usage:
const { postMessage, pendingMessageId } = usePostMessage(
"eventName",
(success) => {
console.log("eventName", success ? "succeeded" : "failed");
}
);
if (pendingMessageId !== undefined) {
return <div>Pending...</div>;
}
return <button onclick={postMessage}>Click me</button>;
As you can see, I tried to implement a way to post a message and get a response from a frame. I also tried to avoid pitfalls such as getting unrelated responses by keeping a message counter.
It works, but I’m afraid that the "message" event might arrive before the setPendingMessageId
state is updated. Is that possible? Are there any guidelines or best practices for implementing this correctly? Thanks.
2
Answers
Update the
setPendingMessageId
inside theuseEffect
hookstate update is applied after the
postMessage
function has been called, avoiding the race condition.No. If a state setter is called inside a React function (such as an
onclick
prop, as in your code), React will re-render a component after that React handler finishes running its code. JavaScript is single-threaded; oncesetPendingMessageId(++messageId);
is called, the click handler will end, and then a re-render will occur. There’s no chance of any other code running before then. The receipt of the message goes through a non-React API (themessage
listener on the window), so React doesn’t try to integrate it into the rerendering flow.That said, although your code will work, to avoid having to worry about this, some might prefer to reference the stateful values as they are when the message is posted rather than put the logic in a separate hook, which could be less reliable if the state gets out of sync for some other reason. So instead of
useEvent
, you could consider something along the lines ofHaving a
messageId
outside of React is a little bit smelly. It’d be nice if you could integrate it into state somehow (perhaps in an ancestor component) and then add it to the dependency array forpostMessage
.