redux-toolkit: The inferred type of 'X' cannot be named without a reference to '@reduxjs/toolkit/node_modules/immer/dist/internal'. This is likely not portable. A type annotation is necessary.

This error shows up in VS Code when using TypeScript. The slice code:

import { POSItem } from './../POSService';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '../../../app/store';

export interface POSState {
    addedPOSItems: POSItem[],
}


const initialState = {
    addedPOSItems: []
} as POSState;

export const POSSlice = createSlice({
    name: 'pos',
    initialState,
    reducers: {
        setAddedPOSItems: (state, action: PayloadAction<POSItem[]>) => {
            state.addedPOSItems = action.payload;
        }
    }
});

export const { setAddedPOSItems } = POSSlice.actions;
export const selectAddedPOSItems = (state: RootState) => state.pos.addedPOSItems;
export default POSSlice.reducer;

The store config

import { configureStore } from '@reduxjs/toolkit';
import POSSlice from '../client/POS/POSService/POSSlice';

const store = configureStore({
    reducer: {
        pos: POSSlice
    },
});

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export default store;

Versions:

    "react": "^17.0.2",
    "redux": "^4.1.0",
    "@reduxjs/toolkit": "^1.6.1",

Full error message:

"The inferred type of 'POSSlice' cannot be named without a reference to '@reduxjs/toolkit/node_modules/immer/dist/internal'. This is likely not portable. A type annotation is necessary."

Reinstalling node_modules or clearing the cache didn’t help. Do you need any more information?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 7
  • Comments: 32 (4 by maintainers)

Most upvoted comments

I solved the error by doing the following in my tsconfig.json

This is probably just a workaround but if someone comes across this problem this should work.

{
  "compilerOptions": {
    "declaration": false,
  },
}

@PeiLiao : please don’t do that 😦 You’re throwing away all the actual TS types in templateSlice.

export const templateSlice: any = createSlice({ name: 'template', initialState, *****

only need to declare the slice explicitly to resolve this ts type error

TLDR; No clue why this happens, but you can add immer as a dev dependency and still compile with declarations.

Follow up on this one. I’m working with two different monorepos, both using RTK, and both using "declaration": true. For reasons that should be obvious, simply setting "declaration": false is not a solution (although it does work, if you don’t have any need to publish types).

Here’s the deal for me. One project has this error (let’s call it project CRAP), the other project doesn’t (call this one CLAP). I have desperately tried to figure out why this error is occurring in CRAP and not in CLAP. Nearly everything I can think of I have normalized between the two:

  • Same tsconfig (nearly, see below)
  • Same RTK version
  • Same TypeScript version
  • The one without the error was importing castDraft from immer, so I tried that, thinking the import might trigger some bail out logic. Didn’t do squat.
  • Both packages I tested on within the separate monorepos have RTK peer dependency defined as ^1.7
  • The various apps within these projects have RTK as a dependency (shouldn’t matter, because it’s not the apps but the supporting packages that I’m building and publishing using tsc).
  • None of the packages within either monorepo have immer as a dependency or devDependency.

And when I say the versions are the same, these are both running yarn workspaces, properly deduped. So I do mean the same.

The only thing that’s different, that I can see at least, is the project structure and the path mappings in the tsconfigs. In CLAP, the apps themselves are not part of the path mappings (although, everything is still defined in workspaces). Only the “shared” and externally published packages are defined in the path mappings.

I frankly can’t see how that would matter though. I also tried, changing the path mappings in CRAP so only the “shared” modules are mapped, and this did nothing.

All of this said, long before I tried any of this, I did find a way to “trick” TypeScript into thinking all is right in the world by adding immer as a dev dependency. So I guess that’s what I have to do? I know this “fixes” it and I can now compile with declarations and without errors, but it seems so arbitrary.

If I’m able to turn CRAP into CLAP, then I’ll update this.

Hey the solution is to:

  • add immer to devDependencies
  • add import 'immer' on top of your file, this will not import anything at runtime but it will import the types to make the compiler happy

We could fix this inside redux-library by enforcing the type of slice.actions to not include WritableDraft as part of it (it’s an implementation detail either way)

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { WritableDraft } from '@reduxjs/toolkit/node_modules/immer/dist/internal';

As another potential not the prettiest workaround.

Adding immer to devDependencies is not strictly necessary.

A simple import "immer" at the top of the file quiets the error.

The fix for me was to add declaring the slice as : Slice in the line where createSlice is called:

import { createSlice, Slice } from '@reduxjs/toolkit'

type MyState = {
   name: string,
   address: string,
}

const mySlice: Slice = createSlice({
    name: 'mystate',
    initialState,  
    reducers: {
      ...
    },
}

For those for whom importing “immer” or turning “declaration” off did not work/is not an option:

If this happens for a slice: instead of exporting the slice directly, only export the specific attributes that are used to register the slice in the store:

const navigationSlicePrivate = createSlice({
  name: 'navigation',
  initialState,
  reducers: {
    /*....*/
  }
}

export const navigationSlice = {
  name: navigationSlicePrivate.name,
  reducer: navigationSlicePrivate.reducer
}

In our application, we have around 25 slices, and only a single one of those has this problem. Comparing the slices, there seem to be no obvious difference.

Hey the solution is to:

  • add immer to devDependencies
  • add import 'immer' on top of your file, this will not import anything at runtime but it will import the types to make the compiler happy

We could fix this inside redux-library by enforcing the type of slice.actions to not include WritableDraft as part of it (it’s an implementation detail either way)

In my case, unfortunately, none of these setups is working.

Attempts:

  1. not adding immer to devDependencies and import 'immer' on top of slice.ts file
  2. adding immer to devDependencies and import 'immer' on top of slice.ts file

The only setup works when I disable declaration in compilerOptions. However, can’t do it as the library is exportable. Are there any other settings I might miss, adding immer. Is it possible to share a git link of your minimal?

A solution might be adding "preserveSymlinks": true in the "compilerOptions" of tsconfig.json.

we don’t export the WriteableDraft type because we export the Draft type.

It also wouldn’t be correct for the case reducers to change Draft<T> to T - the point is that Draft<T> makes a readonly object mutable, and by changing that you would be able to unsafely pass a readonly state to a mutating case reducer. image

@sallyzoughaib that seems to be completely unrelated to this project - and honestly, it’s a very weird way of installing your dependencies. Even if runtime JavaScript might pick them up, TypeScript probably doesn’t.

I came across this same error when using createEntityAdapter(). The issue is the type of state in the reducer. Instead of letting the type be inferred to be a WritableDraft, you need to explicitly specify it. Based on how I fixed it in my code, I believe the code in the OP can be fixed as follows:

export interface POSState {
    addedPOSItems: POSItem[],
}


const initialState = {
    addedPOSItems: []
} as POSState;

export const POSSlice = createSlice({
    name: 'pos',
    initialState,
    reducers: {
        // Change `state` to `state: POSState`
        //                 vvvvvvvvvvvvvvv
        setAddedPOSItems: (state: POSState, action: PayloadAction<POSItem[]>) => {
            state.addedPOSItems = action.payload;
        }
    }
});

In my case, I had code that looks like this:

const fooAdapter = createEntityAdapter<FooModel>();
export const fooSlice = createSlice({
    name: "foo",
    initialState: fooAdapter.getInitialState({
        myValue: 0
    }),
    reducers: {
        addFoo: FooAdapter.addOne,
        setFoo: FooAdapter.setOne,
        updateFoo: FooAdapter.updateOne,
        removeFoo: FooAdapter.removeOne,
        // Adding this reducer causes the error
        setValue: (state, action: PayloadAction<number>) => {
            state.myValue = action.payload;
        }
    },
});

I fixed the error by adding an interface that declared the shape of the state object returned by fooAdapter and explicitly adding the type to state:

export interface FooState extends EntityState<FooModel> {
    myValue: number;
}

const fooAdapter = createEntityAdapter<FooModel>();
export const fooSlice = createSlice({
    name: "foo",
    initialState: fooAdapter.getInitialState({
        myValue: 0
    }),
    reducers: {
        addFoo: FooAdapter.addOne,
        setFoo: FooAdapter.setOne,
        updateFoo: FooAdapter.updateOne,
        removeFoo: FooAdapter.removeOne,
        setValue: (state: FooState, action: PayloadAction<number>) => {
            state.myValue = action.payload;
        }
    },
});

For me, none of the above works. pnpm monorepo with immer installed separately too.

Edit: Installing the same immer version as Redux toolkit uses, in this case it was immer@9.0.21, and adding import "immer" to the top of the file fixed it.

Thank you @srosato this worked!