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
Conversation
Deploy preview for react-redux-docs ready! Built with commit 0549388 |
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 |
Yeah, I think that would be a good idea. Thanks, Jon! |
docs/api/hooks.md
Outdated
) | ||
|
||
export const OtherTodoListItems = ({ id }) => { | ||
const otherTodos = useSelector(s => allOtherTodos(s, id), [id]) |
There was a problem hiding this comment.
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 )
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
Depending on the outcome of the |
Yep, looks like those both need to be updated 😄 |
…seSelector and add some more examples
36a6123
to
8400f56
Compare
… 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
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 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 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 |
@josepot I think you had some examples in your PR that removed the |
docs/api/hooks.md
Outdated
import { useSelector } from 'react-redux' | ||
import { createSelector } from 'reselect' | ||
|
||
const selectNrOfDoneTodos = createSelector( |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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]) |
There was a problem hiding this comment.
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 🙂
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@josepot you meant @lukaszfiszer ?
There was a problem hiding this comment.
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! 😅
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 The reason for this is that I think that when the docs of Reselect were written, they had in mind the API of 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} />
</>
)
} |
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 |
@timdorr @markerikson what do you think? should we include |
@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 |
…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
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.