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!
Suppose you have initialized your room with:
Reading nested data from there is now much easier:
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
b.x changes, but
not more often.
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
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
If you have expressions in your code that look like...
You can now remove these optional chainings. The fields
will now always be set on
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
null checks from your code.
Afterward, please verify that your app still works like normal.
useStorageStep 5a: Replace reads with
We recommend rewriting all usages of
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
For example, change:
Note that the
root argument you receive here is the immutable normal
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
useMap are not deprecated and
still work with the same behavior as before. We just no longer recommend their
If you are (also) using
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.
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
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()callsStep 6: Get rid of
Historically the only way to get full control over exactly when and how your
components would rerender was to use the low-level
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
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
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
room.batch should no
longer be needed and can be replaced by
useMutation, which automatically
If you run into issues with these new patterns and you need help, please let us know. We’re here to help!