Like most developers I know, I learn a lot every day at work. That's part of why I wanted to make programming part of my job!
I really like getting to immediately apply new things, and that helps me learn better, too. Despite reading lots of explainers and a few tutorials, I didn't really start to grok React Hooks until I needed to dig into the Daily React video chat demo.
When I think about the roles useEffect
, useMemo
, and useCallback
play in a video chat app, something a lot of us use every day, I better remember how each hook works and recognize other opportunities to use them.
In case reading about that practical application could help you too, I decided to write about it! After a quick Hooks refresher, we'll look at an example of each these Hooks in the Daily demo app, and why we decided to use each.
I mentioned in my last post about avoiding global variables that I'm trying to get better about sharing what my colleagues teach me. This round is brought to us by the inimitable Jess Mitchell, who I am so grateful to work with and learn from!
Before we get hook'd
I really liked Ali Spittel's definition of Hooks on the latest Ladybug podcast:
"Hooks are a special type of function that allow you to hook into the component lifecycle of react components. So they allow you to do special things that a normal function wouldn't be able to do."
This means we can do unique things when components mount, update, and unmount. Like the docs say, we can take advantage of state and other features without having to write class components.
With that overview in mind, let's look at three Hooks in our video chat app: useEffect
, useMemo,
and useCallback
.
useEffect
to manage participant updates in state
With useEffect
, we can, well, perform side effects in function components, based on state or prop changes.
In a video chat app, lots of things happen! Participants join and leave calls, start and stop their audio and video tracks, and then some. Our UI needs to update along with these changes. For example, it needs to add and remove video tracks as participants come and go.
The Daily API fires corresponding events as these things happen, e.g. 'participant-joined'
, 'track-stopped'
, etc. In our video chat app, we listen for these events and their handlers set our particpantUpdated
state in response.
Here's where useEffect
comes in! We only need to update the UI when a change has happened, when participantUpdated
is set. We pass participantUpdated
as a dependency (along with the call object that contains the updated participant data) to a useEffect
hook, so we only update our participant list when something has changed.
useEffect(() => {
if (participantUpdated) {
const list = Object.values(callObject?.participants());
setParticipants(list);
}
}, [participantUpdated, callObject]);
That covers storing the participant list, but what about displaying participants, rendering their video and audio tracks? That's where our next hook comes in.
useMemo
to re-render videos only when we have to
useMemo
returns a memoized value. Memoized means a value that's the result of an expensive function call.
There are lots of expensive calculations in a video chat app. Each participant's audio and video track alone contains loads of data, and computing that on every render would be a lot.
Instead, we pass our participants
state value as a dependency to the useMemo
hook that displays our tiles.
const displayLargeTiles = useMemo(() => {
const isLarge = true;
const tiles = participants?.filter((p) => !p.local);
return (
<div className="large-tiles">
{tiles?.map((t, i) => (
<Tile
key={`large-${i}`}
videoTrackState={t?.tracks?.video}
audioTrackState={t?.tracks?.audio}
isLarge={isLarge}
disableCornerMessage={isScreenShare}
onClick={
t.local
? null
: () => {
sendHello(t.id);
}
}
/>
))}
</div>
);
}
}, [participants]);
The useMemo
hook lets us only change the videos displayed when the participants
have changed, instead of recalculating on every render.
Note: Because we built a video chat app with participant changes that happen often, the performance benefits from
useMemo
are significant; it saves us many recalculations. In a lot of cases, though, this hook might be more than your application needs.useMemo
has a reputation of being overused, so tread with caution.
useCallback
to re-render startLeavingCall()
function only when we have to
Just like useMemo
prevents us from re-calculating values that haven't changed, useCallback
lets us stop specific functions from re-rendering.
In our App.js component, lots of things can trigger a re-render. But our startLeavingCall
function, for example, only needs to re-render if the callObject
, which stores data about our call, or our appState
changes. This is because the function does different things depending on those values.
We pass callObject
and appState
as our dependencies.
/**
* Starts leaving the current call.
*/
const startLeavingCall = useCallback(() => {
if (!callObject) return;
// If we're in the error state, we've already "left", so just clean up
if (appState === STATE_ERROR) {
callObject.destroy().then(() => {
setRoomUrl(null);
setCallObject(null);
setAppState(STATE_IDLE);
});
} else {
setAppState(STATE_LEAVING);
callObject.leave();
}
}, [callObject, appState]);
Hook'd and wanting more?
I hope this helped make Hooks feel a little more applicable! Can you think of any new ways to apply useEffect
or useMemo
, or useCallback
in apps that you're building? Tell me in the comments! Especially tell me if you'll be building any video (or audio!) apps. You can give me a shout over on Twitter too!
Top comments (11)
Nice post.
Thank you!
Good post! It helped me a lot
I'm so glad to hear that, thank you, @ajest ! @gemontracks helped me a lot and I'm happy to pass it on.
I had built a telehealth app with the same logic as above. If I read this post at that time, things could be easier, lol. But the process of finding a solution is fun :)
Solutions-finding is what it's all about, and we need all the telehealth solutions we can get these days! It's awesome that you built one 💯
Please update the link to Twitter, it's not working
Thank you for letting me know! Sorry about that. I just updated that link, and here it is too in case useful: twitter.com/kimeejohnson
Great! Thank you for the quick response!
Nice! Thank U!
Thank you so much for reading!