react-redux icon indicating copy to clipboard operation
react-redux copied to clipboard

[Docs]: improve usage of second arg of `useSelector` (equality function)

Open ematipico opened this issue 5 years ago • 15 comments

The documentation talks about using shallowEqual as second argument for comparison (or lodash), although the documentation doesn't explain what are the arguments of this function and how a user could write their own custom equality function.

Is there a way to improve that part of the documentation?

ematipico avatar Jan 20 '20 14:01 ematipico

Yes, make a PR :)

timdorr avatar Jan 20 '20 16:01 timdorr

@timdorr Sure, please give me some guideline about what to write inside the documentation

ematipico avatar Jan 20 '20 16:01 ematipico

The TS type declarations over in DT are pretty simple:

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d459d36f4e1f47c3c6273b287718c79426e41a9f/types/react-redux/index.d.ts#L491

/**
 * Compares two arbitrary values for shallow equality. Object values are compared based on their keys, i.e. they must
 * have the same keys and for each key the value must be equal according to the `Object.is()` algorithm. Non-object
 * values are also compared with the same algorithm as `Object.is()`.
 */
export function shallowEqual(left: any, right: any): boolean;

markerikson avatar Jan 20 '20 16:01 markerikson

Easiest thing to do would be to replace the args on useSelector(selector: Function, equalityFn?: Function) with more specific types than just Function. Maybe just (a: any, b: any) => Boolean?

As an aside: In my mind, Function is pretty close to any, since you can put basically any function in there. I'm not sure what the internal type representation is there, but it would seem like defining (...args: any[]) => any, which is super duper loose.

Edit: Yep, what Mark said jibes with that.

timdorr avatar Jan 20 '20 16:01 timdorr

And yeah, when I wrote the Hooks docs page in the first place, I was still relatively new to TS. So, I wrote some pseudo-ish type declarations. Close enough to get across the general idea, but not actually based on real types. (That said, I also didn't think it was appropriate to try to spell out every last generic arg in the API docs either, for clarity.)

markerikson avatar Jan 20 '20 16:01 markerikson

I saw the types and the source code and I think the types are too generic. From what I understood, left and right are basically what's returned by the selector, right?

ematipico avatar Jan 20 '20 16:01 ematipico

No. shallowEqual is a standalone function that can be used separately, and it literally does accept any arguments. That's kind of the point of the function in the first place: "are these two things basically the same?".

markerikson avatar Jan 20 '20 16:01 markerikson

Docs start with const result: any = useSelector(selector: Function, equalityFn?: Function) which is quite understandable. And describes that the default is a "strict reference check".

That being said, a paragraph below casually uses shallowEqual and then casually references lodash. If no PR is forthcoming, I'd say, maybe close?

Personally I'd prefer it if redux had ready-made comparison functions for common cases available in a submodule...

dimaqq avatar Jul 13 '20 08:07 dimaqq

I understand that shallowEqual per se accepts two properties, but when used with useSelector, what's inside left and right?

By doing some logging, I can see left and right are the portion of the state selected by the selector.

ematipico avatar Jul 13 '20 12:07 ematipico

IMO the returns value of useSelector, i.e. selected state as that's what determines if the component will be rerendered.

I think it matters when only part of selected state is used by the components, for example: const {name, email} = useSelector(state => state.xxx.yyy.users[zzz]); Where user has other fields, e.g. address, but the component ignores them.

Edit: it's already documented so:

When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value.

dimaqq avatar Jul 13 '20 13:07 dimaqq

Yeah, it's the previous and current selected value that are passed to the compare function.

In other words, the point is to decide "has the selected value changed at all?" in order to determine if the component actually needs to re-render or not.

markerikson avatar Jul 13 '20 13:07 markerikson

It would be great if this equality function would be explained in plain ol' Javascript. Right now I'm stuck with a useless project because of this and it's been a few days now. As soon as I log in, dashboard renders without the data that was retrieved and stored in store, and I can see the error in the username (no username). As soon as I refresh the page, the username appears.

Please let me know if this is going to be addressed. Otherwise I'll just stick with react useContext.

alvamanu avatar Jun 30 '21 16:06 alvamanu

@alvamanu : The type signature is:

export type EqualityFn = (left: any, right: any) => boolean;

(loosely - I haven't added that specific type to the typedefs)

In other words:

  • it takes any two arguments
  • and returns a boolean indicating if they are equal or not

Per our source, the default equality check is a standard reference equality comparison:

const refEquality = (a, b) => a === b

Any other function that takes two values and returns a boolean is acceptable here.

I'll note that we did ask for someone from the community to file a docs PR a year and a half ago, and no one has done so yet :)

If you're having usage issues, your best bet is to ask on Stack Overflow or Reactiflux - we're happy to try to answer questions, but this issue isn't a good place to ask for help.

markerikson avatar Jun 30 '21 17:06 markerikson

Thank you @markerikson. I see you don't really have to use shallowEqual. useSelector takes two arguments, the second one being a function that takes two arguments:

the first argument being the past state of what's being selected, and the second one being the new state of what's being selected.

A simple (oldState, newState) => oldState === newState will suffice.

I tested this with two buttons, each dispatching a nameChange action with a different argument. Works great onclick. My issue is that even though store is updated on logging, new and old state are blank after the login redirect. I can see the store is updated in redux-logger, just not in my component. I want for my component to grab the username from the newly updated store and place it on component load.

That being said, I know now that's not the issue that I'm having so at least it's a step forward for me. Thanks again!

alvamanu avatar Jun 30 '21 17:06 alvamanu

Yeah, and that is the default behavior of useSelector already - it does that exact === comparison if you don't provide an equalityFn yourself

markerikson avatar Jun 30 '21 17:06 markerikson

I'm new to react-redux and was surprised to not find API reference for shallowEqual. IMO, the docs aren't complete without API references for each part of the library API.

I appreciate that this is an open-source project, and it's a lot easier to ask for something than it is to provide it :)

Perhaps after I've been using the library a bit longer I can take a stab at a PR.

dwaltrip avatar Nov 28 '22 19:11 dwaltrip

@dwaltrip : tbh it's a trivial enough function that there's never been a specific need to document it :) (Plus in this case you don't normally call it directly, you just import it and pass it as the second arg to useSelector.)

But yeah, happy to have a PR!

markerikson avatar Nov 28 '22 20:11 markerikson