microsoft/TypeScript

Incorrectly infers DOM Map Instead of immutable Map inside a redux slice

Closed this issue ยท 5 comments

SCdF commented

Bug Report

๐Ÿ”Ž Search Terms

immutable map redux slice

๐Ÿ•— Version & Regression Information

  • This fails in some way in every version I tried, and I reviewed the FAQ for entries about
  • The error changes between versions 3.7.5 and 3.8.3 (versions available on playground)
  • 3.7.5 and prior can't infer the state type at all
  • 3.8.3 and after infer it incorrectly

โฏ Playground Link

Playground link with relevant code

๐Ÿ’ป Code

//  "dependencies": {
//    "@reduxjs/toolkit": "^1.6.1",
//    "immutable": "^4.0.0-rc.14",
//    "typescript": "next"
//  }
import { Map } from 'immutable';
import { createSlice } from '@reduxjs/toolkit';

// This works as you'd expect in all versions
let myMap = Map<string, number>();
myMap = myMap.set('abc', 1);
myMap = myMap.deleteAll(['abc']);
console.log(myMap);

// In a slice though the type binds to the default Map type
export const testSlice = createSlice({
  name: 'test',
  initialState: {
    stale: Map<string, number>(),
    staleLast: 0,
  },
  reducers: {
    // <=3.7.5 errors here: Parameter 'state' implicitly has an 'any' type.(7006)
    test: (state) => {
      // 3.8.3+: (property) stale: globalThis.Map<string, number>
      // Type 'void' is not assignable to type 'Map<string, number>'.(2322)
      state.stale = state.stale.clear();
      // 3.8.3+: Property 'deleteAll' does not exist on type 'Map<string, number>'.(2339)
      state.stale = state.stale.deleteAll(['test']);
    },
  },
});

export const { test } = testSlice.actions;
export default testSlice.reducer;

๐Ÿ™ Actual behavior

Typescript binds the wrong Map type, using the default DOM Map instead of the immutable Map declared. It definitely knows it a Map, it just gets the wrong one.

๐Ÿ™‚ Expected behavior

That it would bind to the correct Map type.

SCdF commented

Also note:

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "noEmit": true
  },
  "include": ["problem.ts"]
}

I also pushed this to a repository: https://github.com/SCdF/typescript-redux-immutable-map

nmain commented

This appears to be a problem with @reduxjs/toolkit's types. It creates a WritableDraft<S> from S as the parameter to a reducer, and their type decides that an immutable Map should be represented as a regular Map in that context.

It's not surprising to me that this doesn't work and never worked. immer is generally expecting to receive vanilla JS objects and mixing it with another immutable library sounds complicated.

SCdF commented

Cool since this isn't typescript I'll close this.

While I'm here are you saying that a regular Map will be immutable via immer in the context of the reducer?

nmain commented

That's what the @reduxjs/toolkit types claim, but they could be wrong. I'm honestly not sure; you might want to dig in starting at https://immerjs.github.io/immer/complex-objects.

SCdF commented

fwiw I think immer supports it but redux yells at you. I just went with type Foo = Record<string,number> instead