How can I set up a connection inside useEffect and at the same time decorate that connection with a custom hook? Custom hooks are not allowed to run inside useEffect and ref.current is not permitted during rendering. What is the proper way to wrap a connection with a React-friendly interface?
I believe the right way to set up a WebSocket connection in React, is with a useEffect hook, along the lines of
const connection = useRef(null);
useEffect(() => {
const ws = new WebSocket(socketUrl);
connection.current = ws;
return () => {
ws.close();
connection.current = null;
}
}, [socketUrl]);
In a similar way I envisage creating a RTCDataConnection, which follows the same connection interface with .send('...') and open, close, error and message events.
I am looking for a way to provide a React-like interface to these connections. I was thinking something like the following custom useConnection hook, which takes a JavaScript connection object and in return provides two React state variables lastMessage and readyState, and a method sendMessage:
export function useConnection(connection) {
const [lastMessage, setLastMessage] = useState(null);
const [readyState, setReadyState] = useState(null);
const onReadyStateChange = useCallback(
(e) => {
console.info(e);
setReadyState(connection.readyState);
},
[setReadyState]
);
const onMessage = useCallback(
({ data }) => {
const message = JSON.parse(data);
setLastMessage(message);
},
[setLastMessage]
);
function sendMessage(message) {
const msgJSON = JSON.stringify(message);
connection.send(msgJSON);
}
useEffect(() => {
connection.addEventListener('open', onReadyStateChange);
connection.addEventListener('close', onReadyStateChange);
connection.addEventListener('error', onReadyStateChange);
connection.addEventListener('message', onMessage);
return () => {
connection.removeEventListener('open', onReadyStateChange);
connection.removeEventListener('close', onReadyStateChange);
connection.removeEventListener('error', onReadyStateChange);
connection.removeEventListener('message', onMessage);
};
}, [connection]);
return { lastMessage, readyState, sendMessage };
}
I can't find out, however, how to properly apply the hook to the connection.
- By the rules of hooks, I can't call
useConnectioninsideuseEffect. It throws "Invalid hook call" at me. - I can't reference
connection.currentoutside theuseEffecteither, because that violates the rules ofuseRef: It is not allowed to access the.currentproperty during rendering.
So my question is, how do I bring the connection and the hook together? Or should I use a different approach altogether?