Assign correct types to Reselect createSelector function

Tristan Hamel picture Tristan Hamel · May 31, 2017 · Viewed 7.9k times · Source

Edit: added package.json excerpt

I am trying to implement typescript in an existing React project and I am facing difficulties with Reselect library. Typescript compiler insists on importing the first definition of createSelector function and fails recognize that my function's signature corresponds to another definition.

The definition it imports:

export function createSelector<S, R1, T>(
  selector: Selector<S, R1>,
  combiner: (res: R1) => T,
): OutputSelector<S, T, (res: R1) => T>;

The one I want to use:

export function createSelector<S, R1, R2, T>(
  selectors: [Selector<S, R1>,
              Selector<S, R2>],
  combiner: (res1: R1, res2: R2) => T,
): OutputSelector<S, T, (res1: R1, res2: R2) => T>;

Here is my actual code:

// groups.selectors.tsx

import { Selector, createSelector } from 'Reselect';
import { IGroup, IQuestionnaire, IGroupReselect, IState } from '../interfaces';

const getGroups:Selector<IState, IGroup[]> = state => state.groups;
const getQuestionnaires:Selector<IState, IQuestionnaire[]> = state => state.questionnaires;

export const groups = createSelector<IState, IGroup[], IQuestionnaire[], IGroupReselect>(
  [getGroups, getQuestionnaires],
  (g, q) => {

    return g.map(group => Object.assign(
      {},
      group,
      {questionnaires: group.questionnairesIds.map(id => q.find(q => q.id === id))}
    ));
  }
);

And in case it my be of any help, here is my ts.config:

{
  "compilerOptions": {
    "module": "es6",
    "target": "es6",
    "outDir": ".temp",
    "allowSyntheticDefaultImports": true,
    "baseUrl": "src",
    "noImplicitAny": false,
    "sourceMap": false,
    "jsx": "preserve",
    "strict": true,
    "moduleResolution": "node"
  },
  "exclude": [
    "node_modules"
  ],
  "files": [
    "typings.d.ts"
  ]
}

I am not completely comfortable with TypeScript so there is certainly something wrong in my implementation. What bugs me is that if I write the simplest possible reselector, that is one with only 1 selector and a combiner of arity 1, it passes type checking which gives me the impression that the compiler does not properly pick the correct definition among overloaded functions in Reselect's index.d.ts

Here are relevant parts of my package.json:

"dependencies": {
    "react": "^15.5.4",
    "react-dom": "^15.5.4",
    "react-redux": "^5.0.5",
    "react-router": "^4.1.1",
    "react-router-dom": "^4.1.1",
    "redux": "^3.6.0",
    "redux-devtools-extension": "^2.13.2",
    "redux-thunk": "^2.2.0",
    "reselect": "^3.0.1"
  },
  "devDependencies": {
    "@types/react": "^15.0.25",
    "@types/react-router-dom": "^4.0.4",
    "@types/redux-thunk": "^2.1.0",
    "typescript": "^2.3.3"
  },

Answer

Evan Sebastian picture Evan Sebastian · Jun 1, 2017

I might be wrong, but from this this issue, it seems to be caused by how the language infers your heterogenous array [getGroups, getQuestionnaires] as an array and not a tuple.

Forcing the first argument to tuple type will make it work but causes unnecessary boilerplate

const getGroups:Selector<IState, IGroup[]> = state => state.groups;
const getQuestionnaires:Selector<IState, IQuestionnaire[]> = state => 
  state.questionnaires;

const selectors: [Selector<IState, IGroup[]>, Selector<IState, IQuestionnaire[]>] = [getGroups, getQuestionnaires]

export const groups = createSelector<IState, IGroup[], IQuestionnaire[], IGroupReselect>(
  selectors,
  (g, q) => {

    return g.map(group => Object.assign(
      {},
      group,
      {questionnaires: group.questionnairesIds.map(id => q.find(q => q.id === id))}
    ));
  }
);