jamsocket / y-sweet

A standalone yjs server with persistence to S3 or filesystem.
https://docs.y-sweet.dev
Other
389 stars 27 forks source link

Redux Binding? #137

Open SebastianStehle opened 11 months ago

SebastianStehle commented 11 months ago

Hi,

I really like your project. I have created a binding for redux and ideally I would like to integrate it to an existing platform. Either the yjs organization or your service.

My project URL: https://github.com/SebastianStehle/yjs-redux. Your review and feedback is very welcome.

I actually created the project for https://github.com/mydraft-cc/ui and I think I will use y-sweet to add collaboration.

paulgb commented 11 months ago

Thanks for sharing @SebastianStehle, this looks cool! And the timing is great, we've been thinking a lot lately about interfaces between Yjs and frontend UI/state libraries.

My Redux knowledge is a bit dated now, do you have an example of what some application-side code in a reducer would look like?

I'd love to help you get it set up with y-sweet, let me know if there's anything I can help with.

SebastianStehle commented 11 months ago

My Redux knowledge is a bit dated now, do you have an example of what some application-side code in a reducer would look like?

Usually the reduxer is just a function like (state, action) => state. So it returns a new state based on the old state. There are several ways to do that:

  1. Use a library like immer.js, which creates a new state based on mutation updates. This is the recommended way by the redux toolkit: https://redux-toolkit.js.org/tutorials/quick-start
  2. Use normal javascript methods like spread operators to create a new state, which can be annoying for deep states.
  3. Use a class based approach like in my sample.

The class based approach is more exotic, but it was needed for my case.

Because yjs is not immutable, we cannot use the types directly. Therefore the redux library is responsible to update abstract types from the diff of the current and the previous state and to create a new state from the old state and the events. It is a little bit more complicated, but this is described in the readme.

The API surface is very small. It basically looks like this:

// Call this for every slice you want to synchronize.
const binder = createYjsReduxBinder(options);

binder.connectSlice(doc);

export const store = configureStore({
    reducer: binder.enhanceReducer(combineReducers({
        ... YOUR REDUCERS
    })),
    middleware: [binder.middleware()]
});

You create the binder, that registers a middleware and a reducer to the store. The reducer is needed to accept the incoming changes (because there is no API for something like store.setState()) and the middleware is responsible to listen to changes. They exist through the lifetime of the application.

Then connectSlice creates the connection to the doc and returns a function to destroy the connection. This is needed if you create something like mydraft and you get a new document, when you click a button.

websiddu commented 8 months ago

I have tested y-sweet with mobx and https://syncedstore.org and works perfectly well. Not sure about the yjs-redux tho.