Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble with TypeScript typing for store enhancers in redux 4.0

Can some TypeScript master help me figure out why this code to create store enhancers in redux 4.0 is giving me type errors?

The minimal repo is here. It's creating a do-nothing store ehnahcer. After cloning, install dependencies by running yarn. To see the error, run yarn run compile.

The relevant file is

import {
  StoreEnhancer,
  StoreEnhancerStoreCreator,
  Reducer,
  DeepPartial,
  AnyAction,
} from 'redux';

interface RootState {
  someKey: string;
}

export const enhancer: StoreEnhancer =
  (createStore: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator =>
    (reducer: Reducer<RootState, AnyAction>, preloadedState?: DeepPartial<RootState>) => {

  const store = createStore(reducer, preloadedState);
  const newDispatch: typeof store.dispatch = <A extends AnyAction>(action: A) => {
    const result = store.dispatch(action);
    return result;
  }
  return {
    ...store,
    dispatch: newDispatch,
  };
};

The error I'm getting is

src/enhancer.ts(15,5): error TS2322: Type '(reducer: Reducer<RootState, AnyAction>, preloadedState?: DeepPartial<RootState> | undefined) => ...' is not assignable to type 'StoreEnhancerStoreCreator<{}, {}>'.
  Types of parameters 'reducer' and 'reducer' are incompatible.
    Types of parameters 'state' and 'state' are incompatible.
      Type 'RootState | undefined' is not assignable to type 'S | undefined'.
        Type 'RootState' is not assignable to type 'S | undefined'.
          Type 'RootState' is not assignable to type 'S'.

I don't understand why RootState is not assignable to S.

like image 575
Huy Nguyen Avatar asked May 21 '18 15:05

Huy Nguyen


People also ask

Does Redux work with TypeScript?

We strongly recommend using TypeScript in Redux applications. However, like all tools, TypeScript has tradeoffs. It adds complexity in terms of writing additional code, understanding TS syntax, and building the application.

What are store enhancers in Redux?

And an enhancer is basically if we wanted to add additional functionality to a Redux store. This is a place where we can pass in a function which will get an instance of createStore, allow us to do whatever we want and then pass all the arguments into it.

What are reducers in TypeScript?

Reducers are a type of function that can perform these task for state management. Because Reducers are built into the React API, you don't necessarily have to use a separate state-management library like Redux or MobX.


1 Answers

Lets deep dive at how StoreEnhancerStoreCreator is defined

export type StoreEnhancerStoreCreator<Ext = {}, StateExt = {}> = <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, preloadedState?: DeepPartial<S>) => Store<S & StateExt, A> & Ext;

This is generic type with two type arguments, Ext and StateExt. The result of this type is another generic function. For example if we do

const genericStoreCreator: StoreEnhancerStoreCreator = <S = any, A extends Action = AnyAction>(r: Reducer<S, A>) => createStore(r)

genericStoreCreator will still be generic function. We already used generic type StoreEnhancerStoreCreator without providing type arguments to it (with default type values), but result is still generic function.

Then we may call genericStoreCreator providing specific type arguments for S and A.

const store = genericStoreCreator((state: RootState = { someKey: 'someKey' }, action: AnyAction) => state)

And now we have specific store without any generics.

You may consider StoreEnhancerStoreCreator as generic inside generic.

Now lets get back to store enhancers. From source (didn't find it in documentation)

A store enhancer is a higher-order function that composes a store creator to return a new, enhanced store creator.

So result of store enhancer should still be generic store creator. Inside enhancer function we shouldn't relate on specific shape of state (i.e. it have someKey property) or specific shape of action (except it has type property). When implementing enhancer we don't know what exact store it will enhance.

To make your code working, just carry generics inside enhancer code

export const enhancer: StoreEnhancer =
  (createStore: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator =>
    <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, preloadedState?: DeepPartial<RootState>) => {

  const store = createStore(reducer, preloadedState);
  const newDispatch: Dispatch<A> = (action) => {
    const result = store.dispatch(action);
    return result;
  }
  return {
    ...store,
    dispatch: newDispatch,
   };
};

Take a note that enhanced store creator is still generic and can be user with any state and actions.

But what can we enhance with enhancer? As source says

@template Ext Store extension that is mixed into the Store type.

@template StateExt State extension that is mixed into the state type.

Lets extend store with print5 function which when called will log 5 to console.

export const enhancer: StoreEnhancer<{ print5: () => void }> =
  (createStore: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator<{ print5: () => void }> =>
    <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, preloadedState?: DeepPartial<RootState>) => {

  const store = createStore(reducer, preloadedState);
  const newDispatch: Dispatch<A> = (action) => {
    const result = store.dispatch(action);
    return result;
  }
  return {
    ...store,
    dispatch: newDispatch,
    print5 () { console.log(5) },
   };
  };

Again take a note that resulting store is still generic.

And here is simple demo. Take a note how print5 function is propagated from enhancer to resulting store.

like image 79
Fyodor Avatar answered Sep 28 '22 04:09

Fyodor