Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add effectOn API #419

Closed
paul-sachs opened this issue Jan 29, 2020 · 18 comments
Closed

Add effectOn API #419

paul-sachs opened this issue Jan 29, 2020 · 18 comments

Comments

@paul-sachs
Copy link

paul-sachs commented Jan 29, 2020

So I was trying to figure out a way to centralize an effect based on when a specific part of a model changes. This should cover when it loads from default to persisted value. The idea would be something like:

const storeModel = persist({
  token: '',
  setToken: action((state, payload) => ({ ...state, token: payload })),
  onTokenChange: effectOn(state => state.token, async () => {
    // fetch user data and dispatch actions.
  })
})

This would allow us to fetch user data when token is changed from anywhere, whether it be from an action, or from the initial load from the persisted store.

The specific api isn't important, but I think this mechanism could allow much easier centralization of logic.

What do ya'll think?

@kamikazePT
Copy link

Seems like an interesting feature to have!

I would enjoy discussing the implementation details :)

@paul-sachs
Copy link
Author

I haven't looked into the details yet, but i'll see if i can get a proof of concept PR together. I mostly just have experience with bare redux middleware, so hopefully that'll be enough to put something together.

@ctrlplusb
Copy link
Owner

@paul-sachs you may want to hold off until I finish the plugin architecture rewrite. I need to solidify the API a bit more. It will make this type of experimentation easier for sure. 👍

@paul-sachs
Copy link
Author

@ctrlplusb yeah, i just looked at the code, and although I think i gathered all i needed to make it, i wasn't really happy with the distribution of changes. I'll wait for plugin architecture changes before giving it another shot. Let me know if you want a second pair of eyes on the API, maybe i can look to see what it would look like for this feature.

@paul-sachs
Copy link
Author

Actually, just saw #417, so maybe i'll just fork off that and see what i can do with what you got there.

@ctrlplusb
Copy link
Owner

It's still a WIP, so you may as of yet find it frustrating

@paul-sachs
Copy link
Author

paul-sachs commented Jan 31, 2020

It would definitely suffice for my implementation of effectOn. All i really need is a custom middleware that has some access to what was being subscribed to. Haven't actually implemented it so wouldn't know if it got stuck on some details down the line though.

@ctrlplusb
Copy link
Owner

ctrlplusb commented Apr 15, 2020

@paul-sachs

I had a play around with this. It was fairly straight forward to implement.

It's quite a nice feature and closes the gap in terms of feature parity with the official React hooks. I have actually found myself having to utilise components to grab state and pass it through a useEffect hook to achieve similar results.

I eventually settled on the following API:

const todosModel = {
  todos: [],
  saving: false,
  setSaving: action((state, payload) => {
    state.saving = payload;
  }),
  onTodosChanged: effectOn(
    // Provide an array of state dependency resolvers
    [(state, storeState) => state.todos],
    // Provide a handler, which is very similar to a thunk in behaviour.
    // It receives the following arguments:
    // - actions: the local model actions
    // - previous: the previous state values for each of the dependencies
    // - helpers: same as the helpers a thunk receives
    async (actions, previous, helpers) => {
     const [previousTodos] = previous; // 👈 could be handy in flag / logic checking
      actions.setSaving(true);
      await todosService.save(
        helpers.getState().todos // 👈 use the helpers to get current state as in thunks
      ); 
      actions.setSaving(false);
    }
}

My one inhibition for the above API, is the ability to target the entire store state. This is actually a big potential killer on performance as we would have to resolve and check every dependency, without that capability it's quite cheap to short circuit and know which effects need to be checked/executed.

Perhaps as an initial release we could not expose the storeState to the dependency resolvers.

@paul-sachs
Copy link
Author

@ctrlplusb oh man, i've been totally swamped lately, i totally forgot to follow up on this. I think you're right, I would rather have a basic API that only exposes the current model first.

@ctrlplusb ctrlplusb changed the title [Proposal] Support an effectOn helper Add effectOn API Apr 15, 2020
@paul-sachs
Copy link
Author

@ctrlplusb i'd love to see these in 3.4, is there anything i can do to help get it in?

@ctrlplusb
Copy link
Owner

Hey @paul-sachs

I will admit I have been feeling a bit hesitant to add a new API but then I thought I could take a play out of the React teams playbook. I could release it with an "unstable_" prefix. This would allow us then to make breaking changes to the API of effectOn without having to do a major bump on the whole library. I'd prefer this as in my experience with adding new APIs, real world usage normally exposes weaknesses or missed opportunities to make the API that much more useful. So I think think this will be a good compromise for now.

I've also been working on it already. It's almost got 100% test coverage and I've made a couple of further changes to the API to make it even more useful.

I am hoping to attach it to the v3.4.0 branch very soon and will definitely ping you for feedback.

Thoughts on the prefix strategy? You wouldn't mind doing some minor patches to your code if we modified the API slightly would you?

@paul-sachs
Copy link
Author

@ctrlplusb I have no problem with the prefix strategy. I'm more curious about the status of v4 (fiddling with ideas for an undo/redo plugin) but that's an idea for another issue.

@ctrlplusb
Copy link
Owner

@paul-sachs the initial plugin architecture will be a part of v3.4.0 release although I won't announce it as a public API. I would be happy to work with you to allow you to play with if after release. But would like to keep it marked as internal to allow for changes without major bumps. When we move then to v4 I am hoping to document and expose the API. Probably we could do a code walkthrough via zoom/Skype. I'd be interested in hearing your feedback when you do try to write some extensions. 👍

@paul-sachs
Copy link
Author

@ctrlplusb would love that. I need a change of pace for my current stuff anyways, feeling a little cooped up.

@ctrlplusb
Copy link
Owner

@paul-sachs the code, tests and TS typings are complete. 👍

I just need to write docs then will release into alpha.

@ctrlplusb ctrlplusb mentioned this issue May 4, 2020
@ctrlplusb
Copy link
Owner

@paul-sachs - sorry for the delay. I had to make some decisions around semantics. It's on the alpha branch now if you'd like to review. 👍

@paul-sachs
Copy link
Author

@ctrlplusb awesome! I took a look, and it looks good to me. I was looking for the plugin architecture you mentioned but it still seems pretty tied to the internals. Perhaps I'm just looking at it sideways. I'd really love to see what you have in mind for that architecture. Either way, that's a different issue. Thanks again!

@ctrlplusb
Copy link
Owner

Great! Yep, plugin architecture is still on the way. I did a trial run on another branch, but will apply it to 3.4.0 soon. Will ping you when it is ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants