jeffbski / redux-logic

Redux middleware for organizing all your business logic. Intercept actions and perform async processing.
MIT License
1.81k stars 107 forks source link

Run Logic within Logic, like you can with Sagas? #141

Open christian314159 opened 5 years ago

christian314159 commented 5 years ago

Currently converting some Redux Sagas into Logics and have run into one issue:

We've got a Saga that runs another Saga within it (this Saga is also sometimes triggered independently), like so:

function* sagaA() {
  yield call(api.getSomething, a, b);
  yield sagaB();
  yield put(actionDone());
}

I'm struggling to grasp how to do this with Logic. Or could I do something where I called the other Logic's process method directly from the other?

async process({ getState }, dispatch, done) {
  await api.getSomething(a,b);
  await logicB.process({ getState}, dispatch);
  dispatch(actionDone());
  done()
}

Any ideas/help appreciated, thanks.

Shannor commented 5 years ago

Interesting problem. I'm not actually sure if you can do the second part you want. I have normally dispatched another process if needed since they weren't completely coupled. If the logic needs to be ran synchronously, then the only way I have seen is to put the logic there. Though I could be wrong!

skbolton commented 5 years ago

I would suggest a slightly different approach. We had a situation in our app where you could upload a photo, tag a photo, or upload and tag a photo. I recognized that we could have two processors that handle the tagging and uploading part and then all there was left to do was compose them together with a processor that orchestrates the work. Hopefully this code makes sense and applies to your problem. The interesting bit here is listening on the action stream for other events after this processor so you can jump back in when the thing you are delegating to is complete/errored. See the action$ below.

export const uploadAndTagPhoto = createLogic({
  type: constants.UPLOAD_AND_TAG.ACTION,
  warnTimeout: 0,
  processOptions: {
    dispatchReturn: true,
    dispatchMultiple: true
  },
  process({ action, action$ }, dispatch) {
    const { tag, projectId, file } = action.payload
    const fileNameWeAreUploading = file.name

    dispatch(Images.actions.uploadPhotoToProject(projectId, [file]))

    return action$.pipe(
      // filter stream to only photo upload events
      filter(onlyUploadEvents),
      // filter to files that have same name as one we care about
      filter(byFileName(fileNameWeAreUploading)),
      // stream could contain error event
      // in that case we want to throw and handle it as an error
      map(fileUploadAction => {
        if (
          fileUploadAction.type ===
          Project.constants.UPLOAD_AND_TAG.REJECTED
        ) {
          throw new Error()
        }

        return fileUploadAction
      }),
      // upload action can upload multiple files
      // we cannot so we grab our first file back out
      map(uploadSuccess => uploadSuccess.payload.files[0]),
      map(uploadSuccess =>
        Images.actions.tagElement(
          uploadSuccess.id,
          uploadSuccess.location,
          tag
        )
      ),
      catchError(error =>
        of({
          type: Project.constants.UPLOAD_AND_TAG.REJECTED,
          payload: {
            error,
            tag
          }
        })
      )
    )
  }
})

Also I had to adapt this example slightly because our code in our application does slightly different things but this idea is easier to explain. If I made any errors or something looks off let me know and I will fix the goof or explain better.