Movatterモバイル変換


[0]ホーム

URL:


Skip to main content
Redux.dev - a new course by Mark Erikson + ui.dev - Learn more

Hooks

React's"hooks" APIs give function components the ability to use local component state, execute side effects, and more. React also lets us writecustom hooks, which let us extract reusable hooks to add our own behavior on top of React's built-in hooks.

React Redux includes its own custom hook APIs, which allow your React components to subscribe to the Redux store and dispatch actions.

tip

We recommend using the React-Redux hooks API as the default approach in your React components.

The existingconnect API still works and will continue to be supported, but the hooks API is simpler and works better with TypeScript.

These hooks were first added in v7.1.0.

Using Hooks in a React Redux App

As withconnect(), you should start by wrapping your entire application in a<Provider> component to make the store available throughout the component tree:

const store=createStore(rootReducer)

// As of React 18
const root=ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Providerstore={store}>
<App/>
</Provider>,
)

From there, you may import any of the listed React Redux hooks APIs and use them within your function components.

useSelector()

typeRootState= ReturnType<typeof store.getState>
typeSelectorFn=<Selected>(state: RootState)=> Selected
typeEqualityFn=(a:any, b:any)=>boolean
exporttypeDevModeCheckFrequency='never'|'once'|'always'

interfaceUseSelectorOptions{
equalityFn?: EqualityFn
devModeChecks?:{
stabilityCheck?: DevModeCheckFrequency
identityFunctionCheck?: DevModeCheckFrequency
}
}

const result: Selected=useSelector(
selector: SelectorFn,
options?: EqualityFn| UseSelectorOptions
)

Allows you to extract data from the Redux store state for use in this component, using a selector function.

info

The selector function should bepure since it is potentially executed multiple times and at arbitrary points in time.

SeeUsing Redux: Deriving Data with Selectors in the Redux docs for more details on writing and using selector functions.

The selector will be called with the entire Redux store state as its only argument. The selector may return any value as a result, including directly returning a value that was nested insidestate, or deriving new values. The return value of the selector will be used as the return value of theuseSelector() hook.

The selector will be run whenever the function component renders (unless its reference hasn't changed since a previous render of the component so that a cached result can be returned by the hook without re-running the selector).useSelector() will also subscribe to the Redux store, and run your selector whenever an action is dispatched.

When an action is dispatched,useSelector() will do a reference 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, the component will not re-render.useSelector() uses strict=== reference equality checks by default, not shallow equality (see the following section for more details).

The selector is approximately equivalent to themapStateToProps argument toconnect conceptually.

You may calluseSelector() multiple times within a single function component. Each call touseSelector() creates an individual subscription to the Redux store. Because of the React update batching behavior used in React Redux v7, a dispatched action that causes multipleuseSelector()s in the same component to return new valuesshould only result in a single re-render.

info

There are potential edge cases with using props in selectors that may cause issues. See theUsage Warnings section of this page for further details.

Equality Comparisons and Updates

When the function component renders, the provided selector function will be called and its result will be returnedfrom theuseSelector() hook. (A cached result may be returned by the hook without re-running the selector if it's the same function reference as on a previous render of the component.)

However, when an action is dispatched to the Redux store,useSelector() only forces a re-render if the selector resultappears to be different than the last result. The default comparison is a strict=== referencecomparison. This is different thanconnect(), which uses shallow equality checks on the results ofmapState callsto determine if re-rendering is needed. This has several implications on how you should useuseSelector().

WithmapState, all individual fields were returned in a combined object. It didn't matter if the return object wasa new reference or not -connect() just compared the individual fields. WithuseSelector(), returning a new objectevery time willalways force a re-render by default. If you want to retrieve multiple values from the store, you can:

  • CalluseSelector() multiple times, with each call returning a single field value
  • Use Reselect or a similar library to create a memoized selector that returns multiple values in one object, butonly returns a new object when one of the values has changed.
  • Use theshallowEqual function from React-Redux as theequalityFn argument touseSelector(), like:
import{ shallowEqual, useSelector}from'react-redux'

// Pass it as the second argument directly
const selectedData=useSelector(selectorReturningObject, shallowEqual)

// or pass it as the `equalityFn` field in the options argument
const selectedData=useSelector(selectorReturningObject,{
equalityFn: shallowEqual,
})
  • Use a custom equality function as theequalityFn argument touseSelector(), like:
import{ useSelector}from'react-redux'

// equality function
constcustomEqual=(oldValue, newValue)=> oldValue=== newValue

// later
const selectedData=useSelector(selectorReturningObject, customEqual)

The optional comparison function also enables using something like Lodash's_.isEqual() or Immutable.js's comparison capabilities.

useSelector Examples

Basic usage:

importReactfrom'react'
import{ useSelector}from'react-redux'

exportconstCounterComponent=()=>{
const counter=useSelector((state)=> state.counter)
return<div>{counter}</div>
}

Using props via closure to determine what to extract:

importReactfrom'react'
import{ useSelector}from'react-redux'

exportconstTodoListItem=(props)=>{
const todo=useSelector((state)=> state.todos[props.id])
return<div>{todo.text}</div>
}

Using memoizing selectors

When usinguseSelector with an inline selector as shown above, a new instance of the selector is created whenever the component is rendered. This works as long as the selector does not maintain any state. However, memoizing selectors (e.g. created viacreateSelector fromreselect) do have internal state, and therefore care must be taken when using them. Below you can find typical usage scenarios for memoizing selectors.

When the selector does only depend on the state, simply ensure that it is declared outside of the component so that the same selector instance is used for each render:

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

const selectNumCompletedTodos=createSelector(
(state)=> state.todos,
(todos)=> todos.filter((todo)=> todo.completed).length,
)

exportconstCompletedTodosCounter=()=>{
const numCompletedTodos=useSelector(selectNumCompletedTodos)
return<div>{numCompletedTodos}</div>
}

exportconstApp=()=>{
return(
<>
<span>Number of completed todos:</span>
<CompletedTodosCounter/>
</>
)
}

The same is true if the selector depends on the component's props, but will only ever be used in a single instance of a single component:

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

const selectCompletedTodosCount=createSelector(
(state)=> state.todos,
(_, completed)=> completed,
(todos, completed)=>
todos.filter((todo)=> todo.completed=== completed).length,
)

exportconstCompletedTodosCount=({ completed})=>{
const matchingCount=useSelector((state)=>
selectCompletedTodosCount(state, completed),
)

return<div>{matchingCount}</div>
}

exportconstApp=()=>{
return(
<>
<span>Number of done todos:</span>
<CompletedTodosCountcompleted={true}/>
</>
)
}

However, when the selector is used in multiple component instances and depends on the component's props, you need to ensure that selector's memoization behavior is properly configured (seehere for details).

Development mode checks

useSelector runs some extra checks in development mode to watch for unexpected behavior. These checks do not run in production builds.

info

These checks were first added in v8.1.0

Selector result stability

In development, the provided selector function is run an extra time with the same parameter during the first call touseSelector, and warns in the console if the selector returns a different result (based on theequalityFn provided).

This is important, asa selector that returns a different result reference when called again with the same inputs will cause unnecessary rerenders.

// this selector will return a new object reference whenever called,
// which causes the component to rerender after *every* action is dispatched
const{ count, user}=useSelector((state)=>({
count: state.count,
user: state.user,
}))

If a selector result is suitably stable (or the selector is memoized), it will not return a different result and no warning will be logged.

By default, this will only happen when the selector is first called. You can configure the check in the Provider or at eachuseSelector call.

Global setting via context
<Providerstore={store}stabilityCheck="always">
{children}
</Provider>
Individual hook setting
functionComponent(){
const count=useSelector(selectCount,{
devModeChecks:{ stabilityCheck:'never'},
})
// run once (default)
const user=useSelector(selectUser,{
devModeChecks:{ stabilityCheck:'once'},
})
// ...
}

Identity Function (state => state) Check

Breaking Change!

This was previously referred to asnoopCheck.

In development, a check is conducted on the result returned by the selector. It warns in the console if the result is the same as the parameter passed in, i.e. the root state.

AuseSelector call returning the entire root state is almost always a mistake, as it means the component will rerender wheneveranything in state changes. Selectors should be as granular as possible, likestate => state.some.nested.field.

// BAD: this selector returns the entire state, meaning that the component will rerender unnecessarily
const{ count, user}=useSelector((state)=> state)

// GOOD: instead, select only the state you need, calling useSelector as many times as needed
const count=useSelector((state)=> state.count.value)
const user=useSelector((state)=> state.auth.currentUser)

By default, this will only happen when the selector is first called. You can configure the check in the Provider or at eachuseSelector call.

Global setting via context
<Providerstore={store}identityFunctionCheck="always">
{children}
</Provider>
Individual hook setting
functionComponent(){
const count=useSelector(selectCount,{
devModeChecks:{ identityFunctionCheck:'never'},
})
// run once (default)
const user=useSelector(selectUser,{
devModeChecks:{ identityFunctionCheck:'once'},
})
// ...
}

Comparisons withconnect

There are some differences between the selectors passed touseSelector() and amapState function:

  • The selector may return any value as a result, not just an object.
  • The selector normallyshould return just a single value, and not an object. If you do return an object or an array, be sure to use a memoized selector to avoid unnecessary re-renders.
  • The selector function doesnot receive anownProps argument. However, props can be used through closure (see the examples above) or by using a curried selector.
  • You can use theequalityFn option to customize the comparison behavior

useDispatch()

importtype{ Dispatch}from'redux'
const dispatch: Dispatch=useDispatch()

This hook returns a reference to thedispatch function from the Redux store. You may use it to dispatch actions as needed.

Examples

importReactfrom'react'
import{ useDispatch}from'react-redux'

exportconstCounterComponent=({ value})=>{
const dispatch=useDispatch()

return(
<div>
<span>{value}</span>
<buttononClick={()=>dispatch({type:'increment-counter'})}>
Increment counter
</button>
</div>
)
}

When passing a callback usingdispatch to a child component, you may sometimes want to memoize it withuseCallback.If the child component is trying to optimize render behavior usingReact.memo() or similar, this avoids unnecessary rendering of child components due to the changed callback reference.

importReact,{ useCallback}from'react'
import{ useDispatch}from'react-redux'

exportconstCounterComponent=({ value})=>{
const dispatch=useDispatch()
const incrementCounter=useCallback(
()=>dispatch({type:'increment-counter'}),
[dispatch],
)

return(
<div>
<span>{value}</span>
<MyIncrementButtononIncrement={incrementCounter}/>
</div>
)
}

exportconstMyIncrementButton=React.memo(({ onIncrement})=>(
<buttononClick={onIncrement}>Increment counter</button>
))
info

Thedispatch function reference will be stable as long as the same store instance is being passed to the<Provider>.Normally, that store instance never changes in an application.

However, the React hooks lint rules do not know thatdispatch should be stable, and will warn that thedispatch variableshould be added to dependency arrays foruseEffect anduseCallback. The simplest solution is to do just that:

exportconstTodos=()=>{
const dispatch=useDispatch()

useEffect(()=>{
dispatch(fetchTodos())
// Safe to add dispatch to the dependencies array
},[dispatch])
}

useStore()

importtype{ Store}from'redux'
const store: Store=useStore()

This hook returns a reference to the same Redux store that was passed in to the<Provider> component.

This hook should probably not be used frequently. PreferuseSelector() as your primary choice. However, this may be useful for less common scenarios that do require access to the store, such as replacing reducers.

Examples

importReactfrom'react'
import{ useStore}from'react-redux'

exportconstExampleComponent=({ value})=>{
const store=useStore()

constonClick=()=>{
// Not _recommended_, but safe
// This avoids subscribing to the state via `useSelector`
// Prefer moving this logic into a thunk instead
const numTodos= store.getState().todos.length
}

// EXAMPLE ONLY! Do not do this in a real app.
// The component will not automatically update if the store state changes
return<div>{store.getState().todos.length}</div>
}

Custom context

The<Provider> component allows you to specify an alternate context via thecontext prop. This is useful if you're building a complex reusable component, and you don't want your store to collide with any Redux store your consumers' applications might use.

To access an alternate context via the hooks API, use the hook creator functions:

importReactfrom'react'
import{
Provider,
createStoreHook,
createDispatchHook,
createSelectorHook,
}from'react-redux'

constMyContext=React.createContext(null)

// Export your custom hooks if you wish to use them in other files.
exportconst useStore=createStoreHook(MyContext)
exportconst useDispatch=createDispatchHook(MyContext)
exportconst useSelector=createSelectorHook(MyContext)

const myStore=createStore(rootReducer)

exportfunctionMyProvider({ children}){
return(
<Provider context={MyContext} store={myStore}>
{children}
</Provider>
)
}

Usage Warnings

Stale Props and "Zombie Children"

info

The React-Redux hooks API has been production-ready since we released it in v7.1.0, andwe recommend using the hooks API as the default approach in your components. However, there are a couple of edge cases that can occur, andwe're documenting those so that you can be aware of them.

In practice, these are a rare concern - we've received far more comments about these being in the docs than actual reports of these being a real problem in an app.

One of the most difficult aspects of React Redux's implementation is ensuring that if yourmapStateToProps function is defined as(state, ownProps), it will be called with the "latest" props every time. Up through version 4, there were recurring bugs reported involving edge case situations, such as errors thrown from amapState function for a list item whose data had just been deleted.

Starting with version 5, React Redux has attempted to guarantee that consistency withownProps. In version 7, that is implemented using a customSubscription class internally inconnect(), which forms a nested hierarchy. This ensures that connected components lower in the tree will only receive store update notifications once the nearest connected ancestor has been updated. However, this relies on eachconnect() instance overriding part of the internal React context, supplying its own uniqueSubscription instance to form that nesting, and rendering the<ReactReduxContext.Provider> with that new context value.

With hooks, there is no way to render a context provider, which means there's also no nested hierarchy of subscriptions. Because of this, the "stale props" and "zombie child" issues may potentially re-occur in an app that relies on using hooks instead ofconnect().

Specifically, "stale props" means any case where:

  • a selector function relies on this component's props to extract data
  • a parent componentwould re-render and pass down new props as a result of an action
  • but this component's selector function executes before this component has had a chance to re-render with those new props

Depending on what props were used and what the current store state is, thismay result in incorrect data being returned from the selector, or even an error being thrown.

"Zombie child" refers specifically to the case where:

  • Multiple nested connected components are mounted in a first pass, causing a child component to subscribe to the store before its parent
  • An action is dispatched that deletes data from the store, such as a todo item
  • The parent componentwould stop rendering that child as a result
  • However, because the child subscribed first, its subscription runs before the parent stops rendering it. When it reads a value from the store based on props, that data no longer exists, and if the extraction logic is not careful, this may result in an error being thrown.

useSelector() tries to deal with this by catching all errors that are thrown when the selector is executed due to a store update (but not when it is executed during rendering). When an error occurs, the component will be forced to render, at which point the selector is executed again. This works as long as the selector is a pure function and you do not depend on the selector throwing errors.

If you prefer to deal with this issue yourself, here are some possible options for avoiding these problems altogether withuseSelector():

  • Don't rely on props in your selector function for extracting data
  • In cases where you do rely on props in your selector functionand those props may change over time,or the data you're extracting may be based on items that can be deleted, try writing the selector functions defensively. Don't just reach straight intostate.todos[props.id].name - readstate.todos[props.id] first, and verify that it exists before trying to readtodo.name.
  • Becauseconnect adds the necessarySubscription to the context provider and delays evaluating child subscriptions until the connected component has re-rendered, putting a connected component in the component tree just above the component usinguseSelector will prevent these issues as long as the connected component gets re-rendered due to the same store update as the hooks component.

Performance

As mentioned earlier, by defaultuseSelector() will do a reference equality comparison of the selected value when running the selector function after an action is dispatched, and will only cause the component to re-render if the selected value changed. However, unlikeconnect(),useSelector() does not prevent the component from re-rendering due to its parent re-rendering, even if the component's props did not change.

If further performance optimizations are necessary, you may consider wrapping your function component inReact.memo():

constCounterComponent=({ name})=>{
const counter=useSelector((state)=> state.counter)
return(
<div>
{name}:{counter}
</div>
)
}

exportconstMemoizedCounterComponent=React.memo(CounterComponent)

Hooks Recipes

We've pared down our hooks API from the original alpha release, focusing on a more minimal set of API primitives.However, you may still wish to use some of the approaches we tried in your own apps. These examples should be readyto copy and paste into your own codebase.

Recipe:useActions()

This hook was in our original alpha release, but removed inv7.1.0-alpha.4, based onDan Abramov's suggestion.That suggestion was based on "binding action creators" not being as useful in a hooks-based use case, and causing toomuch conceptual overhead and syntactic complexity.

You should probably prefer to call theuseDispatch hook in your components to retrieve a reference todispatch,and manually calldispatch(someActionCreator()) in callbacks and effects as needed. You may also use the ReduxbindActionCreators function in your own code to bind action creators,or "manually" bind them likeconst boundAddTodo = (text) => dispatch(addTodo(text)).

However, if you'd like to still use this hook yourself, here's a copy-pastable version that supports passing in actioncreators as a single function, an array, or an object.

import{ bindActionCreators}from'redux'
import{ useDispatch}from'react-redux'
import{ useMemo}from'react'

exportfunctionuseActions(actions, deps){
const dispatch=useDispatch()
returnuseMemo(
()=>{
if(Array.isArray(actions)){
return actions.map((a)=>bindActionCreators(a, dispatch))
}
returnbindActionCreators(actions, dispatch)
},
deps?[dispatch,...deps]:[dispatch],
)
}

Recipe:useShallowEqualSelector()

import{ useSelector, shallowEqual}from'react-redux'

exportfunctionuseShallowEqualSelector(selector){
returnuseSelector(selector, shallowEqual)
}

Additional considerations when using hooks

There are some architectural trade offs to take into consideration when deciding whether to use hooks or not. Mark Erikson summarizes these nicely in his two blog postsThoughts on React Hooks, Redux, and Separation of Concerns andHooks, HOCs, and Tradeoffs.


[8]ページ先頭

©2009-2025 Movatter.jp