-
I do not maintain my state in LiveBlocks only. It is a React App, but I cannot use the LiveBlocks React library. Instead, I am approaching the use of LiveBlocks as a collaborative, intermediate state management component, in an end to end lifecycle. I am using @liveblocks/client to manage the local state, and WebHooks to keep the state synced with the server. My "Storage" is a pretty straightforward set of interfaces that are mostly external to the client application, meaning, I have to "trick" TypeScript into using them appropriately to be accepted as LiveStructures in the realm of Storage. My state object is called
Prior to an authenticated user requesting entry into a Room, we ensure the room has been created and initialized with the So far, I haven't been able to craft a LiveStructure which will provide me with this capability. First of all, my objects do not exptend Lson, nor can I make them Types. A workaround was shared in #1558, which partially got me over this hurdle. I thought the correct approach would be to turn my data structure into a LiveStructure manually, and upon doing so, changes anywhere in my structure would be synced as discreetly as possible. However, my initial approaches to do this have failed.
I thought if I created my own LiveStructure from scratch, I would achieve the reactivity and granularity which I wanted. I wrote this routine to navigate the data structure. export const livePlanData = (planData: PlanData): LiveObject<Storage> => {
const result = new LiveObject<Storage>({
segments: new LiveList<SerializableSegment>([]),
attributes: new LiveList<SerializableAttribute>([]),
});
const liveSegments = result.get('segments');
planData.segments.forEach((segment) => {
segment.attributes = new LiveObject(segment.attributes);
const liveSegment = new LiveObject<Segment & LsonObject>(
segment as unknown as Segment & LsonObject,
);
liveSegments.push(liveSegment as unknown as SerializableSegment);
});
const liveAttributes = result.get('attributes');
planData.attributes.forEach((attribute) => {
liveAttributes.push(attribute as unknown as SerializableAttribute);
});
return result;
}; However, when I update this data structure, I can see that the I expect that I am missing something. I expect the simplest Storage-based collaborative use case would allow a JSON object to be passed to Storage and have the entire data structure synchronized as efficiently as possible across all clients. Please let me know what I am missing. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
Hi James, Sorry this is a hassle!
This is not the way the const myList = new LiveList([
new LiveObject({a: 1}),
new LiveObject({b: 2}),
]);
myList.toImmutable(); // will be [{a: 1}, {b: 2}]
myList.get(0).set('c', 3); // will sync send this change over the wire to all clients
myList.toImmutable(); // will be [{a: 1, c: 3}, {b: 2}] on all clients As you can see, you can get an efficiently1 updated JSON version of your data from your Live tree. However, what you want seems to be the opposite: give us a new copy of a JSON tree, and we figure out the diff and make the necessary updates under the hood. This is unfortunately not something we have a generic API for yet. I tried explaining why this does not generically exists yet in this Discord message. Such API could look almost like this in the future: room.syncStorageTree(myInitialState);
/* time passes */
room.syncStorageTree(myUpdatedState); …and you don't even have to set up any Such API is close to how we do this in our Zustand/Redux packages (with the trade-offs mentioned in the Discord message that try to cater to 80% of all use cases), so it's definitely possible to build that using the existing building blocks. We're however not currently working on that ourselves. If you're looking for inspiration on how to build something like that yourself, you could take a look at the internals that our Zustand/Redux integrations use, and start from there. Footnotes
|
Beta Was this translation helpful? Give feedback.
-
Not a hassle - just trying to understand why the approach I am attempting doesn't match the performance expectation I see in the examples. Thanks for being patient. Based on this statement, I suppose I am working against your intended API.
It is true, I am looking for a way to update my apps current data model, using the live Storage object as the means of communicating changes between clients. I don't have any issues with applying the changes from a LiveObject to my local state (model), in a highly performant manner. The issue I am having is getting Liveblocks to sync these changes in a performant manner. If pseudo-modeled, my approach looks something like this:
The "apply" step for Client A above, is taking a change to the local model and applying it against the room's storage in a very granular fashion.
Following a storage update event, I perform the reverse action.
All I am missing from Liveblocks in this scenario is a fast update of the Storage on each collaborator after the The only thing I have tried which provides a performant sync is to enable, Questions
|
Beta Was this translation helpful? Give feedback.
-
If you've solved this part, I feel like you've done most of the hard work already.
This part should be simpler, and you should absolutely not have to patch your Storage tree on client B manually. If a Storage updated event happens, then calling Suppose you have a todo list with 3 items, and client A sets: // Client A
root.get('items').get(1).set('checked', true); Then on client B, this update will be the equivalent of: // Client B (before change)
prev = root.toImmutable()
// Client B (after change) will be
newState = root.toImmutable()
// Then the new state will be equivalent to:
// newState = { ...prev, items: [ prev.items[0], { ...prev.items[1], checked: true }, prev.items[2] ], other: prev.other } You mentioned you're using normal React, right? The simplest way to ensure your application re-renders correctly on Client B would be to use the In that case, I'd suggest using its implementation which ultimately is using a
Answers to your direct questions
See my answer above: not if you use the much simpler
I would need more detail to answer this. If every sub-tree of your application would subscribe to a deep update, then you'd have many subscriptions, which would lead to bad performance.
Yep, that's exactly the solution above. Not sure what the reason is you cannot use our React package, but we've efficiently solved this problem in the
It's not a pattern we want to recommend because of all the intricacies involved. We still consider it an implementation detail that can change.
Agreed, but as I mentioned earlier, there isn't a generic approach to this problem that works for all applications yet. Whatever your solution is to implement the diffing in Client A's side, there are deliberate choices being made in your implementation (i.e. what diff / how that diff gets produced), which isn't something we can generically provide in a way that fits all apps. We hear your feedback though, and we're discussing options internally. |
Beta Was this translation helpful? Give feedback.
If you've solved this part, I feel like you've done most of the hard work already.