What’s new in v0.18
This release brings you powerful React hooks that return immutable data, React selector APIs, React Suspense support, improved core performance, and specialized React hooks for optimized use cases.
At Liveblocks, we strive to craft ergonomic and easy-to-use APIs that allow developers to build performant multiplayer experiences. With 0.18, we’re making some important changes in that direction:
- React hooks that return immutable data
- React selector APIs
- React Suspense support
- Improved core performance
- Specialized React hooks for optimized use cases
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.
New React hooks that return immutable data
With 0.18, we’re introducing two main new hooks: useStorage
and useMutation
,
which should make reading and writing to Storage much more natural in a React
world. The biggest conceptual shift is that the useStorage
hook lets you
consume data using normal JavaScript data structures (objects, arrays, maps)
that are immutable.
This means you’ll no longer have to deal with LiveObject
, LiveList
, or
LiveMap
when reading data—only when mutating!
While the useObject
, useList
, and useMap
hooks will still work in 0.18, we
recommend migrating to useStorage
and useMutation
. We’ve written
a step-by-step guide to help you migrate. If
you’re stuck, please reach out to us on
GitHub, and we’ll lend you a hand!
useStorage
This improves the developer experience significantly, enabling you to turn code that looks like this…
into code that looks like this…
Much better, right?
But that’s not all. 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.
useStorage
is also helpful when you need to derive a computed value from
different objects. Previously, it took some manual setup to ensure the component
would automatically rerender when either of those values changed.
The code can now be simplified as follow to rerender automatically any time
a.x
or b.x
changes, but not more often.
Now, this is fully automatic. Or should we say, automagic? useStorage
feels
right at home in React now: say bye to those redundant useEffect
with
room.subscribe
hooks!
useMutation
Now that reading from the Liveblocks storage state is all read-only and no
longer returns mutable data structures, how do we mutate things? The answer is
the new useMutation
hook.
It’s almost like a normal React useCallback
, but it provides you access to the
mutable Storage root through the first callback argument. For instance, here is
what a fill action could look like in a design tool. Simply pass the id and the
color of the shape you want to fill.
Notice how we’re able to also set the last used color for the current user. Handy, right? Especially when you start using this for more advanced scenarios like deleting all the shapes that are currently selected.
With useMutation
, you can now write custom mutation logic for your application
where both presence and storage work seamlessly together—enabling you to build
world-class multiplayer experiences, no matter how complex the experience you’re
trying to build is.
What’s also nice about it is that it allows you the choice of how to organize your mutation code. You can colocate it directly in your components, or centralize them as common actions, making it easier for larger teams to interact with the state at scale.
To read more about how mutations work exactly, please refer to the
useMutation
API reference.
React selector APIs
As you may have seen in the section above, useStorage
takes a selector
function. With 0.18, we’re also bringing these selectors to the existing
useSelf
and useOthers
hooks. The selector APIs receive immutable data,
return arbitrary values, and automatically subscribe to updates.
The received input to all selector functions is a read-only and immutable top-level context value that differs for each hook:
useStorage((root) => ...)
receives the Storage rootuseSelf((me) => ...)
receives the current useruseOthers((others) => ...)
receives a list of other users in the roomuseOthersMapped((other) => ...)
receives each individual other user in the roomuseOther(connectionId, (other) => ...)
receives a specific user in the room
For full details, you can read more in the how selectors work section of our documentation.
React Suspense support
The benefit of using
Suspense with Liveblocks is
that the hooks will no longer return null
when Liveblocks is still loading.
Instead, you can let your Suspense boundary handle the still-loading case
centrally by showing the fallback state.
This improves the developer experience significantly, enabling you to turn code that looks like this…
into code that looks like this…
Much better, right?
If you want to learn more about using Suspense with React, please make sure to read the guide in the documentation.
Improved core performance
For this release, we’ve had to refactor several parts of our core. Not only did
this make the @liveblocks/react
package size go down from 12.6kB to 8.2kB
minified, but it also made the core faster, more scalable, and future-proof.
We’ve also made several changes to the current functionalities in Liveblocks
core. Setting the initial presence upon entering a room is now required, for
example, either by calling client.enter()
or using RoomProvider
. This means
that when a user enters the room, their presence is instantly recognized. In the
past, there would be a brief moment when a user’s presence data wasn’t known yet
and it would be unknown, causing a UI flash in some situations. But this is no
longer the case — either the user is fully known, or they will not show up in
the room until then.
Specialized React hooks for optimized use cases
Lastly, in addition to the useStorage
and useMutation
hooks above, we added
a few more specialized pragmatic hooks to assist with building for two common
use cases where rendering performance matters:
useOthersMapped
to select subsets of othersuseOthersConnectionIds
anduseOther
to build highly optimized UIs
These can be particularly useful for rendering live cursors in highly interactive applications where multiple people are simultaneously editing the same document.
If you’re interested in learning more, please read the API reference for
useOthersMapped
,
useOthersConnectionIds
,
and useOther
.
Contributors
Huge thanks to everyone who contributed! Keep checking out the changelog for the full release notes – and see you next time!