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

adjust the hooks docs with more details about useSelector and add some more examples #1267

Merged
merged 4 commits into from May 20, 2019

Conversation

MrWolfZ
Copy link
Contributor

@MrWolfZ MrWolfZ commented Apr 30, 2019

I have adjusted the hooks docs with some more details regarding useSelector. I have also added some more examples.

I know some of these changes may be controversial, especially since it is not always a good idea to talk too much about implementation details in the docs. However, I feel in this case it is required due to the complexity of the stale props issue.

I don't expect all of these changes to be accepted, but I would like to discuss them anyways.

@netlify
Copy link

netlify bot commented Apr 30, 2019

Deploy preview for react-redux-docs ready!

Built with commit 0549388

https://deploy-preview-1267--react-redux-docs.netlify.com

@MrWolfZ
Copy link
Contributor Author

MrWolfZ commented Apr 30, 2019

Reading through the docs another time just now I realized that it doesn't highlight one of the primary advantages of hooks: composability. Does it make sense to add an example for useSelector and useActions to show how they can be used to create custom hooks?

@timdorr
Copy link
Member

timdorr commented Apr 30, 2019

Yeah, I think that would be a good idea. Thanks, Jon!

)

export const OtherTodoListItems = ({ id }) => {
const otherTodos = useSelector(s => allOtherTodos(s, id), [id])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have an in-depth understating of useSelector so I might be wrong, but I think that in this example the same allOtherTodos selector will be used in each render irrespectively if deps are provided or not. This is because the selector is created outside of OtherTodoListItems, so its reference never changes. The arrow function wrapping it will/will not change, but it does not really matter.

Additionally the selector will be shared across all instances of this OtherTodoListItems. Because reselect selectors have a cache of 1, this means allOtherTodos will always recompute (see last paragraph from https://github.com/reduxjs/reselect#accessing-react-props-in-selectors )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are of course right. When writing this example I was thinking whether I should use a factory selector instead but thought that might be too much added complexity. However, it seems that is necessary for the selector to work correctly. I'll adjust the example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to just move the selector into the component after all to keep it simple

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had more thoughts on the subject of deps. I have written them down in #1252 (comment)

@MrWolfZ
Copy link
Contributor Author

MrWolfZ commented May 2, 2019

Depending on the outcome of the useSelector deps discussion and also the useActions discussion I'll make further changes to this PR (including those custom hook examples I mentioned).

@timdorr
Copy link
Member

timdorr commented May 8, 2019

Yep, looks like those both need to be updated 😄

… usage with memoizing selectors; removed code example for custom `useActions` hook to not second-guess our own API design decision; some other minor docs tweaks
@MrWolfZ
Copy link
Contributor Author

MrWolfZ commented May 18, 2019

So, I finally got around to updating the docs. Please let me know what you think. I am especially interested in feedback regarding the memoizing selector examples. As far as I can see, there are in fact multiple ways how you can use them with component props. The useMemo approach I used in the examples felt most natural to me, since it only creates a single instance of the selector per component instance and you don't need to think about using the correct deps.

This is what I used in the docs for now:

const makeNrOfTodosWithIsDoneSelector = () =>
  createSelector(
    state => state.todos,
    (_, isDone) => isDone,
    (todos, isDone) => todos.filter(todo => todo.isDone === isDone).length
  )

export const TodoCounterForIsDoneValue = ({ isDone }) => {
  const selectNrOfTodosWithIsDone = useMemo(makeNrOfTodosWithIsDoneSelector, [])

  const nrOfTodosWithIsDoneValue = useSelector(state =>
    selectNrOfTodosWithIsDoneValue(state, isDone)
  )

  return <div>{nrOfTodosWithIsDoneValue}</div>
}

However, the other ways I see are these:

Creating a new instance of the selector for each new set of props (which makes the memoization part a bit more cumbersome but simplifies the useSelector call):

export const TodoCounterForIsDoneValue = ({ isDone }) => {
  const selectNrOfTodosWithIsDone = useMemo(
    () => {
      const selector = makeNrOfTodosWithIsDoneSelector();
      return state => selector(state, isDone)
    },
    [isDone]
  )

  const nrOfTodosWithIsDoneValue = useSelector(selectNrOfTodosWithIsDoneValue)
  return <div>{nrOfTodosWithIsDoneValue}</div>
}

Using a factory selector:

const makeNrOfTodosWithIsDoneSelector = isDone =>
  createSelector(
    state => state.todos,
    todos => todos.filter(todo => todo.isDone === isDone).length
  )

export const TodoCounterForIsDoneValue = ({ isDone }) => {
  const selectNrOfTodosWithIsDone = useCallback(
    makeNrOfTodosWithIsDoneSelector(isDone),
    [isDone]
  )

  const nrOfTodosWithIsDoneValue = useSelector(selectNrOfTodosWithIsDone)
  return <div>{nrOfTodosWithIsDoneValue}</div>
}

I am honestly not sure which should be the recommended way shown in the docs.

PS: I also feel I am missing an obvious way of doing this with useCallback but without a factory selector (I am sure I have seen examples using that, but can't find them right now).

@MrWolfZ
Copy link
Contributor Author

MrWolfZ commented May 18, 2019

@josepot I think you had some examples in your PR that removed the deps, but they aren't there anymore. I also remember you saying you want to comment on these changes since you disagree with some. Can you please have a look at the examples above and the changes in general and tell me your thoughts? Is there another way I missed?

import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const selectNrOfDoneTodos = createSelector(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is super-pedantic, but instead of "Nr" can you use "Num"? That's a more common shortening of "number".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


```jsx
import React from 'react'
import { useSelector } from 'react-redux'

export const TodoListItem = props => {
const todo = useSelector(state => state.todos[props.id], [props.id])

const todo = useSelector(state => state.todos[props.id])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that perhaps the docs in this case should encourage the usage of useCallback?

const todoSelector = useCallback(state => state.todos[props.id], [props.id])
const todo = useSelector(todoSelector)

FTR I actually think that this is a good practice regardless of what ends up happening with #1273 🙂

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why should we encourage usage of useCallback? I see no real benefit (the cost of creating new lamba per render is negligible) and it only adds complexity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep @lukeapage you are right. Actually, that would be likely to make perf even worse because the overhead of an extra hook. Yep, that was a brain-fart. Sorry!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josepot you meant @lukaszfiszer ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I did, indeed mean @lukaszfiszer ... Sorry about that! 😅

@josepot
Copy link
Contributor

josepot commented May 19, 2019

Thanks @MrWolfZ for pinging me into the discussion.

I have been thinking a lot about this and I think that perhaps it would be best if (for now) our docs don't even show an example of how to use useSelector with Reselect. I think that we should leave that to the Reselect team.

The reason for this is that I think that when the docs of Reselect were written, they had in mind the API of connect, and perhaps we are now making some assumptions about what the best way to leverage Reselect with React-Redux hooks are that are now outdated.

So, that would be my first preference.

If we think that it's still valuable to add that section into the docs, then I would prefer changing the example of the selector-creator so that it receives the parameter that it needs. I think that would be the right way to suggest using factory-selectors with hooks:

import React, { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const makeNumOfTodosWithIsDoneSelector = (isDone) =>
  createSelector(
    state => state.todos,
    (todos) => todos.filter(todo => todo.isDone === isDone).length
  )

export const TodoCounterForIsDoneValue = ({ isDone }) => {
  const selectNumOfTodosWithIsDone = useMemo(
    () => makeNumOfTodosWithIsDoneSelector(isDone),
    [isDone]
  )

  const NumOfTodosWithIsDoneValue = useSelector(selectNumOfTodosWithIsDone)

  return <div>{NumOfTodosWithIsDoneValue}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of done todos:</span>
      <TodoCounterForIsDoneValue isDone={true} />
      <span>Number of unfinished todos:</span>
      <TodoCounterForIsDoneValue isDone={false} />
    </>
  )
}

@lukaszfiszer
Copy link

Agreeing with @josepot that react-redux docs are not a good place for detailed examples of integration with a particular 3rd party library like reselect. It's rather reselect docs that should be updated with those examples as soon as v7.1 is published.

Currently connect docs contain exactly 0 examples with reselect - they only have few external links at the bottom. On the other hand, the whole documentation of reselect focuses on integration with connect.

@MrWolfZ
Copy link
Contributor Author

MrWolfZ commented May 19, 2019

@timdorr @markerikson what do you think? should we include reselect examples in the docs?

@markerikson
Copy link
Contributor

markerikson commented May 19, 2019

@lukaszfiszer , @MrWolfZ , @josepot : the only reason the React-Redux docs don't mention use of Reselect right now is because we just haven't gotten around to it. I've explicitly wanted to add pages on "performance optimization" (including use of selectors) since the start of the docs revamp - see #1001 .

#1068 was an attempt to adapt my post Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance as a docs page. I just closed that PR, as I wasn't entirely happy with how the content got tweaked.

However, all that said: I do think we should have at least some examples of using Reselect with useSelector(), especially now that I just merged the "ref equality" changes.

@markerikson markerikson merged commit 0ea274a into reduxjs:master May 20, 2019
webguru7 pushed a commit to webguru7/react-redux that referenced this pull request Dec 8, 2022
…e more examples (reduxjs#1267)

* adjust the hooks docs with more details about the inner workings of useSelector and add some more examples

* update hooks docs for removal of `useSelector` deps; add examples for usage with memoizing selectors; removed code example for custom `useActions` hook to not second-guess our own API design decision; some other minor docs tweaks

* in the hooks doc code examples change nr to num or number

* Update useSelector equality info and add hooks recipes
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

Successfully merging this pull request may close these issues.

None yet

6 participants