6

I'm working on a React application that uses the following architecture:

  • redux
  • typesafe-actions
  • redux-observable

My question is: How can I execute an UI action on specific redux action?

For example, suppose we have the following async actions defined with typesafe-actions:

export const listTodo = createAsyncAction(
  'TODO:LIST:REQUEST',
  'TODO:LIST:SUCCESS',
  'TODO:LIST:FAILURE',
)<void, Todo[], Error>();

An Epic will watch for listTodo.request() and send the API call, then convert the response to a listTodo.success() action. Then the redux reducer will be triggered by listTodo.success() action and store the todo list into redux store.

In this setting, suppose I want to do the following things in an component:

  • dispatch a listTodo.request() action to retrieve all the actions
  • After the async request is done (i.e. after listTodo.success() action appears in the action stream), redirect the UI to a second path

So my question is, how could I watch the action stream and react to the listTodo.success() action?

UPDATE: To avoid being too specific, we can think of another case. I want to simply display an alert with window.alert() after listTodo.success() appears in the action stream. Or simply console.log(), or whatever that changes local state (instead of global redux state). Is there a way to implement that?

UPDATE 2: There is a similar question here, but for Angular w/ ngrx. What I want to do is exactly the thing described in above post, but in React / redux-observable fashion:

import { Actions } from '@ngrx/effects';

@Component(...)
class SomeComponent implements OnDestroy {
    constructor(updates$: Actions) {
        updates$
            .ofType(PostActions.SAVE_POST_SUCCESS)
            .takeUntil(this.destroyed$)
            .do(() => /* hooray, success, show notification alert ect..             
            .subscribe();
    }

}
charlee
  • 1,309
  • 1
  • 11
  • 22
  • How do you do navigation in your app? Are you using a redux based router like `connected-react-router` ? – Harald Gliebe Mar 27 '19 at 19:02
  • @Harald I don't want this question to be too specific. I know if the route is stored as redux state this problem can be solved. What if, say, I want to simply show a dialog with `window.alert()` after `listTodo.success()` is observed? – charlee Mar 27 '19 at 19:05
  • Then either create a new epic that watches for `listTodo.success()`, perform a side effect with `action$.tap(() => {window.alert()})` and then `ignoreElements()` – Harald Gliebe Mar 27 '19 at 19:10
  • Thanks @Harald. Can I do this (create an epic) in a component? – charlee Mar 27 '19 at 19:11
  • You need to use `combineEpics()` to combine all the Epics in your application and run it with the `epicMiddleware`. See https://redux-observable.js.org/docs/basics/SettingUpTheMiddleware.html how to set up `redux-observable` correctly – Harald Gliebe Mar 27 '19 at 19:14
  • @Harald I started to understand your first question. Probably route should be a global state and changing route is not something a component should worry about. I'm trying to move more logics into Epics and see if I can solve all the problems. – charlee Mar 27 '19 at 20:07
  • Good luck! It's a bit tricky at the beginning, but it will become easier very quickly. And then epics will be good way to encapsulate logic and reduce global state. – Harald Gliebe Mar 27 '19 at 20:17

4 Answers4

1

With redux the components update based on state.

If you want to update a component based on an action than you update the state in the reducer, such as setting {...state, success: true} in the reducer. From there you simply read the state into your component as you normally would and if the state is changing to success than you show your window.

Ralph Ritoch
  • 3,260
  • 27
  • 37
  • Thanks @ralph-ritoch for the solution. I kind of feel that this would be less error proof - what if there are some other actions also update `success` state? what if the original async "request" action is emitted twice and the successor "success" actions' order cannot be guaranteed? – charlee Mar 27 '19 at 19:32
  • @charlee for multiple success states you just give them their own names, such as loginSuccess, fetchUserSuccess, whatever. I regularly store state from redux into my component so when receiving the state updates from the observable I can check if the state has changed or not so if success is emitted twice it only shows once. – Ralph Ritoch Mar 27 '19 at 19:37
  • @charlee if you need to sync multiple results with a component than I can see the possiblity of holding an array in your state which has something such as {success: boolean, id: string} and populating id with some random or unique value. I can't think of too many times when that would be needed, but it is possible to have a queue redux state. – Ralph Ritoch Mar 27 '19 at 19:39
1

I feel like a dialogue should be a side effect, so you'd put them in epics

TrySpace
  • 2,233
  • 8
  • 35
  • 62
1

Nowadays, the best way to listen for actions inside a component is using the createListenerMiddleware that is included with the redux-toolkit (RTK), combined with useEffect():

useEffect(() => {
  // Could also just `return dispatch(addListener())` directly, but showing this
  // as a separate variable to be clear on what's happening
  const unsubscribe = dispatch(
    addListener({
      actionCreator: todoAdded,
      effect: (action, listenerApi) => {
        // do some useful logic here
      },
    })
  )
  return unsubscribe
}, [])

Docs: https://redux-toolkit.js.org/api/createListenerMiddleware#adding-listeners-inside-components

I usually use Epic Observables for side effects, and use a selector for state changes in components, but there are certain situations where listening for an action directly inside a component (instead of a state change) is the best solution. The above fits that bill perfectly.

RcoderNY
  • 1,204
  • 2
  • 16
  • 23
0

Might be a little late but I solved a similar problem by creating a little npm module. It allows you to subscribe to and listen for redux actions and executes the provided callback function as soon as the state change is complete. Usage is as follows. In your componentWillMount or componentDidMount hook:

 subscribeToWatcher(this,[
      {  
        action:"SOME_ACTION",
        callback:()=>{
          console.log("Callback Working");
        },
        onStateChange:true
      },
    ]);

Detailed documentation can be found at https://www.npmjs.com/package/redux-action-watcher