Skip to content

Commit 2ecb0b2

Browse files
committed
move useTrackedState docs into hooks.md
1 parent 9fcdbd3 commit 2ecb0b2

File tree

2 files changed

+176
-184
lines changed

2 files changed

+176
-184
lines changed

docs/api/hooks.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,182 @@ export const CounterComponent = ({ value }) => {
290290
}
291291
```
292292

293+
## `useTrackedState()`
294+
295+
### How does this get used?
296+
297+
`useTrackedState` allows components to read values from the Redux store state.
298+
It is similar to `useSelector`, but uses an internal tracking system
299+
to detect which state values are read in a component,
300+
without needing to define a selector function.
301+
302+
> **Note**: It doesn't mean to replace `useSelector` completely. It gives a new way of connecting Redux store to React.
303+
304+
The usage of `useTrackedState` is like the following.
305+
306+
```jsx
307+
import React from 'react'
308+
import { useTrackedState } from 'react-redux'
309+
310+
export const CounterComponent = () => {
311+
const { counter } = useTrackedState()
312+
return <div>{counter}</div>
313+
}
314+
```
315+
316+
If it needs to use props, it can be done so.
317+
318+
```jsx
319+
import React from 'react'
320+
import { useTrackedState } from 'react-redux'
321+
322+
export const TodoListItem = props => {
323+
const state = useTrackedState()
324+
const todo = state.todos[props.id]
325+
return <div>{todo.text}</div>
326+
}
327+
```
328+
329+
### Why would you want to use it?
330+
331+
#### For beginners
332+
333+
When learning Redux for the first time,
334+
it would be good to learn things step by step.
335+
`useTrackedState` allows developers to directly access the store state.
336+
They can learn selectors later for separation of concerns.
337+
338+
#### For intermediates
339+
340+
`useSelector` requires a selector to produce a stable value for performance.
341+
For example,
342+
343+
```js
344+
const selectUser = state => ({
345+
name: state.user.name,
346+
friends: state.user.friends.map(({ name }) => name),
347+
})
348+
const user = useSelector(selectUser)
349+
```
350+
351+
such a selector needs to be memoized to avoid extra re-renders.
352+
353+
`useTrackedState` doesn't require memoized selectors.
354+
355+
```js
356+
const state = useTrackedState()
357+
const user = selectUser(state)
358+
```
359+
360+
This works fine without extra re-renders.
361+
362+
Even a custom hook can be created for this purpose.
363+
364+
```js
365+
const useTrackedSelector = selector => selector(useTrackedState())
366+
```
367+
368+
This can be used instead of `useSelector` for some cases.
369+
370+
#### For experts
371+
372+
`useTrackedState` doesn't have [the technical issue](#stale-props-and-zombie-children) that `useSelector` has.
373+
This is because `useTrackedState` doesn't run selectors in checkForUpdates.
374+
375+
### What are the differences in behavior compared to useSelector?
376+
377+
#### Capabilities
378+
379+
A selector can create a derived values. For example:
380+
381+
```js
382+
const isYoung = state => state.person.age < 11;
383+
```
384+
385+
This selector computes a boolean value.
386+
387+
```js
388+
const young = useSelector(isYoung);
389+
```
390+
391+
With useSelector, a component only re-renders when the result of `isYoung` is changed.
392+
393+
```js
394+
const young = useTrackedState().person.age < 11;
395+
```
396+
397+
Whereas with useTrackedState, a component re-renders whenever the `age` value is changed.
398+
399+
#### Caveats
400+
401+
Proxy-based tracking has limitations.
402+
403+
- Proxied states are referentially equal only in per-hook basis
404+
405+
```js
406+
const state1 = useTrackedState();
407+
const state2 = useTrackedState();
408+
// state1 and state2 is not referentially equal
409+
// even if the underlying redux state is referentially equal.
410+
```
411+
412+
You should use `useTrackedState` only once in a component.
413+
414+
- An object referential change doesn't trigger re-render if an property of the object is accessed in previous render
415+
416+
```js
417+
const state = useTrackedState();
418+
const { foo } = state;
419+
return <Child key={foo.id} foo={foo} />;
420+
421+
const Child = React.memo(({ foo }) => {
422+
// ...
423+
};
424+
// if foo doesn't change, Child won't render, so foo.id is only marked as used.
425+
// it won't trigger Child to re-render even if foo is changed.
426+
```
427+
428+
It's recommended to use primitive values for props with memo'd components.
429+
430+
- Proxied state shouldn't be used outside of render
431+
432+
```js
433+
const state = useTrackedState();
434+
const dispatch = useUpdate();
435+
dispatch({ type: 'FOO', value: state.foo }); // This may lead unexpected behavior if state.foo is an object
436+
dispatch({ type: 'FOO', value: state.fooStr }); // This is OK if state.fooStr is a string
437+
```
438+
439+
It's recommended to use primitive values for `dispatch`, `setState` and others.
440+
441+
#### Performance
442+
443+
useSelector is sometimes more performant because Proxies are overhead.
444+
445+
useTrackedState is sometimes more performant because it doesn't need to invoke a selector when checking for updates.
446+
447+
### What are the limitations in browser support?
448+
449+
Proxies are not supported in old browsers like IE11, and React Native (JavaScript Core).
450+
451+
However, one could use [proxy-polyfill](https://github.com/GoogleChrome/proxy-polyfill) with care.
452+
453+
There are some limitations with the polyfill. Most notably, it will fail to track undefined properties.
454+
455+
```js
456+
const state = { count: 0 }
457+
458+
// this works with polyfill.
459+
state.count
460+
461+
// this won't work with polyfill.
462+
state.foo
463+
```
464+
465+
So, if the state shape is defined initially and never changed, it should be fine.
466+
467+
`Object.key()` and `in` operater is not supported. There might be other cases that polyfill doesn't support.
468+
293469
## Custom context
294470
295471
The `<Provider>` component allows you to specify an alternate context via the `context` 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.

docs/api/proxy-based-tracking.md

Lines changed: 0 additions & 184 deletions
This file was deleted.

0 commit comments

Comments
 (0)