How to use Liveblocks Storage with React
In this guide, we’ll be learning how to use Liveblocks Storage with React using
the hooks from the @liveblocks/react
package.
This guide uses TypeScript. Liveblocks can definitely be used without TypeScript. We believe typings are helpful to make collaborative apps more robust, but if you’d prefer to skip the TypeScript syntax, feel free to write your code in JavaScript
Sync and persist the state across client
Some collaborative features require a single shared state between all users—an
example of this would be a
collaborative design tool, with each shape having
its own state, or a form with shared inputs. In Liveblocks, this is where
storage
comes in. Room storage automatically updates for every user on
changes, and unlike presence, persists after users disconnect.
Storage types
Our storage uses special data structures (inspired by CRDTs) to resolve all conflicts, meaning that state is always accurate. There are multiple storage types available:
LiveObject
- Similar to a JavaScript object.LiveList
- An array-like ordered collection of items.LiveMap
- Similar to a JavaScript Map.
Defining initial storage
To use storage, first define a type named Storage
in liveblocks.config.ts
,
like we did for Presence
. In this example we’ll define a LiveObject
called scientist
, containing first and last name properties.
Then, define the initial structure within RoomProvider
.
Using storage
Once the default structure is defined, we can then make use of our storage. The
useStorage
hook allows us to access an immutable version of our storage
using a selector function.
The two input values will now automatically update in a realtime as firstName
and lastName
are modified by other users.
useStorage
returns null
during the initial loading because the storage is
loaded from the server. It can quickly become cumbersome to handle null
whenever we use useStorage
, but we have some good new for you;
@liveblocks/react
contains a
Suspense
version of all
of our hooks.
Updating storage
The best way to update storage is through mutations. The useMutation
hook
allows you to create reusable callback functions that modify Liveblocks state.
For example, let’s create a mutation that can modify the scientist’s name.
Inside this mutation we’re accessing the storage root, a LiveObject
like
scientist
, and retrieving a mutable copy of scientist
with
LiveObject.get
. From there, we can set the updated name using
LiveObject.set
.
We can then call this mutation, and pass nameType
and newName
arguments.
If we take a look at this in the context of a component, we can see how to
combine useStorage
to display the names, and useMutation
to modify
them. Note that useMutation
takes a dependency array, and works similarly to
useCallback
.
All changes made within useMutation
are automatically batched and sent to the
Liveblocks together. useMutation
can also be used to retrieve and modify
presence too, giving you access to multiple parameters, not just storage
.
Find more information in the Mutations section of our documentation.
Nested data structures
With Liveblocks storage, it’s possible to nest data structures inside each
other, for example scientist
could hold a LiveList
containing a list of
pets.
Because the useStorage
selector converts your data structure into a normal
immutable JavaScript structure (made from objects, arrays, maps), pets
can be
accessed directly with useStorage
.
You can even reach into a LiveObject
or LiveList
and extract a property.
Improving storage performance
useStorage
is highly efficient and only triggers a rerender when the value
returned from the selector changes. For example, the following selectors will
only trigger rerenders when their respective values change, and are unaffected
by any other storage updates.
However, selector functions must return a stable result to be efficient—if a new object is created within the selector function, it will rerender on every storage change.
To account for this, we can pass a shallow
equality check function, provided
by @liveblocks/react
:
Find more information in the How selectors work section of our documentation.
Using Suspense
If you’d like to use Suspense
in your application, make sure to re-export our
hooks from liveblocks.config.ts
like so.
And then put a Suspense
component right below the RoomProvider
. This version
of useStorage
never returns null
, the loading fallback will be handled by
Suspense
fallback
.
If you’re using a framework that supports Server Side Rendering like
Next.js, you cannot use Suspense
directly like this.
Liveblocks does not load the storage on the server by default, so the components
using useStorage
will never be able to render. To keep the benefits from
Suspense
, you should use ClientSideSuspense
from @liveblocks/react
instead of the normal Suspense
from React like this: