0

Imagine this action:

export const myAction = createAsyncThunk(...)

I dispatch the action in 2 different React components, both of which need this action to populate the state they depend on:

useEffect(() => {
  dispatch(myAction())
}, [dispatch])

This, of course, causes the thunk to run its async code twice.

I want to do something similar to takeLeading in Redux Saga with this thunk.

Is there a way I can get subsequent dispatches of myAction() to be ignored while the first one is running?

Molten Ice
  • 2,715
  • 1
  • 27
  • 37
  • You have access to `getState` inside the thunk. So you can check if `myAction` is currently in progress (via something like `state.stuff.isDoingMyAction`). At that point you can abort the action. To be safe you probably also want to check if the expected result has already been stored, since `myAction` might not be in progress but its data might still be present. Another option is to move this logic into a custom hook that only dispatches `myAction` if necessary. This would actually be preferred over a thunk that aborts itself. – timotgl Apr 21 '22 at 13:04
  • @timotgl I am not looking to abort the thunk. Just ignore all calls till the thunk finishes running. Then once done the next dispatch would kick off another thunk, which would again ignore all future dispatches until it finishes running, – Molten Ice Apr 21 '22 at 14:27
  • 1
    returning early from a thunk gives you the same end result as ignoring the calls. Regardless of how you solve it, you have to keep track of an ongoing thunk somewhere. See my answer below with the hook solution. – timotgl Apr 21 '22 at 15:59

1 Answers1

1

Custom hook solution:

import React, { useCallback, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { myAction } from './actions';

const useMyActionNonConcurrently = () => {
    const dispatch = useDispatch();
    const isPerformingMyAction = useSelector(state => state.someSlice.isPerformingMyAction);
    const performMyAction = useCallback(
        () => {
            if (!isPerformingMyAction) {
                dispatch(myAction())
                // this thunk needs to toggle state.someSlice.isPerformingMyAction while it's running
            }
        },
        [dispatch, isPerformingMyAction]
    );
    return performMyAction;
};

// Usage in a component (on mount):
const performMyAction = useMyActionNonConcurrently();
useEffect(performMyAction, []);
timotgl
  • 2,865
  • 1
  • 9
  • 19
  • 1
    Thanks! But isn't there an edge case here where the thunk kicked off but hasn't yet made `isPerformingMyAction` true so we end up dispatching twice anyway? – Molten Ice Apr 22 '22 at 08:42
  • 1
    @MoltenIce I don't think so, the thunks should run synchronously (as in blocking) *until* they `await` something or return a promise. So the first action you dispatch from within the thunk, which toggles the flag, arrives in the reducer before another thunk could do the same. But good idea for a unit test to verify this assertion! – timotgl Apr 22 '22 at 09:36