zustand my favorite library to
mispronounce everyone else's favorite
library to use for State Management in
react before we go into why we're
talking about it today I need to settle
this pronunciation thing once and for
all because my chat they're mad at me
right now if you didn't already know we
film a lot of videos live and at this
moment they're upset with me I started
Googling I found this very fun poll on
the react subreddit where it seems like
my zustand has successfully pierced the
brains of react devs but we need to hear
it we need a proper hear the Google
pronunciation so for the first time ever
y'all get to hear the right way to
pronounce zustand on my
channel and that is the last time you
will ever hear zustan pronounced
correctly on my channel because I am
never ever saying it that way I don't
care with that all settled let's talk
about why Zan's a community favorite as
soon as I saw this blog post I knew it
had to be a video so let's do it by the
last monthly post our sample app Teemo
was finally ready for some experiments
so in this post let's look at zustan to
see why has become a favorite in the
community to do this I'm going to
refactor Teemo to use zustand so that we
can see what it's like in practice what
is Teemo how much code can that query
save oh boy uh yeah I'm not going to
read this one live but if you are
interested I'll leave the link in the
description these posts actually seem
very useful and super super practical
but I want to figure out what Teo is it
has three screens one for logging in and
signing in another for viewing and
changing time entries and a screen with
a timer for creating new time entries
now we know what the app is now you guys
know there's a great blog post about
about tan stack query with it react
query I'm sure that one is nuts that
it's just showing how much you can
delete I had a great convo yesterday
with an employee at a popular AI company
about how they could use react query to
build a way simpler front-end experience
and it was mind-blowing for both of us
in the process so once again react query
is the search Theo react query and
you'll find a bunch of my YouTube videos
explaining why we're here for zustand
sorry zand second and only time you'll
ever hear me pronounce it even close to
correct so what is zand it's a small
State Management library that provides a
react HS to manage the state it is based
on the flux pattern like Redux but it's
similar to how Redux simplified the
original flux pattern zustan simplifies
it even further for example here's how
you would set up a simple store with a
count value and an action to increase it
import create from zustand use count
store create we have the set function
here we return an object it has a
property count which is starting at zero
and then it has increment which is a
function that you can call and we give
this an inline function definition set
and then here set can take a function as
well and that function gets called with
the current state then we can use that
in the response here so it's similar to
when you use something like UST State
the state updator function can either be
called with a bare value or can be
called with a function that it will call
with the current value super simple API
makes it really easy to do stuff like
this does have some catches with
typescript though and we'll get into
those in a little bit to achieve the
same result with Redux toolkit you would
have had to set up a new slice add the
new slice to the root reducer and also
set up the creation of the store and its
provider if it hasn't already been set
up with such stand you can access the
count value in the increment action from
the generated use count store hook you
just call it we called create here set
it as this value and now this value is a
hook we can use wherever and we get all
the values here we use count was just
calling count store. count and then we
increment it with count store. increment
nice and simple since zustand mostly
consists of a few react hooks its npm
package is only 3.1 kiloby minified
which is it's 44 times smaller than the
base react package if you know react is
quite small but react Dom which is where
the bindings exist it's a bit larger
it's 13 times smaller than Redux as well
which is 40 kiloby minified to be fair
Redux toolkit has a bit of additional
bloat but it has to because Redux
toolkit is the only way to make Redux
usable I also like that he's not
comparing it to vanilla Redux which is a
show he's explicitly comparing to
Redux toolkit good call refactoring
Teemo to use zand since Teo is already
using tanet query only had to rework the
user authentication in profile storage
to use a stand in the plane react
version and the tanet query version this
was done using react context refactoring
this to zustan took around an hour in
many cases it was almost a drop in
replacement since I already used a hook
to access the underlying context values
let's take a look at this it's readable
but not quite as readable as I would
like so here we have the Teemo common
hooks use user and we deleted that and
replaced it with use user store nice and
simple for a lot of places the tedious
part of the refactor was changing the
components in the common package I
wanted to keep separate copies of Teemo
with each of the libraries we took a
look at so I set up the repo as a monor
repo with each version stored as a
separate folder to cut down on the
duplicate code I also created a common
package to store code that was used by
several versions of Teemo sadly some of
the components in the common package use
react context they won't work with the
sustained version of Teemo because I was
using zustand instead of react context
so I had to refactor these components to
be pure and then refactor the previous
versions to work with these changes I
still kept the components that depended
on context in the common package but
moved them to a separate folder to show
that they were not pure this is a very
strange use case to be clear where you
have three user experience-wise
identical versions of the same app and
you're trying to share components
between them I find that sharing
components between projects in general
is kind of rough it's part of why I
don't love component libraries and much
prefer things like Tailwind where I can
Define them myself or things like Shaden
where they live in my code base it's
pretty important to have your shared
components be really really like
low-level things so instead of using
button your reusable component is button
and then you wrap it with the user
button that's somewhere in your
application code so it doesn't surprise
me this was somewhat painful because a
lot of these components can't be pure
and once you abstract non-pure
components and they have expectations
about things like which contexts are
available falls apart fast so I'm
not surprised this was painful but I'm
pumped that he continued to push through
it and make this work the other thing
that stumped me for a moment was
handling side effects in zustand it
seemed to have the concept of middleware
but I couldn't find any docs about how
to write our own the docs only had
instructions about using the middleware
that came with the library why did this
matter well since I was using HTTP only
cookies to authenticate with the API I
had to make a request to the API when
the app started to verify whether the
user was already logged in I could write
an action using zustand to make the
request but how could I run it whenever
the app started ooh I'm going to share
my weird solution to this problem and
then I'm going to see how they solved it
so what I recommend for these types of
things if this is like a fundamental the
application experience is based on
whether or not this exists I wouldn't
put this in the global use user store
like this what I would do instead is use
a context I know the whole point is
we're moving off contexts but zust and
contexts are actually quite useful for
these cases so what I would have done is
I would have created a context for this
store and at the top level of the app I
would have done with like react query or
something the check to figure out if the
user it's authenticated or not and then
I would pass that to the create store
function I would actually require this
take a user in as part of its creation
and pass it the user once we have it and
then render something else entirely if
we don't have a user but I would have
code here in app at the root that
actually figures out if you're
authenticated or not and it generates a
different state either the logged out
State or the rest of the app with the
store already instantiated if you're
logged in this obviously only works if
your store is dependent on that value
and you don't want it if the user isn't
signed in but I've used this pattern a
ton especially in the Ping code base
ping by the way is the video product
that we built and went through YC with
to make it easier for Content creators
to do live collaborations there was a
lot of complex state in this app around
which devices are available so if I go
to my room quick here there's a lot of
complexity around which devices are
available and once they're available
which things can we use so the way that
I handled this if we go back to the page
here that one loading spinner is
actually handling a couple different
things the first layer is a check to see
if you're authenticated to join this
call or not then we check do you have
the right permissions for devices like
does the browser have permission to use
video cameras and microphones on our
page or not and once all of that is
completed then we render the zustand
store that actually manages your devices
so the zustand store that keeps track of
which devices you have and are available
in the video and audio tracks they're
creating does not get created that store
does not exist until all the permissions
are established so that's how I've
solved this problem I actually find that
nested zustan stores like that where
they are created conditionally based on
other things being met helps you escape
from having to do a more complex State
machine with all these different
potentials the idea of a state machine
that only exists when you are ready for
it to might feel weird especially if
you're treating it like a global but if
you don't treat it like a global you're
just using it as an API for defining
what can be accessed where I found it
was actually quite pleasant but now I'm
curious how this author handles the
problem and thank you agor for the shout
out for paying it's both really cool as
like a user of it but also with such a
crazy expor ation of different things
you can do in react and it definitely
leveled up my react skills massively oh
and in case this wasn't clear a zustan
context means that this store is
available in any children components so
if you use this at a lower level like in
the player in ping you can't access that
store from above that component but you
can from there and Below which I found
to be a pretty nice way to manage these
things in a lot of cases so there you go
now you hopefully understand why these
things work this way back to this the
first solution that came to my mind was
to create a custom wrapper component
that had use effect hook to run the
action but use effect hooks are evil and
I try to avoid them as much as I can
yeah I don't love this but then I
realized we can interact with the
zustand store outside of react so we can
run the action immediately after the
store is declared another plus of this
approach is that the API request is only
made once during development if we use
the use effect hook instead like the
previous version of Teemo the request
would be made twice since react triggers
use effect hooks twice when react strict
mode is enabled during Dev yep I got
into a long back and forth of Dan abov
back in the day about this specifically
because of the nature of nested uh
stores and triggering specific events in
this case connecting to a call I'm going
to do the thing I never should do and
actually look for the source code for
this so here is how we handle the
connection to your chat room in ping
because in ping we have a little chat I
should probably reopen that so in ping
we have the little chat here where I can
send
messages it also keeps track of who's in
the call which requests exist for people
to join the call and a bunch of other
random things like that that is handled
through a service that I do not
recommend called a uh I'm scared to say
more because I legitimately think they
might sue me they are one of the most
toxic businesses I've ever worked with
and I'm sure I'm going to get emails
just for saying that much here anyways
the room manager context provider here
is a component that gets mounted when we
know you're allowed in the room so we
now know that you're authenticated to
join that's part of the props that come
in here I then use traditional react
state to create a real-time store which
is again A's way of connecting through
via websocket to their store by default
I don't actually instantiate it though
because I have a use effect here which I
use to create the realtime zustand store
and then I set store to new store and
now I have this store here in state and
then I have a cleanup here that handles
all of the different things you have to
do to leave Ena because there is no one
leave Ena you have to do like 15
things and then I have a really nasty
stringify to make sure that we only
rerun when the config changes even
though some of the keys in this are
objects that might break this obnoxious
but by far the easiest way to handle
that don't do this don't ever do this it
works but don't do it and then we return
the real-time manager provider and the
store for it is created using realtime
store so what is this create realtime
zustand store thing that's where the
interesting stuff happens as somebody in
chat was hinting at earlier you don't
actually need to do this stuff in react
and what was there is just how it's
bound to react but the vast majority of
the logic in particular where the state
changes occur all happen happens outside
of react in this file right here so in
here we have a couple types we have the
user presence data which keeps track of
what the presence info which is which
users are here and what devices do they
have available we have the room manager
store which has the abley client the
public channels the presence channels
which are like who is here and then the
private channels which is something that
I actually don't know how much we're oh
it's for embeds or some things can't
connect to those which is embeds but
that's for private data messages things
like that I have the provider in the
store coming from the D in create
context helper the realtime store config
so this is the stuff that's specific to
this store's existence we have the slug
which is what it's connecting to connect
private Channel which is should we
connect the private Channel or not
publish presentence which is do we
publish the present state or not and do
we use the embed process for
authentication which is a different way
of authenticating this is the stuff you
pass in when you're instantiating the
store then we have the create realtime
zustand store helper function that I
wrote it takes in that config it creates
the able client it connects to the
public Channel basically on the slug
that we passed it attaches it then binds
the analytics events it then catches uh
if there's any errors and we were
obsessed with analytics because a was
breaking constantly and they were
gaslighting us Non-Stop about it so we
over logged and analyzed everything here
so I could give them logs proving that
their service was broken here we have
public channel.on failed again obsessive
logging because they don't give anyone
way to know when it's broken I have the
private Channel which is conditionally
connected not if it's not so here we set
let if we're supposed to connect we
connect bind all of these things same
with presents and here's where things
get interesting we create the store and
you'd think I would just return that
right but that's where things get more
fun if we keep scrolling I actually bind
a bunch of things to the stuff that we
created here because we created the
store with all of these values but I
also want to keep track of presence data
I also want to know what messages were
sent I have a lot of other things that I
want that are not being handled here and
I do that below because this store is an
object that can interfaced with you
don't just have to call this in react
with a hook I call it down here with a
store. set state so without even
touching react at all I have a bunch of
logic here that updates the store and
updates the values in the store just by
creating it because in this Creator
helper function I bind all of these
things after the creation occurs this
isn't the only place we do this with
ping we also do this for our video
provider
Agora similar pattern here where we
instantiate the used state uh for the
Agora store I use this create Agora call
store helper set that to State and then
we have the cleanup for if you leave or
unmount the component we call the
internal zust Gore provider we pass it
the created store and then we can render
our children and all of those will have
access to this store and again if we go
look at the logic here hop into
store you'll see it's very similar Gore
client create simple Goro client store
vanilla create set and we return the
Goro client call State the participants
volume map all of these additional
things but again afterwards where things
get interesting I actually bind all the
Agora listeners that keep the state up
to date so here I new store. set State
whenever new participants come in I just
do this by looking at the vanilla client
Because by the way the Agora client
doesn't reliably let you know who is or
isn't connected you have to ask it who's
currently connected so I set up this
helper to update the current connected
participants and then I have all of the
different events where a user joins or
leaves bound to call this function so
that the state machine gets updated when
any of these events occur then I handle
volume the volume handler was so hard
because doing this in a way that didn't
cause non-stop updates that made
everything update constantly that was a
bit painful but I have that here because
we wanted to debounce and do whatever we
could to keep track of those volume
levels in a way that didn't suck and
then the volume indicator is what
triggers those updates now we have the
connection State changed which if you're
banned we will set the state to double
join cuz that's the only time that uid
ban occurred with the way we had things
set up initially and that would set this
specific State and then leave in those
cases and then we return the store A bit
chaotic but the value of this pattern is
that I can do all of the different
things that this client does in this
case Agora I can set those all to update
our state directly without having to do
that with effects and react this all
exists externally in vanilla JS and then
zustand makes it really easy to consume
this stuff within react so it basically
Al lets us make this store do all of
these updates write the new store.
setstate calls in here and now I can
reuse this code in other places other
Frameworks wherever else but importantly
this code still works in react because I
can now call this as a hook so that's
how we solve the problem and that's also
quite different from using the use
effect to update the store so the
difference to be very clear is that in
this example the user store always
exists no matter what the user store
exists and then these conditions are
triggered to update the store to the
state that the user wants it in what I
did instead is the store is only created
once we know this so if we have to
connect via able if we have to
authenticate the user we do any of those
other things we do that then we use the
results to determine if and how we
should create the store so let's see how
he handles it it's interesting how
getting used to the react way leads you
to overlook simpler Solutions in plain
JS to the same problem it's actually a
funny quote with what I just showed so
here uh yeah we have the instantiation
for the store and then just call use
user store.get state. fetch user Yep
this is basically what I'm doing just
slightly higher level my impressions of
zustand after refactoring Teemo to use
zustand I have to say that I really like
its generated hooks they're easy to get
started with and they can easily be
fine-tuned to prevent renders this makes
it a perfect fit for managing state that
has to be shared across several parts of
a react app oh boy you found some good
context let's watch this one I'm curious
now the volume working oh God pre
mustache Theo so ugly why did you guys
ever let me look like that I'm sure
y'all are having a lot of fun on Twitter
making fun of Brin our old co-founder
for the doesn't listen to Nickelback
good days Classics good find ones in
chat if I looked like that when you
first started watching my
content want to see some ones for the
pre mustache
crew before my videos were clickable
that's a lot of y'all zeros if you came
post mustache so I have that for
comparison
new today so zero yeah you're you're
definitely post mustache if you're
showing up today Zero by a month damn
you were right on the line cool to see
always interesting to see when and how
people showed up here it was overdue
hair cut no mustache the oh you
were early early for those who aren't
familiar
plan here's OG needed a haircut badly
but was too busy Theo back in the day
this was even pryc if I recall yeah YC
started in January of 2022 so this was
so early so early anyways speaking of
early I want to show some stuff in
Dogecoin simulator because it's touched
on here with these selects but I just
want to show it in my own code base this
is a dumb app I made during the Dogecoin
Rush way back it's on GitHub and I want
to show some fun things that I did in
here so I have my game engine and this
game engine is entirely built in Z stand
and the game engine is pretty aggressive
because things are updating constantly
there's a tick that's firing multiple
times a second and that triggers a bunch
of other stuff to change like when I buy
numbers go up and down there's a lot of
things going on in this project that
cause it to update State all over the
place so I had to be pretty careful
about where state does and doesn't get
updated here's where I create the
default State
instance here's where I actually load it
from Storage I save this all in local
store and I have to abuse combine which
is the only way to make zustand type
safe at the moment to do a bunch of
crazy here in order to create all
of the updaters and states that actually
cause this game to behave the way it's
supposed
to but what I want to showcase is the
actual hooks that I consume things with
I need to show how the selectors work
first though so let's start there so see
all of these used game store hooks your
immediate thoughts probably why why not
just call use game store and access all
the keys off It Well by doing it this
way I have selected just this state so
if I was to delete all the other hooks
here and just have this used game store
where I grab state. USD this component
will only update when USD changes if I
just called use gam store then this
component will rerun when anything
inside of the game store changes and
since we're selecting like this we're
telling zustand hey quickly before you
update my component get this key out of
the object and check if that changed the
diff isn't happening on the entirety of
game store the diff is happening on the
resulting selector here which is what
was cool about the thing I was just
showing with the um game engine if I go
back
there yeah not only can you select this
way but you can also calculate things
and do additional math and work here and
as long as what you return here is
representative of if an update should
occur or not because it's different from
the previous value if it's different it
will trigger an update if it's the same
it won't which is super super handy for
just dumping a bunch of in state
like I did here there is so much stuff
going on in the store but now you only
have to access and Trigger updates on
the parts that you want to go back to
the article I'm sure he's about to cover
what I just showed here yeah we have the
count store and then we have cons count
use count store State state. count so
this will trigger when the count changes
if we go back to how this store was
defined up here we have two things count
which is a value that changes and
increment which is a function that
theoretically shouldn't change so if you
have a button component for increment
that doesn't know the count it shouldn't
update when the count changes and you
can be sure of that very easily by
making the increment call to use count
store but we actually select the
increment key and since we did this this
will never trigger a rerender because
the function never changes I also like
how zustand tackles async operations is
just calling set with the result then
the action can be run in a synchronous
context no middleware no promise chains
this is what I was showing before with
all of our Agora Anda stuff where there
a lot of that was promises and then on
the dot thens we would trigger whatever
behaviors we wanted to so we have this
fetch user we try con users await get
user then we set the value if there's an
error then we set it to the error
state so the biggest benefit of
switching away from react context the
biggest issue with using context to
share State across several components is
that any change to the state will cause
all subscribed channels to render
moreover changing the context value also
causes child components within the
context provider to render since the
context provider usually wraps the
entire app the entire app ends up
rendering Teemo used to have this
problem in the profile screen where
saving the user details would cause the
entire app to render you could memorize
to get out of that I almost want to read
the source code oh he called it out here
you can solve this with memorization in
multiple contexts but it's tedious but
by using a state management Library like
zustand you can avoid these issues more
easily zustand doesn't require you to
wrap the entire app with a provider
component or Hawk and it sends updates
directly through the to the generated
hooks so the entire app doesn't render
when the state changes since you can
also subscribe to specific State values
and actions with usand you can also make
components render only when the state
relevant to them changes you know
what I want to prove that this isn't
that big a deal does he have source code
somewhere he does okay react
Source
app.jsx okay I found the problem almost
certainly but let's confirm where is
that coming from is the user context
provider one of the common packages it
is okay common context user context
provider uh ding ding
ding this should be memorized the
problem is that the children are going
to re rerun because there is no
indication that this can be treated
statically
if you were to memorize children before
rendering it you could avoid almost all
of the reenders here I'm pretty
certain yeah I'm almost positive that
would fix this do I clone it to prove
that do we make this already too long
video far too much longer so I'm going
to save this yeah see that the whole
page update that's the thing I think we
can nuke let's go straight in to test
that so my hypothesis is that this isn't
memorized proper
only one way to find out though which is
to go all the way up here to the user
context provider con memo
children and do that not sure if it's
smart enough to handle the hot reloading
for that so I'm going to root Force
resetting of those now I'm curious Tes
Theo V2 I'll change this to
L save it's still updating the
whole thing really
a no it's not was it before though
should probably have confirmed that in
the console ahead of time user context
provider
children H it's not
interesting so what is causing
everything to render then how is it
being
consumed if we go to that's common where
I need to be is the profile
component oh that's why con user is use
user that would do it yeah we only want
clear user from this so that's the issue
is that we're selecting way more than we
want from this hook and that triggers
updates that we don't actually want to
have happen that would not be fun to
optimize for I Now understand the pain
yeah if this context is being used for
all of these values as well as for
exposing that specific function
that sucks ass yeah never mind why did I
ever doubt the author clearly they have
a case here that sucks yeah it's
basically impossible with context to
select just one thing so in this case
what we want isn't the whole use user we
just want clear user which is a function
that theoretically should never change
but sadly because use user is a context
that provides all of these other things
in it it has the user object it has the
authentication state it has additional
values and all of this is part of what
this provider is giving us that's why
this sucks theoretically we could wrap
this with another provider that's like
user context actions provider that is
just the functions which should never
change and then in here we could just
consume that and be good do I do the
miserable thing of that it feels hellish
but I think I can do it relatively quick
source packages react Source yeah we
have it here as well with customize user
we have it at the top of profile which
is where we just were we have it in
change password as
well yeah I see the problem now so the
reason zustan is super useful for
something like this is if this was like
a used store then I could have store
store do user. clear user and now I
could select just the clear user
function and ignore everything else
which keeps it from having to update
when it shouldn't but this example seems
to be a lot more ironed out than I was
initially thinking there when I had my
initial push back yeah the suain version
when you save only updates specific
parts of the page instead of forcing the
whole profile to reender because if we
look at the code I'll just go to the
same place in the zustand version
zustand Source routes profile. jsx use
user store State state. clear user and
now we just have the clear user function
no additional stuff has to be done we
have to create a new function here we
can just pass that to button and it
works dope actually a really good
example nice having the three different
solutions in one repo to compare if
you're curious about the difference
between vanilla react react plus react
query and zustand you have a great place
to look to compare all of those sus
thing it's a thumbs up from me it's a
lightweight package that is well thought
out and approachable especially if
you're familiar with Redux and the flux
and Redux pattern this is probably why
the community's fallen in love with it
what if you hate the flux pattern and
anything else like it the next month's
post let's take a look at the atomic
patterns for managing state which also
come from Facebook and meta as well as
Jodi which is taking over from recoil as
the DEA facto library for this pattern
fun fact both Jodi and zust stand are
part of the point Manders Collective pmn
DRS they are legends the amount of cool
they build is unbelievable this is
where react 3 fiber came from where
react spring and use gesture came from
where a ton of crazy 3D stuff came from
it's also zustand and Jodi as
well great stuff yeah one of the best
sources of good react Solutions and also
a great resource for General full stack
typescript problem solving awesome
project awesome crew awesome stuff that
they're building any opportunity I get
to shout them out I take also my vs code
theme which I get asked about all the
time if you're this late into the video
I'll let you know the secret this is the
PO Anders theme yeah this was great
shout out to veros for writing this it
is a great post and I will definitely be
here to see the Jodi one that you update
in the future check out his blog if you
want to hear more about the uh if you
want to hear more about the react
context version as well as the react
query version the links are all in the
description check out the blog and until
next time peace Nars