Custom middleware causes a circular reference in redux
Asked Answered
C

2

7

I am trying to convert a redux project to typescript using the provided documentation:

https://redux.js.org/usage/usage-with-typescript#type-checking-middleware

However I'm having trouble doing it with my custom middleware. Here is the minimized and extracted code that causes an error for me.

store.ts:

import { configureStore } from '@reduxjs/toolkit';

import reducer from './customReducer';

import { customMiddleware } from "./customMiddleware";

const store = configureStore({
    reducer: {
        custom: customReducer
    },
    middleware: getDefaultMiddleware => getDefaultMiddleware().prepend(customMiddleware)

})

export type RootState = ReturnType<typeof store.getState>

export default store

customMiddleware.ts:

import { Middleware } from 'redux';
import { RootState } from './store';

export const customMiddleware = (): Middleware<{}, RootState> => {
    return store => next => action => {
        return next(action);
    }
}

This causes several error messages: on const store = configur...:

'store' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

on RootState export:

Type alias 'RootState' circularly references itself.

on customMiddleware export:

'customMiddleware' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

Carine answered 2/7, 2021 at 14:31 Comment(0)
C
8

In that case, you'll have to somehow break the circle.

Easiest way here is

export type RootState = ReturnType<typeof customReducer>

Edit: I think your initial code here was reducer: customReducer

With the given code it won't work - you need to split out that reducer creation before the store creation:

const rootReducer = combineRecucers({
        custom: customReducer
})

export type RootState = ReturnType<typeof rootReducer>

const store = configureStore({
    reducer: rootReducer,
    middleware: getDefaultMiddleware => getDefaultMiddleware().prepend(customMiddleware)

})
Cottager answered 2/7, 2021 at 15:53 Comment(3)
Well, first there are more reducers, not just customReducer. I just made a small example. Second, if I pick one of those it doesn't work. Now it throws that there are no matching overloads when I prepend the customMiddleware.Carine
Ah OK, makes sense. If I try this I get the errors on prepend. Argument of type '() => Middleware<{}, RootState, Dispatch<AnyAction>>' is not assignable to parameter of type 'Middleware<any, any, Dispatch<AnyAction>>' and also Argument of type '() => Middleware<{}, RootState, Dispatch<AnyAction>>' is not assignable to parameter of type 'readonly Middleware<any, any, Dispatch<AnyAction>>[]'\Carine
I tried changing export const customMiddleware = (): Middleware<{}, RootState> to export const customMiddleware = (): Middleware<{}, RootState, Dispatch<AnyAction>>Carine
C
0

Ah, OK I figured it out. The problem was in how I was defining my customMiddleware. The documentation simply defined the export as a Middleware:

export const customMiddleware: Middleware = store => next => action => {
    return next(action);
}

But I had my export as a function that returns a middleware, since there's some initialisation there in my actual code:

export const customMiddleware = (): Middleware => {
    return store => next => action => {
        return next(action);
    }
}

So I simply had to call it as a function when prepending:

middleware: getDefaultMiddleware => getDefaultMiddleware().prepend(customMiddleware())

Very silly of me...

EDIT: using a root reducer type was needed as well.

Carine answered 5/7, 2021 at 8:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.