jeffbski/redux-logic

[Questions] What is "done" for? How to orchestrate actions within one logic?

gilbsgilbs opened this issue · 5 comments

Hi,
Sorry if it's not the intended place to ask these questions, but I didn't find a gitter or any mention of where to ask questions.

I find redux-logic really cool. Though there's some tiny things I don't understand.

What is the purpose of the done function?

In the process function, a done parameter is passed. Why am I supposed to call I'm done dispatching? What happens if I forget to call it? What happens if I call it twice? What happens if I dispatch after calling it?

How to orchestrate multiple actions within a logic?

Let's say I want to react to some action and then, to some other action sequentially. In redux-saga, I would probably write something like this:

function* mySaga() {
  yield take('ACTION0');
  console.log('Got ACTION0');
  yield take('ACTION1');
  console.log('Then got ACTION1');
}

I don't see an easy way to replicate this in redux-logic. Is this considered an anti-pattern? Why?

Thank you in advance.

@gilbsgilbs thanks for the questions. Yes this is a good place to ask questions.

redux-logic has several different ways it can be used.

  1. using callbacks
  2. returning a promise
  3. returning an observable

Each of these is useful depending on what your team's comfort level is.

Most people start out with the callback approach since it is very flexible and similar to other things they have seen or used.

Underneath the covers we are ending up being a part of an observable pipeline. Since observables can be cancelled and can emit 0-N actions, and finally complete. To be able to allow for all of these scenarios with callbacks we have a dispatch and a done. The done simply indicates that the observable can be cleaned up and it will emit no more actions. If you have logic that will continue to emit actions over time then you don't need to call done, otherwise you call done when you are finished emitting whatever actions are necessary.

redux-logic took inspiration from saga for features so it supports things like takeLatest, however it is simply done by setting the flag on the logic when you create it. latest: true which means cancel previous and only use the latest response.

Every logic that you pass into the array to createLogicMiddleware is setup to listen for actions. So instead of calling take, it is simply created in advance and you set the type: 'ACTION0'. The type can be a string, regex, array of strings/regex, Symbol, etc. You can also use '*' to have the logic answer to all actions. So each logic can be listening for one or any number of actions.

I hope that helps. Feel free to continue asking questions as you have them.

@jeffbski Hi Jeff! Many thanks for taking time to answer my questions and even dig a bit more into the details. This explains the done callback really. I didn't realize redux-logic was so tightly coupled with RxJS/Observables. Explains a lot.

I probably missed something on your answer about my second question though. Could you provide some snippet that is equivalent to mine? I understand that you can listen to multiple actions using one logic (by providing a regex, an array or a wildcard as type), but that's not exactly what I was looking for. With redux-saga, it's quite easy to:

  1. Take ACTION0
  2. Do some stuff
  3. Then wait for ACTION1
  4. Do some other stuff.
const someLogic = createLogic({
  type: [ACTION0, ACTION1],
  latest: true,
  process({ getState, action }, dispatch, done) {
    console.log('Doing some stuff after taking ACTION0');
    // >>> Here I want to wait for ACTION1 to be dispatched by some other logic or by UI <<<
    // Once it has happened, I can do some other stuff.
    console.log('Doing some other stuff after taking ACTION1');
  }
});

The typical use-case is when a logic has to dispatch some action to get things set up. I'll try to explain with an example (maybe not the best): you take a FETCH_USER_DATA action in the logic. Then you realize you need the user to login. So you dispatch some REQUEST_USER_LOGIN action (that will trigger some other logic). Then you wait in the same logic for a USER_LOGGED_IN action to be dispatched. And only then you fetch the user data.

Anyways, keep up the great work! It looks really promising (no pun intended) and seems less boilerplate and less magic than redux-saga. Also, it should be easier to get proper typings (no ugly yields everywhere, just raw callbacks, promises, async/await or Observables). Can't wait to see full TS support and exhaustive docs. If I can use it at work, I may also try contributing in my spare time.

I'm planning on adding some features that will make it easier to wait for certain actions before proceeding. There are some use cases that could benefit this.

As for what you can do with redux-logic as it is now, you can take advantage of the validate hook. You can intercept the FETCH_USER_DATA action and determine that the user is not logged in, so instead you can issue a different action like REQUEST_USER_LOGIN and I would attach the original action in the payload which could end up creating a hidden field in the form or saving it in state. Then when the user does login, one could fire off the original action.

There's probably other ways but it can be very handy to be able to augment or swap out an action before it gets to the reducer. Note that if you change the action type in the validate hook then redux-logic will dispatch that so that all middleware have a chance of seeing the new action. If it is the same action type then it just sends it down using the next cb.

Yes, I need to get TS support in place and build up the docs. I appreciate help in any of this. I'm still learning TS so I need to learn it better.

Many thanks for your time, @jeffbski .

You are quite welcome!