How to listen to redux action stream in component
Asked Answered
T

4

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();
    }

}
Treadmill answered 27/3, 2019 at 18:55 Comment(7)
How do you do navigation in your app? Are you using a redux based router like connected-react-router ?Antipole
@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?Treadmill
Then either create a new epic that watches for listTodo.success(), perform a side effect with action$.tap(() => {window.alert()}) and then ignoreElements()Antipole
Thanks @Harald. Can I do this (create an epic) in a component?Treadmill
You need to use combineEpics() to combine all the Epics in your application and run it with the epicMiddleware. See redux-observable.js.org/docs/basics/SettingUpTheMiddleware.html how to set up redux-observable correctlyAntipole
@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.Treadmill
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.Antipole
C
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.

Chanda answered 27/3, 2019 at 19:26 Comment(3)
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?Treadmill
@Treadmill 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.Chanda
@Treadmill 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.Chanda
H
1

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

Housebreaker answered 18/11, 2019 at 23:28 Comment(0)
D
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.

Drongo answered 10/7, 2023 at 1:47 Comment(0)
A
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

Adenosine answered 29/6, 2019 at 12:0 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.