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

Allow passing a default equality function to createSelectorHook #1656

Closed
ghost opened this issue Oct 30, 2020 · 8 comments
Closed

Allow passing a default equality function to createSelectorHook #1656

ghost opened this issue Oct 30, 2020 · 8 comments

Comments

@ghost
Copy link

ghost commented Oct 30, 2020

New Features

Allow passing a default equality function to createSelectorHook

What is the new or updated feature that you are suggesting?

I'd like to suggest adding a second argument to createSelectorHook to overwrite the default equality function.

Why should this feature be included?

My redux store consists for the most part out of custom classes from a functional programming library. This library, prelude-ts, relies on hash codes and an internal .equals() function on its objects to check equality between objects that do not share a reference:

const a = Vector.of(1, 2, 3);
const b = Vector.of(1, 2, 3);

console.log(a === b) // False
console.log(a.equals(b)) // True

I assume other functional programming languages do something similar, and even outside of an FP context, this seems like a moderately frequent use-case to me in a language that does not support operator overloading.

To ensure I don't rerender my components unnecessarily, I pass my own equality function for now:

// helpers.ts
const preludeEq = (a: HasEquals, b: any): boolean => a.equals(b);

// In some component:
import { useSelector } from 'react-redux';
import { preludeEq } from '../helpers';

const ProjectList = () => {
  const projects: Vector<string> = useSelector(store => store.project, preludeEq);

  return (
    <ul>
      {projects.map((name) => <li>{name}</li>)}
    </ul>
  );
}

I would like to instead be able to create my own useSelector hook and pass preludeEq as a default equality function. Something like:

// helpers.ts
const preludeEq = (a: HasEquals, b: any): boolean => a.equals(b);

// store.ts
import { preludeEq } from '../helpers';

export const useSelector = createSelectorHook(null, preludeEq);

// In some component:
import { useSelector } from "../store";

const ProjectList = () => {
  const projects: Vector<string> = useSelector(store => store.project);

  return (
    <ul>
      {projects.map((name) => <li>{name}</li>)}
    </ul>
  );
}

What docs changes are needed to explain this?

createSelectorHook actually doesn't seem to be referenced in the docs, but this might touch upon:

I'm more than willing to submit a pull request for this :) Since the first argument to createReactHook is optional, this change could be fully backward compatible.

@markerikson
Copy link
Contributor

On the one hand, I could see this easily being done in userland in much the same way we suggest creating a useAppDispatch that adds a known type signature.

On the other hand, I don't really see a reason why we shouldn't do this.

So sure, if you'd like to submit a PR, we can probably get that one. Would also be great if you could throw in some additional docs info on the custom hook creation as well while you're at it :)

@ghost
Copy link
Author

ghost commented Nov 2, 2020

Awesome, I'll get on it somewhere soon. As for the docs, could you outline the use case for passing your own Context? I don't immediately see it myself.

@markerikson
Copy link
Contributor

@veramone the only real use case for that is if you want to use multiple Redux stores within a single React component tree, so you need sections of the component tree to use store B instead of store A. Both connect and useSelector rely on accessing the Redux store instance via context, and the default behavior is to use the singleton ReactReduxContext instance we create internally. You could actually render <Provider store={storeB}> for a particular subtree and override the default context value for that section, but if you want to intermingle them or something, you'd have to mix contexts.

@antonobergklarna
Copy link

I think createUseSelector should take an object as a second param and not only the eqFunction. This will make it possible to add other options without breaking the api.

The reason I raise this now is that I will quite likely open a new issue in the coming days and pitch allowing passing i custom areStatesEqual function to createUseSelector.

@timdorr
Copy link
Member

timdorr commented Nov 30, 2020

@antonobergklarna No need. That's exactly what this issue is discussing.

@antonobergklarna
Copy link

@timdorr - My interpretation of this discussion is that we are talking about the equalityFn that is used to compare the result returned by the selector (i guess analog to areStatePropsEqual in connect). I want an areStatesEqual that will replace storeState !== latestStoreState.current and default to refEquality.

The second argument to createUseSelector could be an optional object where you could provide both
{ areStatesEqual, equalityFn }.

If an object notation feels like a good idea I think that it will also make sense to challenge the name equalityFn and use something like areSelectedPropsEqual or areDerivedDataEqual

@williamisnotdefined
Copy link

williamisnotdefined commented Jun 25, 2021

Sorry for my ignorance but..

const MySelector = (selector) => { return useSelector(selector, yourOwnEqualityFn) }

That hook doesnt solve the problem?

@timdorr
Copy link
Member

timdorr commented Jun 25, 2021

Actually, yeah, that would be much easier than changing our API. You need to create a hook anyways, and it doesn't have to come from the factory function.

@timdorr timdorr closed this as completed Jun 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants