I've been learning hooks and one concept still really bogles me.
When using useEffect, any variables declared inside becomes old after the next re-render. To get access to changing values inside useEffect, the most common answer, and one Dan Abramov uses himself, is to use the useRef hook instead.
However, imagine that there is a piece of information that you want to store in a global state using something like Redux, but you also want that information available in callback functions inside useEffect. In my particular case, when my component mounts I need to add event listeners to a web socket connected to the server that signals WebRTC connections. The value that the web socket listener callback functions needs will be updated throughout the application's usage.
How do I organize a state that is globally accessible, but can also be referenced the same way that a ref made by useRef can be accessed?
Here's an example of what I mean
//App.js
import React, {useEffect} from "react"
import {useSelector} from "react-redux"
import socketIOClient from "socket.io-client";
const App = () => {
const users = useSelector(state => state.users)
let socket
//when the component mounts, we establish a websocket connection and define the listeners
useEffect(() => {
socket = socketIOClient(URL)
//in my app in particular, when the user decides to broadcast video, they must make new RTCPeerConnections
//to every user that has logged in to the application as well
socket.on("requestApproved", () => {
//at this point, the users array is outdated
users.forEach(callbackToCreateRTCPeerConnection)
})
}, [])
}
When the client receives a response from the server that they can begin broadcasting, the client needs to have an accurate reflection of which users have logged in throughout their use of the app. Obviously, the value of users
is stale at this point, because even the value from useSelector is not updated inside useEffect, although it is outside. So I could use useRef here to achieve what I want, but this isn't the only place where I use the users array, and I don't want to have to pass a ref down as props over and over again.
I have read about using useContext, but, if I understand correctly, when the context value changes, and the whole app is consuming the context, then a re-render is triggered for the entire app.
Any ideas, suggestions, explanations? Maybe there's a better place to add the event listeners to the sockets besides a useEffect?
Thanks in advance.
The idea about listeners is that they should be destroyed and recreated on closure value updates and cleaned up on unmount. You can add a users dependency to the useEffect
and cleanup the listener.
const App = () => {
const users = useSelector((state) => state.users);
let socket;
//when the component mounts, we establish a websocket connection and define the listeners
useEffect(() => {
socket = socketIOClient(URL);
//in my app in particular, when the user decides to broadcast video, they must make new RTCPeerConnections
//to every user that has logged in to the application as well
const listener = () => {
//at this point, the users array is outdated
users.forEach(callbackToCreateRTCPeerConnection);
};
socket.on('requestApproved', listener);
return () => {
socket.off('requestApproved', listener);
};
}, [users]);
};