API Reference - @liveblocks/react
@liveblocks/react
provides you with React bindings for
our realtime collaboration APIs, built on top of WebSockets. Read our
getting started guides to learn more.
Suspense
All Liveblocks React components and hooks can be exported from two different
locations, @liveblocks/react/suspense
and @liveblocks/react
. This is because
Liveblocks provides two types of hooks; those that support
React Suspense, and those that
don’t.
We recommend importing from @liveblocks/react/suspense
and using Suspense by
default, as it often makes it easier to build your collaborative application.
Suspense hooks
Suspense hooks can be wrapped in ClientSideSuspense
, which acts as a
loading spinner for any components below it. When using this, all components
below will only render once their hook contents have been loaded.
Advanced hooks using the { ..., error, isLoading }
syntax, such as
useThreads
, can also use
ErrorBoundary
to render an
error if the hook runs into a problem.
An advantage of Suspense hooks is that you can have multiple different hooks in
your tree, and you only need a single ClientSideSuspense
component to render a
loading spinner for all of them.
Regular hooks
Regular hooks often return null
whilst a component is loading, and you must
check for this to render a loading spinner.
Advanced hooks using the { ..., error, isLoading }
syntax, such as
useThreads
, require you to make sure there isn’t a problem before using
the data.
ClientSideSuspense
Liveblocks provides a component named ClientSideSuspense
which works as a
replacement for Suspense
. This is helpful as our Suspense hooks will throw an
error when they’re run on the server, and this component avoids this issue by
always rendering the fallback
on the server.
Loading spinners
Instead of wrapping your entire Liveblocks application inside a single
ClientSideSuspense
component, you can use multiple of these components in
different parts of your application, and each will work as a loading fallback
for any components further down your tree.
This is a great way to build a static skeleton around your dynamic collaborative application.
Liveblocks
LiveblocksProvider
Sets up a client for connecting to Liveblocks, and is the recommended way to do
this for React apps. You must define either authEndpoint
or publicApiKey
.
Resolver functions should be placed inside here, and a number of other options
are available, which correspond with those passed to createClient
. Unlike
RoomProvider
, LiveblocksProvider
doesn’t call Liveblocks servers when
mounted, and it should be placed higher in your app’s component tree.
- authEndpoint
The URL of your back end’s authentication endpoint as a string, or an async callback function that returns a Liveblocks token result. Either
authEndpoint
orpublicApikey
are required. Learn more about using a URL string and using a callback. - publicApiKeystring
The public API key taken from your project’s dashboard. Generally not recommended for production use. Either
authEndpoint
orpublicApikey
are required. Learn more. - throttlenumberDefault is 100
The throttle time between WebSocket messages in milliseconds, a number between
16
and1000
is allowed. Using16
means your app will update 60 times per second. Learn more. - preventUnsavedChangesbooleanDefault is false
When set, navigating away from the current page is prevented while Liveblocks is still synchronizing local changes. Learn more.
- lostConnectionTimeoutnumberDefault is 5000
After a user disconnects, the time in milliseconds before a
"lost-connection"
event is fired. Learn more. - backgroundKeepAliveTimeoutnumber
The time before an inactive WebSocket connection is disconnected. This is disabled by default, but setting a number will activate it. Learn more.
- resolveUsers
A function that resolves user information in Comments, Text Editor, and Notifications. Return an array of
UserMeta["info"]
objects in the same order they arrived. Learn more. - resolveRoomsInfo
A function that resolves room information in Notifications. Return an array of
RoomInfo
objects in the same order they arrived. Learn more. - resolveGroupsInfo
A function that resolves group information in Comments and Text Editor. Return an array of
GroupInfo
objects in the same order they arrived. Learn more. - resolveMentionSuggestions
A function that resolves mention suggestions in Comments and Text Editor. Return an array of user IDs or mention objects. Learn more.
- polyfills
Place polyfills for
atob
,fetch
, andWebSocket
inside here. Useful when using a non-browser environment, such as Node.js or React Native. - largeMessageStrategyDefault is "default"
How to handle WebSocket messages that are larger than the maximum message size. Can be set to one of these values:
"default"
Don’t send anything, but log the error to the console and notify useErrorListener."split"
Break the message up into chunks each of which is smaller than the maximum message size. Beware that using"split"
will sacrifice atomicity of changes! Depending on your use case, this may or may not be problematic."experimental-fallback-to-http"
Try sending the update over HTTP instead of WebSockets (experimental).
- unstable_streamDatabooleanDefault is false
Experimental. Stream the initial Storage content over HTTP, instead of waiting for a large initial WebSocket message to be sent from the server.
LiveblocksProvider with public key
When creating a client with a public key, you don’t need to set up an authorization endpoint. We only recommend using a public key when prototyping, or on public landing pages, as it makes it possible for end users to access any room’s data. You should instead use an auth endpoint.
LiveblocksProvider with auth endpoint
If you are not using a public key, you need to set up your own authEndpoint
.
Please refer to our Authentication guide.
LiveblocksProvider with auth endpoint callback
If you need to add additional headers or use your own function to call your
endpoint, authEndpoint
can be provided as a custom callback. You should return
the token created with
Liveblocks.prepareSession
or liveblocks.identifyUser
,
learn more in authentication guide.
room
is the room ID that the user is connecting to. When using
Notifications, room
can be undefined
, as the client is requesting a token that grants access to
multiple rooms, rather than a specific room.
Fetch your endpoint
Here’s an example of fetching your API endpoint at /api/liveblocks-auth
within
the callback.
Token details
You should return the token created with
Liveblocks.prepareSession
or liveblocks.identifyUser
.
These are the values the functions can return.
- A valid token, it returns a
{ "token": "..." }
shaped response. - A token that explicitly forbids access, it returns an
{ "error": "forbidden", "reason": "..." }
shaped response. If this is returned, the client will disconnect and won't keep trying to authorize.
Any other error will be treated as an unexpected error, after which the client will retry the request until it receives either 1. or 2.
WebSocket throttle
By default, the client throttles the WebSocket messages sent to one every 100
milliseconds, which translates to 10 updates per second. It’s possible to
override that configuration with the throttle
option with a value between 16
and 1000
milliseconds.
This option is helpful for smoothing out realtime animations in your application, as you can effectively increase the framerate without using any interpolation. Here are some examples with their approximate frames per second (FPS) values.
Prevent users losing unsaved changes
Liveblocks usually synchronizes milliseconds after a local change, but if a user
immediately closes their tab, or if they have a slow connection, it may take
longer for changes to synchronize. Enabling preventUnsavedChanges
will stop
tabs with unsaved changes closing, by opening a dialog that warns users. In
usual circumstances, it will very rarely trigger.
More specifically, this option triggers when:
- There are unsaved changes after calling any hooks or methods, in all of our products.
- There are unsaved changes in a Text Editor.
- There’s an unsubmitted comment in the Composer.
- The user has made changes and is currently offline.
Internally, this option uses the beforeunload event.
Lost connection timeout
If you’re connected to a room and briefly lose connection, Liveblocks will reconnect automatically and quickly. However, if reconnecting takes longer than usual, for example if your network is offline, then the room will emit an event informing you about this.
How quickly this event is triggered can be configured with the
lostConnectionTimeout
setting, and it takes a number in milliseconds.
lostConnectionTimeout
can be set between 1000
and 30000
milliseconds. The
default is 5000
, or 5 seconds.
You can listen to the event with useLostConnectionListener
. Note that this
also affects when others
are reset to an empty array after a disconnection.
This helps prevent temporary flashes in your application as a user quickly
disconnects and reconnects. For a demonstration of this behavior, see our
connection status example.
Background keep-alive timeout
By default, Liveblocks applications will maintain an active WebSocket connection
to the Liveblocks servers, even when running in a browser tab that’s in the
background. However, if you’d prefer for background tabs to disconnect after a
period of inactivity, then you can use backgroundKeepAliveTimeout
.
When backgroundKeepAliveTimeout
is specified, the client will automatically
disconnect applications that have been in an unfocused background tab for at
least the specified time. When the browser tab is refocused, the client will
immediately reconnect to the room and synchronize the document.
backgroundKeepAliveTimeout
accepts a number in milliseconds—we advise using a
value of at least a few minutes, to avoid unnecessary disconnections.
resolveUsers
Comments and Text Editor store user IDs in its system, but no other user information. To display user information in Comments, Text Editor, and Notifications components, such as a user’s name or avatar, you need to resolve these IDs into user objects. This function receives a list of user IDs and you should return a list of user objects of the same size, in the same order.
User IDs are automatically resolved in batches with a maximum of 50 users per batch to optimize performance and prevent overwhelming your user resolution function.
The name and avatar you return are rendered in
Thread
components.
User objects
The user objects returned by the resolver function take the shape of
UserMeta["info"]
, which contains name
and avatar
by default. These two
values are optional, though if you’re using the
Comments default components,
they are necessary. Here’s an example of userIds
and the exact values
returned.
You can also return custom information, for example, a user’s color
:
Accessing user data
You can access any values set within resolveUsers
with the
useUser
hook.
resolveRoomsInfo
When using
Notifications with
Comments, room IDs will be used to
contextualize notifications (e.g. “Chris mentioned you in room-id”) in the
InboxNotification
component. To replace room IDs with more fitting names (e.g. document names,
“Chris mentioned you in Document A”), you can provide a resolver function to
the resolveRoomsInfo
option in LiveblocksProvider
.
This resolver function will receive a list of room IDs and should return a list of room info objects of the same size and in the same order.
In addition to the room’s name, you can also provide a room’s URL as the url
property. If you do so, the
InboxNotification
component will automatically use it. It’s possible to use an inbox
notification’s roomId
property to construct a room’s URL directly in React and
set it on
InboxNotification
via href
, but the room ID might not be enough for you to construct the URL ,
you might need to call your backend for example. In that case, providing it via
resolveRoomsInfo
is the preferred way.
resolveGroupsInfo
When using group mentions with Comments
and Text Editor, group IDs will be used
instead of user IDs. Similarly to
resolveUsers
, you can provide a resolver
function to the resolveGroupsInfo
option in
LiveblocksProvider
to assign information like names and
avatars to group IDs.
Accessing group info
You can access any values set within resolveGroupsInfo
with the
useGroupInfo
hook.
resolveMentionSuggestions
To enable creating mentions in Comments
and Text Editor, you can provide a
resolver function to the resolveMentionSuggestions
option in
LiveblocksProvider
. These mentions will be displayed in
the Composer
component and
in text editors.
This resolver function will receive the mention currently being typed (e.g. when
writing “@jane”, text
will be jane
) and should return a list of user IDs
matching that text. This function will be called every time the text changes but
with some debouncing.
Group mentions
To support group mentions in Comments and Text Editor, you can return a list of mention objects instead of user IDs to suggest a mix of user and group mentions.
The mention objects specify which kind of mention it is, the ID to mention (user ID or group ID), etc.
LiveblocksProvider for Node.js
To use @liveblocks/client
in Node.js, you need to provide WebSocket
and
fetch
polyfills. As polyfills, we recommend installing ws
and
node-fetch
.
Then, pass them to the LiveblocksProvider
polyfill option as below.
Note that node-fetch
v3+
does not support CommonJS.
If you are using CommonJS, downgrade node-fetch
to v2.
LiveblocksProvider for React Native
To use @liveblocks/client
with React Native, you
need to add an atob
polyfill. As a polyfill, we recommend installing
base-64
.
Then you can pass the decode
function to our atob
polyfill option when you
create the client.
createLiveblocksContext
Creates a LiveblocksProvider
and a set of typed hooks. Note that any
LiveblocksProvider
created in this way takes no props, because it uses
settings from the client
instead. We recommend using it in
liveblocks.config.ts
and re-exporting your typed hooks like below.
While createRoomContext
offers APIs for interacting with
rooms (e.g. Presence, Storage, and Comments),
createLiveblocksContext
offers APIs for
interacting with Liveblocks features that are not tied to a specific room (e.g.
Notifications).
useClient
Returns the client
of the
nearest LiveblocksProvider
above in the React component tree.
useErrorListener
Listen to potential Liveblocks errors. Examples of errors include room
connection errors, errors creating threads, and errors deleting notifications.
Each error has a message
string, and a context
object which has different
values for each error type. context
always contains an error type
and
roomId
.
There are many different errors, and each can be handled separately by checking
the value of error.context.type
. Below we’ve listed each error and the context
it provides.
AI Copilots
useAiChats
Returns a paginated list of AI chats created by the current user. Initially fetches the latest 50 chats.
List the user’s chats and switch between them
You can use the AiChat
component alongside the hook to create an AI chat switcher. Below, each button
displays the chat’s automatically generated title, and chats can be deleted with
useDeleteAiChat
.
Querying chats
It’s possible to return chats that match a certain query with the query
option. You can filter by metadata values, or by the absence of a metadata key.
Returned chats must match the entire query.
Pagination
By default, the useAiChats
hook returns up to 50 chats. To fetch more, the
hook provides additional fields for pagination, similar to useThreads
.
hasFetchedAll
indicates whether all available AI chats have been fetched.fetchMore
loads up to 50 more AI chats, and is always safe to call.isFetchingMore
indicates whether more AI chats are being fetched.fetchMoreError
returns error statuses resulting from fetching more.
Pagination example
The following example demonstrates how to use the fetchMore
function to
implement a “Load More” button, which fetches additional AI chats when clicked.
The button is disabled while fetching is in progress.
Error handling
Error handling is another important aspect to consider when using the
useAiChats
hook. The error
and fetchMoreError
fields provide information
about any errors that occurred during the initial fetch or subsequent fetch
operations, respectively. You can use these fields to display appropriate error
messages to the user and implement retry mechanisms if needed.
The following example shows how to display error messages for both initial loading errors and errors that occur when fetching more inbox notifications.
👉 A Suspense version of this hook is also available, which will never
return a loading state and will throw error to the nearest ErrorBoundary
if
initial load failed.
useAiChat
Returns information about an AI chat, for example its title and metadata. Titles are automatically generated from the content of the first user message in a chat, and the AI’s response.
👉 A Suspense version of this hook is also available, which will never return a loading state and will throw error to the nearest ErrorBoundary if initial load failed.
Displaying a default title
If chat.title
is undefined
after an isLoading
check, that means the title
has not been set yet. You can display a default title in this case, and the
title will be displayed once generated.
useCreateAiChat
Returns a function that creates an AI chat.
You can optionally set a title
, which prevents the AI auto-generating a title
from the first messages. Additionally, you can choose to set custom metadata
for the chat, strings or arrays of strings.
useDeleteAiChat
Returns a function that deletes an AI chat by its ID. Use in conjunction with
useAiChats
to
loop through each chat
and add a delete button.
useSendAiMessage
Returns a function that sends a message to an AI chat, indentified by its ID. Useful for creating suggestions in empty inside chats and sending messages on behalf of the user.
You can set options related to the message being sent, such as the copilot ID to use. Alternatively, you can also override or complete the hook’s options when calling the function by passing an object to it.
You can also pass the chat ID in the function rather than the hook. This is particularly helpful in dynamic scenarios where the chat ID might not be known when calling the hook.
If needed (e.g. using the message’s ID), you can also access the newly created message.
useAiChatMessages
Returns a list of every message in an AI chat, identified by its ID. Updates in realtime using WebSockets.
RegisterAiKnowledge
Adds knowledge to all AI features on the page. AI will understand the information you pass, and will answer questions or call tools based on it. This is particularly helpful for passing user info, app state, and other small contextual knowledge.
Each knowledge source has a description
string, and a value
which can be
either a string, object, array or JSON-serializable value that provides
meaningful context for your use case. The AI uses this data, along with the
accompanying description, to better understand and respond to user queries or
perform actions based on the supplied context. These components can be placed
anywhere in your app, so long as they’re under
LiveblocksProvider
.
Pass in assorted context
Passing the AI context about the current datetime, the user’s language, the page the user’s visiting, and navigable pages on your website, is an effective method for improving your chat’s replies.
When building your app, it’s worth considering which app-specific context will be helpful, for example the user’s payment plan, or a list of their projects.
Pass in user data from your auth provider
You can pass in knowledge from your auth provider, for example with
useUser
from
Clerk. You can tell AI that the state is loading in a
simple string, and it will understand.
Pass in assorted data from fetching hooks
You can pass in knowledge from data fetching hooks such as with
useSWR
from
SWR. You can tell AI that the state is loading in a
simple string, and it will understand.
Pass in text editor document data
You can pass in knowledge from your text editor, for example when using Liveblocks Tiptap.
As well as editor.getHTML()
, editor.getJSON()
and editor.getText()
are
also available when using Tiptap. Its worth trying them all, in case your AI
model understands one of them better than the others.
Pass in comment data
You can also pass in knowledge from your custom Liveblocks Comments app with
useThreads
. This way, your
AI chat will understand the context in the current room.
Pass in storage data
You can also pass in knowledge from your custom Liveblocks storage app with
useStorage
. This way, your
AI chat will understand the context in the current room.
Props
- descriptionstring
A clear description of what this knowledge represents. This helps the AI understand the context and relevance of the provided information.
- valueJson
The actual data or information to share with the AI. Can be a string, object, array, or any JSON-serializable information that offers context relevant to your application or user.
- idstring
Optional unique identifier for this knowledge source. If provided, subsequent updates with the same ID will replace the previous knowledge.
RegisterAiTool
Registers a tool that can be used by AI chats on the page. Tools allow AI to autonomously run actions, render custom components, and show confirmation or human-in-the-loop UIs within the chat.
defineAiTool
is used to
create a tool definition, and you can supply parameters
as a
JSON Schema that the AI can fill in. If you supply
an execute
function the AI will call it. render
is used to show UI inside
the chat. Below is an example of a tool that lets AI get the current weather in
a given location, then renders a compoennt in the chat.
Tool that sends a toast notification
The following snippet shows a tool that lets AI send a toast notification with
Sonner, then adds a message in the chat with
AiTool
, letting the user
know that a toast was sent.
Scoping a tool to a specific chat
Tools can be scoped to specific chats by providing a chatId
prop. When scoped,
the tool will only be available to that specific chat.
Props
- namestring
Unique name for the tool. This is used internally to identify and manage the tool.
- toolAiOpaqueToolDefinition
The tool definition created with
defineAiTool
. - chatIdstring
Optional chat ID to scope this tool to a specific chat. If provided, the tool will only be available to that chat.
- enabledboolean
Whether this tool should be enabled. When set to
false
, the tool will not be made available to the AI copilot for any new/future chat messages, but will still allow existing tool invocations to be rendered that are part of the historic chat record. When provided as a prop toRegisterAiTool
, it will take precedence over the value of the tool’senabled
value indefineAiTool
.
Room
RoomProvider
Makes a Room
available in the component hierarchy below. Joins the room
when the component is mounted, and automatically leaves the room when the
component is unmounted. When using
Sync Datastore, initial Presence values for
each user, and Storage values for the room can be set.
- idstringRequired
The unique ID for the current room.
RoomProvider
will join this room when it loads. If the room doesn’t exist already it will automatically create the room first then join. After setting up authentication for your app, it can helpful to decide on a naming pattern for your room IDs. - initialPresenceJsonObject
The initial Presence of the user entering the room. Each user has their own presence, and this is readable for all other connected users. A user’s Presence resets every time they disconnect. This object must be JSON-serializable. This value is ignored after the first render. Learn more.
- initialStorageLsonObject
The initial Storage structure for the room when it’s joined for the first time. This is only set a single time, when the room has not yet been populated. This object must contain conflict-free live structures. This value is ignored after the first render, and if Storage for the current room has already been created. Learn more.
- autoConnectbooleanDefault is true
Whether the room immediately connects to Liveblocks servers. This value is ignored after the first render.
Setting initial Presence
Presence is used for storing temporary user-based values, such as a user’s
cursor coordinates, or their current selection. Each user has their own
presence, and this is readable for all other connected users. Set your initial
Presence value by using initialPresence
.
Each user’s Presence resets every time they disconnect, as this is only meant
for temporary data. Any JSON-serializable object is allowed (the JsonObject
type).
Setting initial Storage
Storage is used to store permanent data that’s used in your application, such as
shapes on a whiteboard, nodes on a flowchart, or text in a form. The first time
a room is entered, you can set an initial value by using initialStorage
.
initialStorage
is only read and set a single time, unless a new top-level
property is added.
If a new top-level property is added to initialStorage
, the next time a user
connects, the new property will be created. Other properties will be unaffected.
Any
conflict-free live structures
and JSON-serializable objects are allowed (the LsonObject
type).
createRoomContext
Creates a RoomProvider
and a set of typed hooks to use in your app. Note
that any RoomProvider
created in this way does not need to be nested in
LiveblocksProvider
, as it already has access to the client
. We generally
recommend typing your app using the newer method instead. When using
createRoomContext
it can be helpful to use it in liveblocks.config.ts
and
re-export your typed hooks as below.
Suspense with createRoomContext
To use the React suspense version of our hooks with createRoomContext
, you can
export from the suspense
property instead.
Typing createRoomContext
To type your hooks, you can pass multiple different types to
createRoomContext
. A full explanation is in the code snippet below.
useRoom
Returns the Room
of the nearest RoomProvider
above in the React
component tree.
Will throw when used outside of a RoomProvider
. If you don’t want this
hook to throw when used outside of a Room context (for example to write
components in a way that they can be used both inside and outside of a
Liveblocks room), you can use the { allowOutsideRoom }
option:
useIsInsideRoom
Returns a boolean, true
if the hook was called inside a
RoomProvider
context, and
false
otherwise.
Displaying different components inside rooms
useIsInsideRoom
is helpful for rendering different components depending on
whether they’re inside a room, or not. One example is a header component that
only displays a live avatar stack when users are connected to the room.
Here’s how the example above would render in three different
LiveblocksProvider
and RoomProvider
contexts.
useStatus
Returns the current WebSocket connection status of the room, and will re-render your component whenever it changes.
The possible value are: initial
, connecting
, connected
, reconnecting
, or
disconnected
.
useSyncStatus
Returns the current synchronization status of Liveblocks, and will re-render your component whenever it changes. This includes any part of Liveblocks that may be synchronizing local changes to the server, including (any room’s) Storage, text editors, threads, or notifications.
A { smooth: true }
option is also available, which prevents quick changes
between states, making it ideal for
rendering a synchronization badge in your app.
👉 A Suspense version of this hook is also available.
Display a synchronization badge
Passing { smooth: true }
prevents the status changing from "synchronizing"
to "synchronized"
until 1 second has passed after the final change. This means
it’s ideal for rendering a synchronization status badge, as it won’t flicker in
a distracting manner when changes are made in quick succession.
Prevent users losing unsaved changes
Liveblocks usually synchronizes milliseconds after a local change, but if a user
immediately closes their tab, or if they have a slow connection, it may take
longer for changes to synchronize. Enabling preventUnsavedChanges
will stop
tabs with unsaved changes closing, by opening a dialog that warns users. In
usual circumstances, it will very rarely trigger.
More specifically, this option triggers when:
- There are unsaved changes after calling any hooks or methods, in all of our products.
- There are unsaved changes in a Text Editor.
- There’s an unsubmitted comment in the Composer.
- The user has made changes and is currently offline.
Internally, this option uses the beforeunload event.
useOthersListener
Calls the given callback when an “others” event occurs. Possible event types are:
enter
– A user has entered the room.leave
– A user has left the room.reset
– The others list has been emptied. This is the first event that occurs when the room is entered. It also occurs when you’ve lost connection to the room.update
– A user’s presence data has been updated.
useLostConnectionListener
Calls the given callback in the exceptional situation that a connection is lost and reconnecting does not happen quickly enough.
This event allows you to build high-quality UIs by warning your users that the app is still trying to re-establish the connection, for example through a toast notification. You may want to take extra care in the mean time to ensure their changes won’t go unsaved.
When this happens, this callback is called with the event lost
. Then, once the
connection restores, the callback will be called with the value restored
. If
the connection could definitively not be restored, it will be called with
failed
(uncommon).
The lostConnectionTimeout
client option will determine how quickly this
event will fire after a connection loss (default: 5 seconds).
Automatically unsubscribes when the component is unmounted.
For a demonstration of this behavior, see our connection status example.
Presence
useMyPresence
Return the presence of the current user, and a function to update it. Automatically subscribes to updates to the current user’s presence.
Note that the updateMyPresence
setter function is different to the setter
function returned by React’s useState
hook. Instead, you can pass a partial
presence object to updateMyPresence
, and any changes will be merged into the
current presence. It will not replace the entire presence object.
This is roughly equal to:
updateMyPresence
accepts an optional argument to add a new item to the
undo/redo stack. See room.history
for more information.
useUpdateMyPresence
Returns a setter function to update the current user’s presence.
Use this if you don’t need the current user’s presence in your component, but
you need to update it (e.g. live cursor). It’s better to use
useUpdateMyPresence
because it won’t subscribe your component to get
rerendered when the presence updates.
Note that the updateMyPresence
setter function is different to the setter
function returned by React’s useState
hook. Instead, you can pass a partial
presence object to updateMyPresence
, and any changes will be merged into the
current presence. It will not replace the entire presence object.
updateMyPresence
accepts an optional argument to add a new item to the
undo/redo stack. See room.history
for more information.
useSelf
Returns the current user once it is connected to the room, and automatically subscribes to updates to the current user.
The benefit of using a selector is that it will only update your component if that particular selection changes. For full details, see how selectors work.
👉 A Suspense version of this hook is also available, which will never
return null
.
useOthers
Extracts data from the list of other users currently in the same Room, and automatically subscribes to updates on the selected data. For full details, see how selectors work.
The others
argument to the useOthers
selector function is an immutable
array of Users.
👉 A Suspense version of this hook is also available, which will never
return null
.
One caveat with this API is that selecting a subset of data for each user
quickly becomes tricky. When you want to select and get updates for only a
particular subset of each user’s data, we recommend using the
useOthersMapped
hook instead, which is optimized for this use case.
When called without arguments, returns the user list and updates your component whenever anything in it changes. This might be way more often than you want!
useOthersMapped
Extract data using a selector for every user in the room, and subscribe to all changes to the selected data. A Suspense version of this hook is also available.
The key difference with useOthers
is that the selector (and the optional
comparison function) work at the item level, like doing a .map()
over the
others array.
Returns an array where each item is a pair of [connectionId, data]
. For
pragmatic reasons, the results are keyed by the connectionId
, because in most
cases you’ll want to iterate over the results and draw some UI for each, which
in React requires you to use a key={connectionId}
prop.
useOthersConnectionIds
Returns an array of connection IDs (numbers), and rerenders automatically when
users join or leave. This hook is useful in particular in combination with the
useOther
(singular) hook, to implement high-frequency rerendering of
components for each user in the room, e.g. cursors. See the useOther
(singular) documentation below for a full usage example.
Roughly equivalent to:
👉 A Suspense version of this hook is also available.
useOther
Extract data using a selector for one specific user in the room, and subscribe to all changes to the selected data. A Suspense version of this hook is also available.
The reason this hook exists is to enable the most efficient rerendering model for high-frequency updates to other’s presences, which is the following structure:
- Makes sure this whole component tree will never rerender beyond the first time.
- Makes sure the parent component only rerenders when users join/leave.
- Makes sure each cursor remains associated to the same connection.
- Makes sure each cursor rerenders whenever its data changes only.
👉 A Suspense version of this hook is also available, which will never
return null
.
Broadcast
useBroadcastEvent
Returns a callback that lets you broadcast custom events to other users in the room.
useEventListener
Listen to custom events sent by other people in the room via
useBroadcastEvent
. Provides the event
along with the connectionId
of
the user that sent the message. If an event was sent from the
Broadcast to a room
REST API, connectionId
will be -1
.
The user
property will indicate which User instance sent the message. This
will typically be equal to one of the others in the room, but it can also be
null
in case this event was broadcasted from the server, using the
Broadcast Event API.
Automatically unsubscribes when the component is unmounted.
Storage
Each room contains Storage, a conflict-free data store that multiple users can edit at the same time. When users make edits simultaneously, conflicts are resolved automatically, and each user will see the same state. Storage is ideal for storing permanent document state, such as shapes on a canvas, notes on a whiteboard, or cells in a spreadsheet.
Data structures
Storage provides three different conflict-free data structures, which you can use to build your application. All structures are permanent and persist when all users have left the room, unlike Presence which is temporary.
-
LiveObject
- Similar to JavaScript object. Use this for storing records with fixed key names and where the values don’t necessarily have the same types. For example, aPerson
with aname: string
and anage: number
field. If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner. -
LiveList
- An ordered collection of items synchronized across clients. Even if multiple users add/remove/move elements simultaneously, LiveList will solve the conflicts to ensure everyone sees the same collection of items. -
LiveMap
- Similar to a JavaScript Map. Use this for indexing values that all have the same structure. For example, to store an index ofPerson
values by their name. If multiple users update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.
Typing Storage
To type the Storage values you receive, make sure to set your Storage
type.
You can then set an initial value in RoomProvider
.
The type received in your Storage will match the type passed. Learn more under typing your data.
useStorage
will return an immutable copy of the data, for example a
LiveList
is converted to an array
, which makes it easy to render.
Nesting data structures
All Storage data structures can be nested, allowing you to create complex trees of conflict-free data.
Here’s an example of setting initialStorage
for this type.
useStorage
Extracts data from Liveblocks Storage state and automatically subscribes to updates to that selected data. For full details, see how selectors work.
The root
argument to the useStorage
selector function is an immutable copy
of your entire Liveblocks Storage tree. Think of it as the value you provided in
the initialStorage
prop at the RoomProvider
level, but then
(recursively) converted to their “normal” JavaScript equivalents (objects,
arrays, maps) that are read-only.
From that immutable root
, you can select or compute any value you like. Your
component will automatically get rerendered if the value you return differs from
the last rendered value.
This hook returns null
while storage is still loading. To avoid that, use the
Suspense version.
useHistory
Returns the room’s history. See Room.history
for more information.
useUndo
Returns a function that undoes the last operation executed by the current client. It does not impact operations made by other clients.
useRedo
Returns a function that redoes the last operation executed by the current client. It does not impact operations made by other clients.
useCanUndo
Returns whether there are any operations to undo.
useCanRedo
Returns whether there are any operations to redo.
useMutation
Creates a callback function that lets you mutate Liveblocks state.
To make the example above more flexible and work with any color, you have two options:
- Close over a local variable and adding it to the dependency array, or
- Have it take an extra callback parameter.
Both are equally fine, just a matter of preference.
With dependency arrays
With extra callback parameters
Alternatively, you can add extra parameters to your callback function:
Depending on current presence
For convenience, the mutation context also receives self
and others
arguments, which are immutable values reflecting the current Presence state,
in case your mutation depends on it.
For example, here’s a mutation that will delete all the shapes selected by the current user.
Mutations are automatically batched, so when using useMutation
there’s no need
to use useBatch
, or call room.batch()
manually.
ESLint rule
If you are using ESLint in your project, and are using
the React hooks plugin,
we recommend to add a check for "additional hooks", so that it will also check
the dependency arrays of your useMutation
calls:
Comments
useThreads
Returns a paginated list of threads within the current room. Initially fetches the latest 50 threads.
Use the Thread
component to
render the latest 50 threads with our default UI.
Querying threads
It’s possible to return threads that match a certain query with the query
option. You can filter threads based on their resolved status, and metadata.
Additionally, you can filter for metadata strings that being with certain
characters using startsWith
. Returned threads match the entire query.
Pagination
By default, the useThreads
hook returns up to 50 threads. To fetch more, the
hook provides additional fields for pagination, similar to
useInboxNotifications
.
hasFetchedAll
indicates whether all available threads have been fetched.fetchMore
loads up to 50 more threads, and is always safe to call.isFetchingMore
indicates whether more threads are being fetched.fetchMoreError
returns error statuses resulting from fetching more.
Pagination example
The following example demonstrates how to use the fetchMore
function to
implement a “Load More” button, which fetches additional threads when clicked.
The button is disabled while fetching is in progress.
Error handling
Error handling is another important aspect to consider when using the
useThreads
hook. The error
and fetchMoreError
fields provide information
about any errors that occurred during the initial fetch or subsequent fetch
operations, respectively. You can use these fields to display appropriate error
messages to the user and implement retry mechanisms if needed.
The following example shows how to display error messages for both initial loading errors and errors that occur when fetching more threads.
Avoid scrolling to a comment
By default, scrollOnLoad
, is enabled. This options scrolls to a comment if the
URL’s hash is set to a comment ID (e.g.
https://example.com/my-room#cm_nNJs9sb...
), the page will scroll to that
comment once the threads are loaded. To avoid scrolling to a comment, set
scrollOnLoad
to false
.
👉 A Suspense version of this hook is also available, which will never
return a loading state and will throw error to the nearest ErrorBoundary
if
initial load failed.
useCreateThread
Returns a function that creates a thread with an initial comment, and optionally some metadata.
useDeleteThread
Returns a function that deletes a thread and all its associated comments by ID. Only the thread creator can delete the thread.
useEditThreadMetadata
Returns a function that edits a thread’s metadata.
useMarkThreadAsResolved
Returns a function that marks a thread as resolved.
useMarkThreadAsUnresolved
Returns a function that marks a thread as unresolved.
useMarkThreadAsRead
Returns a function that marks a thread as read.
useThreadSubscription
Returns the subscription status of a thread, methods to update it, and when the thread was last read. The subscription status affects whether the current user receives inbox notifications when new comments are posted.
subscribe
and unsubscribe
work similarly to
useSubscribeToThread
and
useUnsubscribeFromThread
, but they only affect
the current thread.
useSubscribeToThread
Returns a function that subscribes the current user to a thread, meaning they will receive inbox notifications when new comments are posted.
Subscribing will replace any existing subscription for the current thread set at room-level. This value can also be overridden by a room-level call that is run afterwards.
useUnsubscribeFromThread
Returns a function that unsubscribes the current user from a thread, meaning they will no longer receive inbox notifications when new comments are posted.
Unsubscribing will replace any existing subscription for the current thread set at room-level. This value can also be overridden by a room-level call that is run afterwards.
useCreateComment
Returns a function that adds a comment to a thread.
useEditComment
Returns a function that edits a comment’s body.
useDeleteComment
Returns a function that deletes a comment. If it is the last non-deleted comment, the thread also gets deleted.
useAddReaction
Returns a function that adds a reaction to a comment. Can be used to create an emoji picker or emoji reactions.
useRemoveReaction
Returns a function that removes a reaction from a comment. Can be used to create an emoji picker or emoji reactions
useAttachmentUrl
Returns a presigned URL for an attachment by its ID.
👉 A Suspense version of this hook is also available, which will never return a loading state and will throw when there’s an error.
Notifications
useInboxNotifications
Returns a paginated list of inbox notifications for the current user. Initially fetches the latest 50 items. Inbox notifications are project-based, meaning notifications from outside the current room are received.
Use the
InboxNotification
component to render the latest 50 inbox notifications with our default UI.
Querying inbox notifications
It’s possible to return inbox notifications that match a certain query with the
query
option. You can filter inbox notifications based on their associated
room ID or kind.
Pagination
By default, the useInboxNotifications
hook returns up to 50 notifications. To
fetch more, the hook provides additional fields for pagination, similar to
useThreads
.
hasFetchedAll
indicates whether all available inbox notifications have been fetched.fetchMore
loads up to 50 more notifications, and is always safe to call.isFetchingMore
indicates whether more notifications are being fetched.fetchMoreError
returns error statuses resulting from fetching more.
Pagination example
The following example demonstrates how to use the fetchMore
function to
implement a “Load More” button, which fetches additional inbox notifications
when clicked. The button is disabled while fetching is in progress.
Error handling
Error handling is another important aspect to consider when using the
useInboxNotifications
hook. The error
and fetchMoreError
fields provide
information about any errors that occurred during the initial fetch or
subsequent fetch operations, respectively. You can use these fields to display
appropriate error messages to the user and implement retry mechanisms if needed.
The following example shows how to display error messages for both initial loading errors and errors that occur when fetching more inbox notifications.
👉 A Suspense version of this hook is also available, which will never return a loading state and will throw error to the nearest ErrorBoundary if initial load failed.
useUnreadInboxNotificationsCount
Returns the number of unread inbox notifications for the current user.
Querying unread inbox notifications count
It’s possible to return the count of unread inbox notifications that match a
certain query with the query
option. You can filter unread inbox notifications
count based on their associated room ID or kind.
👉 A Suspense version of this hook is also available, which will never return a loading state and will throw when there’s an error.
useMarkInboxNotificationAsRead
Returns a function that marks an inbox notification as read for the current user.
useMarkAllInboxNotificationsAsRead
Returns a function that marks all of the current user‘s inbox notifications as read.
useDeleteInboxNotification
Returns a function that deletes an inbox notification for the current user.
useDeleteAllInboxNotifications
Returns a function that deletes all of the current user‘s inbox notifications.
useInboxNotificationThread
Returns the thread associated with a "thread"
inbox notification.
It can only be called with IDs of "thread"
inbox notifications, so we
recommend only using it
when customizing the rendering
or in other situations where you can guarantee the kind of the notification.
useRoomSubscriptionSettings
Returns the user’s subscription settings for the current room and a function to
update them. Updating this setting will change which
inboxNotifications
the current user receives in the
current room.
For "threads"
, these are the three possible values that can be set:
"all"
Receive notifications for every activity in every thread."replies_and_mentions"
Receive notifications for mentions and threads you’re participating in."none"
No notifications are received.
For "textMentions"
, these are the two possible values that can be set:
"mine"
Receive notifications for mentions of you."none"
No notifications are received.
useUpdateRoomSubscriptionSettings
Returns a function that updates the user’s notification settings for the current
room. Updating this setting will change which
inboxNotifications
the current user receives in the
current room.
For "threads"
, these are the three possible values that can be set:
"all"
Receive notifications for every activity in every thread."replies_and_mentions"
Receive notifications for mentions and threads you’re participating in."none"
No notifications are received.
For "textMentions"
, these are the two possible values that can be set:
"mine"
Receive notifications for mentions of you."none"
No notifications are received.
Works the same as updateSettings
in
useRoomSubscriptionSettings
.
Replacing individual thread subscriptions
Subscribing will replace any existing thread subscriptions in the current room. This value can also be overridden by a room-level call that is run afterwards.
useNotificationSettings
Returns the user’s notification settings in the current project, in other words
which notification webhook events
will be sent for the current user. Notification settings are project-based,
which means that settings
and updateSettings
are for the current user’s
settings in every room. Useful for creating a
notification settings panel.
A user’s initial settings are set in the dashboard, and different kinds should
be enabled there. If no kind is enabled on the current channel, null
will be
returned. For example, with the email channel:
Updating notification settings
The updateSettings
function can be used to update the current user’s
notification settings, changing their settings for every room in the project.
Each notification kind
must first be enabled on your project’s notification
dashboard page before settings can be used.
Subscribing will replace any existing thread subscriptions in the current room. This value can also be overridden by a room-level call that is run afterwards.
Error handling
Error handling is an important aspect to consider when using the
useNotificationSettings
hook. The error
fields provides information about
any error that occurred during the fetch operation.
The following example shows how to display error messages for both initial loading errors and errors that occur when fetching more inbox notifications.
👉 A Suspense version of this hook is also available, which will never
return a loading state and will throw and error to the nearest ErrorBoundary
if initial load failed.
useUpdateNotificationSettings
Returns a function that updates user’s notification settings, which affects
which notification webhook events
will be sent for the current user. Notification settings are project-based,
which means that updateSettings
modifies the current user’s settings in every
room. Each notification kind
must first be enabled on your project’s
notification dashboard page before settings can be used. Useful for creating a
notification settings panel.
Works the same as updateSettings
in
useNotificationSettings
. You can pass a partial
object, or many settings at once.
Version History
useHistoryVersions
Returns the versions of the room. See Version History Components for more information on how to display versions.
Miscellaneous
useUser
Returns user info from a given user ID.
👉 A Suspense version of this hook is also available, which will never return a loading state and will throw when there’s an error.
useRoomInfo
Returns room info from a given room ID.
👉 A Suspense version of this hook is also available, which will never return a loading state and will throw when there’s an error.
useGroupInfo
Returns group info from a given group ID.
👉 A Suspense version of this hook is also available, which will never return a loading state and will throw when there’s an error.
TypeScript
Typing your data
It’s possible to have automatic types flow through your application by defining
a global Liveblocks
interface. We recommend doing this in a
liveblocks.config.ts
file in the root of your app, so it’s easy to keep track
of your types. Each type (Presence
, Storage
, etc.), is optional, but it’s
recommended to make use of them.
Here are some example values that might be used.
Typing with createRoomContext
Before Liveblocks 2.0, it was recommended to create your hooks using
createRoomContext
, and manually pass your types to this function. This is
no longer the recommended method for setting up Liveblocks,
but it can still be helpful, for example you can use createRoomContext
multiple times to create different room types, each with their own correctly
typed hooks.
To upgrade to Liveblocks 2.0 and the new typing system, follow the 2.0 migration guide.
Helpers
shallow
Compares two values shallowly. This can be used as the second argument to selector based functions to loosen the equality check:
The default way selector results are
compared is by checking referential equality (===
). If your selector returns
computed arrays (like in the example above) or objects, this will not work.
By passing shallow
as the second argument, you can “loosen” this check. This
is because shallow
will shallowly compare the members of an array (or values
in an object):
Please note that this will only do a shallow (one level deep) check. Hence the name. If you need to do an arbitrarily deep equality check, you’ll have to write a custom equality function or use a library like Lodash for that.
How selectors work
The concepts and behaviors described in this section apply to all of our
selector hooks: useStorage
, useSelf
, useOthers
,
useOthersMapped
, and useOther
(singular).
In a nutshell, the key behaviors for all selector APIs are:
Let’s go over these traits and responsibilities in the next few sections.
Selectors receive immutable data
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 example, suppose you have set up Storage in the typical way by setting
initialStorage
in your RoomProvider
to a tree that describes your app’s
data model using LiveList
, LiveObject
, and LiveMap
. The "root" argument
for your selector function, however, will receive an immutable and read-only
representation of that Storage tree, consisting of "normal" JavaScript
datastructures. This makes consumption much easier.
Internally, these read-only trees use a technique called structural sharing. This means that between rerenders, if nodes in the tree did not change, they will guarantee to return the same memory instance. Selecting and returning these nodes directly is therefore safe and considered a good practice, because they are stable references by design.
Selectors return arbitrary values
Selectors you write can return any value. You can use it to “just” select nodes from the root tree (first two examples above), but you can also return computed values, like in the last two examples.
Selector functions must return a stable result
One important rule is that selector functions must return a stable result to be efficient. This means calling the same selector twice with the same argument should return two results that are referentially equal. Special care needs to be taken when filtering or mapping over arrays, or when returning object literals, because those operations create new array or object instances on every call (the reason why is detailed in the next section).
Examples of stable results
- ✅
(root) => root.animals
is stable Liveblocks guarantees this. All nodes in the Storage tree are stable references as long as their contents don’t change.
- ️️⚠️
(root) => root.animals.map(...)
is not stable Because
.map()
creates a new array instance every time. You’ll need to useshallow
here.- ✅
(root) => root.animals.map(...).join(", ")
is stable Because
.join()
ultimately returns a string and all primitive values are always stable.
Use a shallow comparison if the result isn’t stable
If your selector function doesn’t return a stable result, it will lead to an
explosion of unnecessary rerenders. In most cases, you can use a shallow
comparison function to loosen the check:
If your selector function constructs complex objects, then a shallow
comparison may not suffice. In those advanced cases, you can provide your own
custom comparison function, or use _.isEqual
from Lodash.
Selectors auto-subscribe to updates
Selectors effectively automatically subscribe your components to updates to the selected or computed values. This means that your component will automatically rerender when the selected value changes.
Using multiple selector hooks within a single React component is perfectly fine. Each such hook will individually listen for data changes. The component will rerender if at least one of the hooks requires it. If more than one selector returns a new value, the component still only rerenders once.
Technically, deciding if a rerender is needed works by re-running your selector
function (root) => root.child
every time something changes inside Liveblocks
storage. Anywhere. That happens often in a busy multiplayer app! The reason why
this is still no problem is that even though root
will be a different value on
every change, root.child
will not be if it didn’t change (due to how
Liveblocks internally uses structural sharing).
Only once the returned value is different from the previously returned value, the component will get rerendered. Otherwise, your component will just remain idle.
Consider the case:
And the following timeline:
- First render,
root.animals
initially is["🦁", "🦊", "🐵"]
. - Then, something unrelated elsewhere in Storage is changed. In response to the
change,
root.animals
gets re-evaluated, but it still returns the same (unchanged) array instance. - Since the value didn’t change, no rerender is needed.
- Then, someone removes an animal from the list. In response to the change,
root.animals
gets re-evaluated, and now it returns["🦁", "🦊"]
. - Because the previous value and this value are different, the component will rerender, seeing the updated value.
Deprecated
useStorageStatus
Returns the current storage status of the room, and will re-render your
component whenever it changes. A { smooth: true }
option is also available,
which prevents quick changes between states, making it ideal for
rendering a synchronization badge in your app.
👉 A Suspense version of this hook is also available.
useBatch
Returns a function that batches Storage and Presence modifications made during the given function. Each modification is grouped together, which means that other clients receive the changes as a single message after the batch function has run. Every modification made during the batch is merged into a single history item (undo/redo).
Note that batch
cannot take an async
function.
useRoomNotificationSettings
Returns the user’s subscription settings for the current room and a function to
update them. Updating this setting will change which
inboxNotifications
the current user receives in the
current room.
useUpdateRoomNotificationSettings
Returns a function that updates the user’s subscription settings for the current
room. Updating this setting will change which
inboxNotifications
the current user receives in the
current room.