Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TypeScript] defaultMemoize errors #384

Closed
arthwood opened this issue Nov 22, 2018 · 14 comments
Closed

[TypeScript] defaultMemoize errors #384

arthwood opened this issue Nov 22, 2018 · 14 comments
Milestone

Comments

@arthwood
Copy link

arthwood commented Nov 22, 2018

Current TS definitions show error for the following usages:

import { isEqual } from 'lodash'

interface Transaction {
  transactionId: string
}

const toId = (transaction: Transaction) => transaction.transactionId
const transactionsIds = (transactions: Transaction[]) => transactions.map(toId)
const collectionsEqual = (ts1: Transaction[], ts2: Transaction[]) => isEqual(transactionsIds(ts1), transactionsIds(ts2))

const createTransactionsSelector = createSelectorCreator(defaultMemoize, collectionsEqual)

Results in:

Error:(148, 58) TS2345: Argument of type '<F extends Function>(func: F, equalityCheck?: (<T>(a: T, b: T, index: number) => boolean) | undefined) => F' is not assignable to parameter of type '<F extends Function>(func: F, option1: ((ts1: Transaction[], ts2: Transaction[]) => boolean) | undefined) => F'.
  Types of parameters 'equalityCheck' and 'option1' are incompatible.
    Types of parameters 'ts1' and 'a' are incompatible.
      Type 'T' is not assignable to type 'Transaction[]'.

This also happens when creating memoized function:

const groupTransactionsByLabel = defaultMemoize(
  (transactions: Transaction[]) => groupBy(transactions, headerForTransaction),
  collectionsEqual,
)

Possible solution:

When I change:

export function defaultMemoize<F extends Function>(
  func: F, equalityCheck?: <T>(a: T, b: T, index: number) => boolean,
): F;

to:

export function defaultMemoize<F extends Function, T>(
  func: F, equalityCheck?: (a: T, b: T, index: number) => boolean,
): F;

Everything works as expected but I'm not sure if that's correct approach.

@markov00
Copy link

I'm also facing this error, is there any news on that?

@martinschnurer
Copy link

any news? facing same error! really annoying

@oscarvalente
Copy link

Hey all! Wondering if this has had any updated at all? I'm looking foward to remove the ts-ignore I put 😄

@nathggns
Copy link

I have the same issue.

@tommedema
Copy link

Any update on this? It's been 1.5 years

@spassvogel
Copy link

More than 2 years now. I suppose this PR fixes it? #465

@markerikson
Copy link
Contributor

Is this still an issue with TS 4.x? If so, do either #465 or #480 fix the issue?

@eXamadeus
Copy link
Contributor

I don't think anything in #480 would fix this, but it does look like #465 might. Honestly, it's worth noting this edge case and making sure we have it in the type updates planned for v5.

Later I can verify if #465 would fix this. Either way, #465 is looking to get pulled into 4.x soon. If #465 doesn't fix it, we can probably find another non-breaking way to fix this for 4.x too.

@markerikson
Copy link
Contributor

markerikson commented Oct 16, 2021

I'm poking around at this atm as part of #485 . After dropping in the complete overhaul of the types for TS 4.2, tweaking the <T> param as listed in the original comment does not appear to change the error at all appears to fix the second issue of using defaultMemoize by itself, but doesn't fix createSelectorCreator.

@markerikson
Copy link
Contributor

Hmm. It seems like part of the issue here is that the TS types say that the equalityCheck signature is:

<T>(a: T, b: T, index: number) => boolean

But, if you look at the actual runtime behavior, there isn't any index value being passed to the equality function, and the code backs that up:

  for (let i = 0; i < length; i++) {
    if (!equalityCheck(prev[i], next[i])) {
      return false
    }
  }

Interestingly, we also have a pair of type tests that create dummy equality functions that expect an index parameter as well.

Digging back through the actual source history, it looks like that may have been accurate up until late 2016. Originally, the comparison was run by passing it to array.every():

  const isEqualToLastArg = (value, index) => equalityCheck(value, lastArgs[index])
  return (...args) => {
    if (
      lastArgs === null ||
      lastArgs.length !== args.length ||
      !args.every(isEqualToLastArg)
    ) {
      lastResult = func(...args)
    }
    lastArgs = args
    return lastResult
  }
}

which would have indeed passed through an index value as the third arg. That was changed in commit 1134dc1 , and I even see someone commenting on that commit about the change in argument behavior.

So, the typedefs have been broken there for five years :)

If I extract a separate EqualityFn type and pass through a generic arg, then add a specific overload for the usage of defaultMemoize, now the OP's example checks correctly:

export type EqualityFn<T> = (a: T, b: T /*, index: number*/) => boolean

export function defaultMemoize<F extends (...args: any[]) => any, T>(
  func: F, equalityCheck?: EqualityFn<T>,
): F;

export function createSelectorCreator(
  memoize: <F extends (...args: any[]) => any>(func: F) => F,
): typeof createSelector;


export function createSelectorCreator<T>(
  memoize: typeof defaultMemoize, 
  equalityCheck: EqualityFn<T>
) : typeof createSelector

I'm not sure if this is necessarily the "right" or "best" answer, but it seems to solve the issue.

markerikson added a commit to eXamadeus/reselect that referenced this issue Oct 16, 2021
- Extracted standalone `EqualityFn` type
- Removed incorrect `index` param from equality args
- Added specialized overload of `createSelectorCreator` specifically
  for usage with `defaultMemoize`, for better inference

Ref: reduxjs#384 (comment)
@markerikson
Copy link
Contributor

I just published https://github.com/reduxjs/reselect/releases/tag/v4.1.0-alpha.0 , which should fix this issue. Can folks try it out and give us feedback?

@markerikson markerikson added this to the 4.1 milestone Oct 17, 2021
@spassvogel
Copy link

It fixed my issue :)

@markerikson
Copy link
Contributor

@spassvogel Nice. I actually made further progress last night - in my local build, createSelectorCreator should now 100% infer the type of "the arguments to whatever memoize function you provide", and enforce those as the additional args you can pass in.

I hope to put another alpha this evening - would appreciate it if you could try that out as well.

@markerikson
Copy link
Contributor

Should be fixed by #486 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants