Korijn / vue-store

Lightweight Vue 3 composition API-compatible store pattern library with built-in undo/redo functionality.
24 stars 1 forks source link

[Feature Request] Add side effects support #3

Closed zhongqf closed 2 years ago

zhongqf commented 3 years ago

Thanks for you great work. It's a pretty good lightweight state management solution for vue3. But it still has a problem that, because of using rfc6902, it is difficult to add some side-effects like sever communications into undo/redo function.

Is there a good advices for it?

Korijn commented 3 years ago

Hi there, thank you for the kind words!

because of using rfc6902, it is difficult to add some side-effects

It's not entirely clear what you mean by this. Can you explain why you think so? Perhaps with an example?


Regardless, I think the easiest method to add side effects to undo/redo is to just wrap them in a function of your own, and use that!

const myUndo = (store) => {
    // server comms here
    store.undo();
    // or here
};
zhongqf commented 3 years ago

@Korijn Thanks a lot 😄

It's not entirely clear what you mean by this

Consider this, a very simple and common use case.

Korijn commented 3 years ago

Thank you for the detailed explanation! You could use a watch expression to push the todos to the server whenever they change:

watch(() => store.state.todos, async (todos) => {
    await server.commitTodos(todos);
}, { deep: true });

(disclaimer: I wrote this off the cuff and have not tested)

Does that fulfill your needs or am I overlooking something?

You do need to be careful about server request build up, for example if someone rapidly undos many actions. You could buffer the requests by using something like lodash' debounce as a simple workaround for that.

zhongqf commented 3 years ago

Thank you for your reply. Sorry that your solution is not very good for me. If I do that, I need to check in server side if it is a modification of a todo, or a deletion or insertion. That will be a big work.

How about add a callbak or an event listener in your libray after calling the applyPatch and pass the patches as the parameter? I will also try to make a pull request if I get time to do it.

Korijn commented 3 years ago

I have to think about it. I would prefer not to expose patches like that, they're kind of an internal representation.

When committing a mutation, it's simple, you could wrap your mutation in a function similar to VueX actions. For example:

const addTodo = async ({ id, text }) => {
    await server.addTodo({ id, text });
    store.addTodo({ id, text });
};

But now you want to also support undo, is that correct?

Would it help if you had callbacks that told you what mutation is being undone? E.g.:

store.on("undo", async (type, options) => {
    if (type === "addTodo") {
        await server.removeTodo(options.id);
    }
});

store.on("redo", async (type, options) => {
    if (type === "addTodo") {
        await server.addTodo(options.id);
    }
});

It's not very elegant, but it would solve your problem without exposing patches.

I'm thinking of how to make it so that you have less cases to handle.

zhongqf commented 3 years ago

Thanks 😸

Would it help if you had callbacks that told you what mutation is being undone? E.g.:

Maybe only the mutation name is not enough, because for example if I want to undo a modification of todo's title, I need to know what title was...

I would prefer not to expose patches like that, they're kind of an internal representation.

Yes, I understand. But I have no idea on how to implement it in a more elegant way...

Korijn commented 3 years ago

Maybe only the mutation name is not enough, because for example if I want to undo a modification of todo's title, I need to know what title was...

In the example I provided, type is the mutation name, and options are the arguments you provided when you called the mutation. Would that be sufficient?

store.on("undo", async (type, options) => {
    if (type === "addTodo") {
        await server.removeTodo(options.id);
    }
});

But I have no idea on how to implement it in a more elegant way...

I'm going to come up with something. I will need some time to think. :)

zhongqf commented 3 years ago

@Korijn any updates? 😄

Korijn commented 3 years ago

Sorry for the lack of updates. I have been thinking about this.

If I understand correctly, the root of your issue is synchronization with a backend via an API. There are different options to support this:

I think these are the two main solutions you see with libraries such as VueX. Depending on how many cases you have to handle, how big your data is, and how often there are changes, these options may be suitable for you!

However, you propose a new solution here:

I did not realize this during our earlier conversation!

I think you can already implement this yourself by creating watchEffect on store.past and store.future. You would need a small amount of tracking logic to determine what to send to the backend but it should still be very simple.

Can you figure this part out yourself? If it's too challenging I can help, but currently I am vacationing in Italy so it would take some time for me to cook up an example. 😅

I could probably make it easier for you by providing callbacks on the store. I will add that in the next version of this library.

ikomom commented 2 years ago

hello, anything update, i also need this

Korijn commented 2 years ago

Nothing, besides the advice given above. You can already implement this yourself.

If you have a specific example that I can help you with please open a new issue. I can help you implement it.