Skip to content

Commit

Permalink
Remove deps of useSelector
Browse files Browse the repository at this point in the history
  • Loading branch information
josepot committed May 2, 2019
1 parent 06a674e commit 6304f01
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 17 deletions.
64 changes: 59 additions & 5 deletions docs/api/hooks.md
Expand Up @@ -33,7 +33,7 @@ From there, you may import any of the listed React Redux hooks APIs and use them
## `useSelector()`

```js
const result : any = useSelector(selector : Function, deps : any[])
const result : any = useSelector(selector : Function)
```

Allows you to extract data from the Redux store state, using a selector function.
Expand All @@ -43,7 +43,6 @@ The selector is approximately equivalent to the [`mapStateToProps` argument to `
However, there are some differences between the selectors passed to `useSelector()` and a `mapState` function:

- The selector may return any value as a result, not just an object. The return value of the selector will be used as the return value of the `useSelector()` hook.
- The selector function used will be based on the `deps` array. If no deps array is provided, the latest passed-in selector function will be used when the component renders, and also when any actions are dispatched before the next render. If a deps array is provided, the last saved selector will be used, and that selector will be overwritten whenever the deps array contents have changed.
- When an action is dispatched, `useSelector()` will do a shallow comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, they component will not re-render.
- The selector function does _not_ receive an `ownProps` argument. If you wish to use props within the selector function to determine what values to extract, you should call the React [`useMemo()`](https://reactjs.org/docs/hooks-reference.html#usememo) or [`useCallback()`](https://reactjs.org/docs/hooks-reference.html#usecallback) hooks yourself to create a version of the selector that will be re-created whenever the props it depends on change.

Expand All @@ -68,16 +67,71 @@ export const CounterComponent = () => {
Using props to determine what to extract:

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

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

return <div>{todo.text}</div>
}
```
With Reselect:
```jsx
// ducks/todos.js
import { createSelector } from 'reselect'

export const selectPendingTodos = createSelector(
(state) => state.todos,
(todos) => todos.filter(todo => todo.completed !== true)
)

export const selectNPendingTodos = createSelector(
selectPendingTodos,
pendingTodos => pendingTodos.length
)

// component.js

import React from 'react'
import { useSelector } from 'react-redux'
import { selectNPendingTodos } from 'ducks/todos'

export constPendingTodos = () => {
const nPendingTodos = useSelector(selectNPendingTodos)
return <div>{nPendingTodos}</div>
}
```
With Reselect and factory-selectors:
```jsx
// ducks/todos.js
import { createSelector } from 'reselect'

export const makeSelectTodoById = (id) => createSelector(
(state) => state.todos,
(todos) => todos.filter(todo => todo.id === id)
)


// component.js

import React from 'react'
import { useSelector } from 'react-redux'
import { makeSelectTodoById } from 'ducks/todos'

export const Todo = ({id}) => {
const todoSelector = useMemo(() => makeSelectTodoById(id), [id])
const todo = useSelector(todoSelector)

return <div>{todo.name}</div>
}
```
## `useActions()`
```js
Expand Down
14 changes: 5 additions & 9 deletions src/hooks/useSelector.js
Expand Up @@ -24,8 +24,6 @@ const useIsomorphicLayoutEffect =
* useful if you provide a selector that memoizes values).
*
* @param {Function} selector the selector function
* @param {any[]} deps (optional) dependencies array to control referential stability
* of the selector
*
* @returns {any} the selected state
*
Expand All @@ -36,11 +34,11 @@ const useIsomorphicLayoutEffect =
* import { RootState } from './store'
*
* export const CounterComponent = () => {
* const counter = useSelector(state => state.counter, [])
* const counter = useSelector(state => state.counter)
* return <div>{counter}</div>
* }
*/
export function useSelector(selector, deps) {
export function useSelector(selector) {
invariant(selector, `You must pass a selector to useSelectors`)

const { store, subscription: contextSub } = useReduxContext()
Expand All @@ -51,15 +49,13 @@ export function useSelector(selector, deps) {
contextSub
])

const memoizedSelector = useMemo(() => selector, deps)

const latestSubscriptionCallbackError = useRef()
const latestSelector = useRef(memoizedSelector)
const latestSelector = useRef(selector)

let selectedState = undefined

try {
selectedState = memoizedSelector(store.getState())
selectedState = selector(store.getState())
} catch (err) {
let errorMessage = `An error occured while selecting the store state: ${
err.message
Expand All @@ -77,7 +73,7 @@ export function useSelector(selector, deps) {
const latestSelectedState = useRef(selectedState)

useIsomorphicLayoutEffect(() => {
latestSelector.current = memoizedSelector
latestSelector.current = selector
latestSelectedState.current = selectedState
latestSubscriptionCallbackError.current = undefined
})
Expand Down
7 changes: 4 additions & 3 deletions test/hooks/useSelector.spec.js
@@ -1,6 +1,6 @@
/*eslint-disable react/prop-types*/

import React, { useReducer } from 'react'
import React, { useReducer, useCallback } from 'react'
import { createStore } from 'redux'
import { renderHook, act } from 'react-hooks-testing-library'
import * as rtl from 'react-testing-library'
Expand Down Expand Up @@ -155,15 +155,16 @@ describe('React', () => {
expect(renderedItems.length).toBe(1)
})

it('re-uses the selector if deps do not change', () => {
it('re-uses the selector if it does not change', () => {
let selectorId = 0
let forceRender

const Comp = () => {
const [, f] = useReducer(c => c + 1, 0)
forceRender = f
const renderedSelectorId = selectorId++
const value = useSelector(() => renderedSelectorId, [])
const selector = useCallback(() => renderedSelectorId, [])
const value = useSelector(selector)
renderedItems.push(value)
return <div />
}
Expand Down

0 comments on commit 6304f01

Please sign in to comment.