microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

Home Page:https://www.typescriptlang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

SCdF opened this issue Β· comments

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.

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

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.

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?

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.

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