With the release of 0.18 we’re bringing some exciting and pretty major improvements to our React hooks, letting you build apps with ease and with more control over the exact behavior.
The new APIs we’re introducing here solve many subtle and not-so-subtle pain points. We heard your feedback, and think we have shipped something awesome that you’ll love.
This guide consists of two sections:
Let’s dive right in!
With 0.18, the biggest conceptual shift is that our hooks to consume data from Liveblocks now return normal JavaScript data structures (objects, arrays, maps) that are immutable by default.
Suppose you have initialized your room with:
Reading nested data from there is now much easier:
As you can see, because we can read data with normal JavaScript data structures, accessing nested data is now straightforward.
Rerendering your components when nested data—like our scientist’s pets list—changes was a true head breaker before. This required extra helper components, manual subscriptions, and manual conversion to “normal” JavaScript arrays.
Now, rerendering your component when data changes is automatic, even for deeply nested data.
There is no typo in this example. This is the actual code.
Previously, if you wanted to derive a computed value from multiple storage values, it took some manual setup to ensure the component would automatically rerender when either of those values changed.
Now, this is fully automatic. Or should we say, automagic?
This component will rerender automatically any time a.x
or b.x
changes, but
not more often.
Previously we returned mutable Live structures for performance reasons because converting live changing data to JavaScript data structures constantly (and recursively) was previously too slow to do on every render. This led to unintuitive behavior when used with React hooks dependencies.
Not anymore! Due to a technique called structural sharing, we’re now able to guarantee for nodes in the Storage tree that as long as their (direct or nested) contents haven’t changed in Storage, their immutable representation will remain to be the same object references on the next render. This means that you can rely on referential equality, as you may have expected in the first place.
Starting with 0.18, all hooks that read data from Liveblocks come with a
Suspense version of the hook which will never return null
to indicate the
“still loading” state. Instead, they will suspend the rendering of the component
tree until Liveblocks has finished loading.
We recommend you to adopt Suspense if you can because it lets you get rid of the
ugly null
checks, helper components to “eat off” those null cases, and the
prop drilling that necessarily comes with all that.
Set up a Suspense boundary once:
Switch to use the Suspense versions of our hooks instead of the “normal” ones:
Then, enjoy no more null checks everywhere in your app:
To get the most out of the new hooks, we recommend following the steps below to gradually upgrade your app to make use of the new hooks.
We now require setting an initial presence value when you connect to a room explicitly. This ensures that every user is guaranteed to always have a known presence value.
Check that you have this in your config file:
If your app somehow doesn’t use Presence, you can just set an empty object
({}
) here.
If you have expressions in your code that look like...
You can now remove these optional chainings. The fields info
and presence
will now always be set on User
instances.
Now is a great moment to opt-in to Suspense (see the React docs) with Liveblocks, if you can or want to use it in your app. We recommend it for most apps because it makes working with the new hooks even nicer.
To avoid repeating ourselves, please follow the instruction below.
Now that you have updated your app to Suspense, you should be able to remove all
these pesky null
checks from your code.
Afterward, please verify that your app still works like normal.
useStorage
We recommend rewriting all usages of useList
, useObject
and useMap
if
those are used for reading data only. If used only for reading values from
Storage, you could turn these into an equivalent useStorage
call, which has
fewer gotchas.
For example, change:
to:
Note that the root
argument you receive here is the immutable normal
JavaScript equivalent of your entire Storage tree, as returned by calling
.toImmutable
.
So this means that if you have been manually converting the mutable Live structures to normal data structures, you no longer have to do this:
Please note that useList
, useObject
, or useMap
are not deprecated and
still work with the same behavior as before. We just no longer recommend their
use.
If you are (also) using useList
, useObject
or useMap
to obtain a mutable
reference to the Live structure to mutate it, you can rewrite those use cases
to use the new useMutation
hook instead.
For example:
The idiomatic way to deal with Storage is to consume data using simple/normal
JS data structures and to mutate data using a callback function that you can
create with useMutation
, which provides access to the mutable Live structures.
Even though in this contrived example it may look more complicated, in large apps this pattern will vastly simplify your app’s complexity.
room.subscribe()
callsHistorically the only way to get full control over exactly when and how your
components would rerender was to use the low-level room.subscribe()
API.
Most, if not all, of these use cases can be replaced by an equivalent, yet much
simpler call to useStorage
with a selector function that does an equivalent
thing.
Common use case: subscribing to nested data
If you are using room.subscribe
to manually rerender components when nested
data changes, you can replace it by “just” selecting the nested fields you’re
interested in. See this example.
Common use case: subscribing to a computed value
If you are using room.subscribe
to synchronize a computation based on multiple
storage values, you can replace it by “just” doing the computation in See
this example.
If you have another use case for room.subscribe
that you think isn’t possible
to express in an equivalent useStorage
call, please
let us know about it.
We’re happy to help!
Most, if not all, cases of manually calling useBatch
or room.batch
should no
longer be needed and can be replaced by useMutation
, which automatically
batches already!
That’s it!
If you run into issues with these new patterns and you need help, please let us know. We’re here to help!
We use cookies to collect data to improve your experience on our site. Read our Privacy Policy to learn more.