Yjs Redux binding

I’m planning to write an adapter that uses Yjs to synchronize a Redux Slice. In this way the application logic doesn’t have to be aware of Yjs being involved.

Ideally, I would like to build this in a generic way, so that the adapter can be used for new slices without much modification.

Before taking this too far I first want to check with the community to see if something like this already exists or if someone already tried something similar. I’d love to hear your input.

2 Likes

Ended up going with a solution similar to https://github.com/joebobmiles/zustand-middleware-yjs such that:

  • Yjs → Redux in one operation (acceptable since we use memoized selectors (through reselect, through Redux Toolkit))
  • Redux → Yjs through patchSharedType that does clever diffing
1 Like

I had a similar idea recently but it seems like you got much farther than I did. I desperately need exactly what you are describing. Could you possibly make it public? I know others would find it useful too.

I’d love to open source it but we are still shaping some things up. I’ll let you know when it’s done, I think we can have something by next week. Still blocked by some other things that need finishing up. This is part of my work at Sana Labs.

The core ideas is captured by my previous post. There is nothing too fancy going. We still need to figure out if we want patchSharedState to handle Y.Text or just string and how to best perform update actions in Redux (that of course sync seamlessly with Yjs) - our current approach is a bit bulky but we have an idea for how to improve it.

1 Like

That’s great to hear. Can’t wait to take a look!

@ViktorQvarfordt @bioswale I read this thread with interest as I am considering migrating from a custom redux sync mechanism to a Yjs-based one, and has the same question as you posed here. In case you haven’t seen it, take a look at @YousefED 's SyncedStore – there is an overview video posted in this other yjs-discuss thread that is very interesting.

Another aspect of Redux I enjoy is the atomicity guarantee, especially when actions touch many parts of the state. I’m curious how to think about this in Yjs land, and asked about Yjs transaction atomicity in this other thread Transactions: atomicity and labeling? - #3 by dmonad – I’m curious how you’ve been thinking about this?

1 Like

Sorry for the delay on this, but we have great progress. We are currently running our integration in production. We do use atomic updates through transactions. We are not ready to open it up publicly just yet but I’d be happy to invite you if you are curious.

I’d be happy to set up a call with those in the community that are working with Yjs in Redux/React or just a json binding more generally. Send me a message and I’ll set up a call to go through our approach and compare with what’s out there. I would love for the community to settle on a solid way of working with Yjs in frontend code and I’m eager to show what we have.

3 Likes

Thanks for the notes @ViktorQvarfordt !

This sounds interesting. I’m a bit deep in my own project at the moment, but I wanted to share a bit of the direction I’m taking.

I am building a very thin layer on top of @YousefED 's SyncedStore that provides a few facilities similar to redux for my app. SyncedStore and reactive really do the heavy lifting - the “thin layer” here is mostly just a way of organizing my code in a React app:

  1. Use React context to provide a handful of functions/references across the React component tree (similar to react-redux provider)
  2. Use a const { state, actions, selectors } = useSync() hook to access and destructure that context. Importantly the useSync hook calls a fresh useSyncedStore to re-register the observables to the local call trace (I’m hand-waving here, I haven’t fully read the source so I’m not sure this is 100% accurate and the best way to approach this, but this agrees with my observation of stale renders without this)
    2.The actions is just a way of organizing functions which encapsulate changes to the SyncedStore. I could just as easily mutate the store from within components, but I think I wanna go this way for testability and separation-of-concerns.
  3. The selectors is similar to actions – just a way to encapsulate knowledge of the store structure. Again, SyncedStore and reactive do the heavy lifting of tracking observers.
  4. Also, the context provided by (1) and destructured by (2) gives access to the awareness for userId etc. I’m not sure I’ll keep this around… currently I’m storing the userId in awareness and I rely on reading the userId out of the awareness for when React components need to know the current user. Perhaps it’d be better to store this state elsewhere, and then mirror it into awareness rather than relying on awareness as the source of truth (since the awareness, AIUI, depends on the provider and being connected.)

I haven’t extracted this or written a standalone app, but here’s a gist (it’s really messy! not really ready for public consumption :sweat_smile: ) with a few pieces copy/pasted and my app-specific stuff replaces with a single “todos” slice: App.tsx · GitHub

Hey there, I’ve just published a redux-yjs-bindings package. It is not quite production ready (yet) but might be a good starter.

I took some inspiration from the zustand-middleware-yjs project mentioned above, and I think it works very similar to what Viktor described it in his second comment.

@ViktorQvarfordt Would love to get on a call with you, I’m assuming, that your approach is much more mature, considering you’re already using it in production.

2 Likes

Awesome! Would love to chat more with you, sending a DM. Our integrations (y-json, y-react and y-redux) are in closed alpha and anyone who wants early access can reach out.