Skip to content

Commit

Permalink
breaking: make it compatible with memo and react compiler (#202)
Browse files Browse the repository at this point in the history
* reuse affected if the state is not changed from the previous one

* update snapshot

* per-hook affected

* remove memo

* update docs

* v2.0.0-beta.1

* v2.0.0-beta.2

* v2.0.0-beta.3

* update CHANGELOG
  • Loading branch information
dai-shi authored Apr 29, 2024
1 parent ceec9af commit 0542bec
Show file tree
Hide file tree
Showing 12 changed files with 34 additions and 67 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Change Log

## [Unreleased]
### Added
- breaking: make it compatible with memo and react compiler #202
### Removed
- `memo` is removed as it is no longer necessary with #202

## [1.7.14] - 2024-03-08
### Changed
Expand Down
14 changes: 7 additions & 7 deletions __tests__/e2e/__snapshots__/14_dynamic.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -42,55 +42,55 @@ exports[`14_dynamic should work with recorded events 5`] = `
exports[`14_dynamic should work with recorded events 6`] = `
"
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 36<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 2<div>First Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 4<div>First Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 33<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 2<div>First Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 4<div>First Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
"
`;
exports[`14_dynamic should work with recorded events 7`] = `
"
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 36<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 2<div>First Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 4<div>First Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 33<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 2<div>First Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 4<div>First Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
"
`;
exports[`14_dynamic should work with recorded events 8`] = `
"
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 36<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 34<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 36<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 33<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 34<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 36<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
"
`;
exports[`14_dynamic should work with recorded events 9`] = `
"
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 36<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 38<div>Last Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 36<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 33<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 38<div>Last Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 36<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
"
`;
exports[`14_dynamic should work with recorded events 10`] = `
"
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 36<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 38<div>Last Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 36<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 33<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 38<div>Last Name:<input value=""><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 36<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
"
`;
exports[`14_dynamic should work with recorded events 11`] = `
"
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 36<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 44<div>Last Name:<input value="b"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 36<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 33<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 44<div>Last Name:<input value="b"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 36<div>First Name:<input value="a"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
"
`;
exports[`14_dynamic should work with recorded events 12`] = `
"
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 36<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 44<div>Last Name:<input value="b"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 52<div>Last Name:<input value="b"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
<div id="app"><h1>Counter</h1><div>numRendered: 28<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><div>numRendered: 33<div><span>Count: -1</span><button type="button">+1</button><button type="button">-1</button><input value="1"></div></div><h1>Person</h1><div>numRendered: 44<div>Last Name:<input value="b"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div><div>numRendered: 49<div>Last Name:<input value="b"><button type="button">toggle</button></div><div>Age:<input value="0"></div></div></div>
"
Expand Down
4 changes: 1 addition & 3 deletions examples/09_reactmemo/src/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { memo } from 'react-tracked';

import { useDispatch, TodoType } from './store';

Expand All @@ -9,8 +8,7 @@ type Props = {

let numRendered = 0;

// Use custom memo instead of React.memo
const TodoItem = memo(({ todo }: Props) => {
const TodoItem = React.memo(({ todo }: Props) => {
const dispatch = useDispatch();
return (
<li>
Expand Down
6 changes: 3 additions & 3 deletions examples/15_reactmemoref/src/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React, { forwardRef } from 'react';
import { memo } from 'react-tracked';

import { useDispatch, TodoType } from './store';

type Props = {
// FIXME why this complaints?
// eslint-disable-next-line react/no-unused-prop-types
todo: TodoType;
};

let numRendered = 0;

// Use custom memo instead of React.memo
const TodoItem = memo(
const TodoItem = React.memo(
forwardRef<HTMLInputElement, Props>(({ todo }, ref) => {
const dispatch = useDispatch();
return (
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"name": "react-tracked",
"description": "State usage tracking with Proxies. Optimize re-renders for useState/useReducer, React Redux, Zustand and others.",
"version": "1.7.14",
"version": "2.0.0-beta.3",
"publishConfig": {
"tag": "next"
},
"author": "Daishi Kato",
"repository": {
"type": "git",
Expand Down
10 changes: 4 additions & 6 deletions src/createTrackedSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ export const createTrackedSelector = <State>(
) => {
const useTrackedSelector = () => {
const [, forceUpdate] = useReducer((c) => c + 1, 0);
const affected = new WeakMap();
const lastAffected = useRef<typeof affected>();
// per-hook affected, it's not ideal but memo compatible
const affected = useMemo(() => new WeakMap(), []);
const prevState = useRef<State>();
const lastState = useRef<State>();
useEffect(() => {
lastAffected.current = affected;
if (prevState.current !== lastState.current
&& isChanged(
prevState.current,
Expand All @@ -35,11 +34,10 @@ export const createTrackedSelector = <State>(
lastState.current = nextState;
if (prevState.current
&& prevState.current !== nextState
&& lastAffected.current
&& !isChanged(
prevState.current,
nextState,
lastAffected.current,
affected,
new WeakMap(),
)
) {
Expand All @@ -48,7 +46,7 @@ export const createTrackedSelector = <State>(
}
prevState.current = nextState;
return nextState;
}, []);
}, [affected]);
const state = useSelector(selector);
if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { createContainer } from './createContainer';
export { createTrackedSelector } from './createTrackedSelector';
export { memo } from './memo';
export { getUntracked as getUntrackedObject } from 'proxy-compare';
34 changes: 0 additions & 34 deletions src/memo.ts

This file was deleted.

5 changes: 4 additions & 1 deletion website/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ const Component = () => {

## memo

`memo` is removed in v2. With v2, we can use `React.memo`.
The description below only applies to v1.

There is a utility function exported from the library.

This should be used instead of `React.memo` if props
Expand All @@ -155,7 +158,7 @@ work correctly because a memoized component doesn't always render
when a parent component renders.

```javascript
import { memo } from 'react-tracked';
import { memo } from 'react-tracked'; // v1 only

const ChildComponent = memo(({ num1, str1, obj1, obj2 }) => {
// ...
Expand Down
4 changes: 3 additions & 1 deletion website/docs/caveats.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ const state2 = useTrackedState();
You should use `useTrackedState` only once in a component
if you need referential equality of objects in the state.

## An object referential change doesn't trigger re-render if an property of the object is accessed in previous render
## An object referential change doesn't trigger re-render if an property of the object is accessed in previous render (v1 only)

:warning: This caveat only applies to v1. Since v2, we don't have such limitations.

```javascript
const state = useTrackedState();
Expand Down
7 changes: 2 additions & 5 deletions website/docs/tutorial-02.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,8 @@ export default React.memo(TodoItem);
```

This is the TodoItem component.
We prefer primitive props for memoized components.

If you want to use object props for memoized components,
you need to use a special [memo](../api#memo) instead of `React.memo`.
See [example/09](https://github.com/dai-shi/react-tracked/tree/main/examples/09_reactmemo) for the usage.
We used to prefer primitive props for memoized components with v1.
With v2, object props are also fine.

## src/NewTodo.js

Expand Down
7 changes: 2 additions & 5 deletions website/docs/tutorial-03.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,11 +278,8 @@ export default React.memo(TodoItem);
```

This is the TodoItem component.
We prefer primitive props for memoized components.

If you want to use object props for memoized components,
you need to use a special [memo](../api#memo) instead of `React.memo`.
See [example/09](https://github.com/dai-shi/react-tracked/tree/main/examples/09_reactmemo) for the usage.
We used to prefer primitive props for memoized components with v1.
With v2, object props are also fine.

## src/components/NewTodo.js

Expand Down

0 comments on commit 0542bec

Please sign in to comment.