Shira
Shira

Reputation: 424

Immutable.js Combine with React Typescript

I'm having a hard time refactor my reducers to TS since I'm using Immutable.js in my project This is my reducer:

export interface DashboardState {
   componentId: number | null;
   dashboard: number | null;
}

let defaultState: DashboardState = {
   componentId: null,
   dashboard: null,
};

export default function (state = Map<string, number>(defaultState), action):  
Map<string, number> {
    switch (action.type) {
        case dashboardsActionsTypes.SET_PROJECT_VIEW:
            return state.set('componentId', action.payload);
        case dashboardsActionsTypes.SET_DASHBOARD_TYPE:
            return state.set('dashboard', action.payload);
        default:
            return state;
    }
}

I'm getting an error on the Map(defaultState) that says: enter image description here

What I'm missing here?

Thanks

Upvotes: 1

Views: 540

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42160

There are multiple ways to construct a Map.

export function Map<K, V>(collection: Iterable<[K, V]>): Map<K, V>;
export function Map<V>(obj: {[key: string]: V}): Map<string, V>;
export function Map<K, V>(): Map<K, V>;
export function Map(): Map<any, any>;

The error on the first overload is expected because you definitely shouldn't match that one. Your object is closer to the second overload, but you don't match that overload automatically because it involves a string index signature which DashboardState does not have.

The quick and dirty solution here involves setting a generic on the Map function. As you can see, the second overload is the only one with just <V> instead of <K, V>. So you can match this overload by setting just a value type for the Map. In this case the value type of DashboardState is any.

state = Map<any>(defaultState)

The type support with this setup is extremely weak. The keys of state can be any string and the values can be absolutely anything. Why bother using Typescript at all if you can't get any useful information out of it?

I recommend using the official Redux Toolkit which has strong typescript support as well as support for immutability using Immer. And replace the any in your DashboardState with the actual types.


Edit:

After viewing your edit, I recommend that you use optional properties (number | undefined) instead of number | null since Map.get() includes undefined along with the expected value. So you would get number | null | undefined. If using optional properties then you don't actually need an initial state at all. You can use an empty Map.

We can also limit the keys to just the keys of DashboardState.

Use these utilities on any state type:

export type MapOf<T> = Map<keyof T, T[keyof T]>;

const createMap = <T,>(): MapOf<T> => Map<keyof T, T[keyof T]>();

For the dashboard:

export interface DashboardState {
  componentId?: number;
  dashboard?: number;
}

export default function (state = createMap<DashboardState>(), action): MapOf<DashboardState> {
   switch (action.type) {
       case dashboardsActionsTypes.SET_PROJECT_VIEW:
           return state.set('componentId', action.payload);
       case dashboardsActionsTypes.SET_DASHBOARD_TYPE:
           return state.set('dashboard', action.payload);
       default:
           return state;
   }
}

Upvotes: 2

Related Questions