Search code examples
reactjsreduxredux-toolkitredux-middleware

React / Redux custom middleware - useDispatch not updating the state


I would like to create some middleware to check a tokens expires_at field refreshing the token if necessary and updating the state for the token fields.

I have used the redux toolkit to create the redux functionality, making use of slices. I have a slice that will update the state with token data and this works on the callback for the initial token.

When creating the middleware I can not get the slice to be called and therefore the state remains unchanged.

As a simple spike without an api call:

import { useSelector, useDispatch } from 'react-redux';
import { setTokens } from '../features/tokens/tokenSlice';

const refresher = () => ({ dispatch, getState }) => (next) => (action) => {
    const exp_at = useSelector((state) => state.tokens.expires_at);

    if(hasBreachedThreshold(exp_at)){
        dispatch = useDispatch();
        dispatch(
            setTokens({
            access_token: 'new token',
            expires_at: 637423776000000000, -- hard coded date way in the future.
        })
    }
    
    ... else carry on.
);

I would expect when the middleware is called, on the first pass hasBreachedThreshold() returns true and the dispatch method would call the slice reducer and update the state. Any further runs would pass over as hasBreachedThreshold() would return false - for a while anyway.

What is happening though is that the hasBreachThreshold always returns false because the state is never updated, causing an indefinite loop.

The middleware is configured correctly and is called. The expires_at value is extracted from the state. hasBreachThreshold() is tested thoroughly and behaves correctly.

Being fairly new to React / Redux I expect my understanding of how and when to use dispatch is wrong. I assumed I could use dispatch similarly to how I do in my components, is this not the case? or am I going about this totally the wrong way?


Solution

  • When writing a middleware you have already the dispatch function and Redux store available through the function params:

    // Use `dispatch` & `getState`
    const refresher = () => ({ dispatch, getState }) => (next) => (action) => {
        const exp_at = getState().tokens.expires_at;
    
        if(hasBreachedThreshold(exp_at)){
            dispatch(
                setTokens({
                access_token: 'new token',
                expires_at: 637423776000000000, -- hard coded date way in the future.
            })
        }
    );
    

    Also you have essential mistake of when to use React hooks, refer to Rules of Hooks.

    Hooks are not available in context of Redux middleware.