Skip to content

Commit 99eea3e

Browse files
committed
Experiment: Redux with useMutableSource
1 parent 3f4697b commit 99eea3e

File tree

3 files changed

+110
-16
lines changed

3 files changed

+110
-16
lines changed

__tests__/all_spec.js

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@
33
const port = process.env.PORT || '8080';
44

55
const names = [
6-
'react-redux',
7-
'reactive-react-redux',
8-
'react-tracked',
9-
'constate',
10-
'zustand',
11-
'react-sweet-state',
12-
'storeon',
13-
'react-hooks-global-state-1',
14-
'react-hooks-global-state-2',
15-
'use-context-selector',
16-
'mobx-react-lite',
17-
'use-subscription',
18-
'mobx-use-sub',
19-
'react-state',
20-
'simplux',
21-
'react-apollo',
6+
// 'react-redux',
7+
'redux-use-mutable-source',
8+
// 'reactive-react-redux',
9+
// 'react-tracked',
10+
// 'constate',
11+
// 'zustand',
12+
// 'react-sweet-state',
13+
// 'storeon',
14+
// 'react-hooks-global-state-1',
15+
// 'react-hooks-global-state-2',
16+
// 'use-context-selector',
17+
// 'mobx-react-lite',
18+
// 'use-subscription',
19+
// 'mobx-use-sub',
20+
// 'react-state',
21+
// 'simplux',
22+
// 'react-apollo',
2223
];
2324

2425
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"dev-server": "cross-env PORT=8080 webpack-dev-server",
1616
"http-server": "cross-env PORT=8080 http-server dist",
1717
"build:react-redux": "cross-env NAME=react-redux webpack",
18+
"build:redux-use-mutable-source": "cross-env NAME=redux-use-mutable-source webpack",
1819
"build:reactive-react-redux": "cross-env NAME=reactive-react-redux webpack",
1920
"build:react-tracked": "cross-env NAME=react-tracked webpack",
2021
"build:constate": "cross-env NAME=constate webpack",

src/redux-use-mutable-source/index.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, {
2+
useTransition,
3+
createContext,
4+
useCallback,
5+
useContext,
6+
useLayoutEffect,
7+
useMemo,
8+
useRef,
9+
createMutableSource,
10+
useMutableSource,
11+
} from 'react';
12+
import { createStore } from 'redux';
13+
14+
import {
15+
syncBlock,
16+
useRegisterIncrementDispatcher,
17+
reducer,
18+
ids,
19+
useCheckTearing,
20+
} from '../common';
21+
22+
const StoreContext = createContext();
23+
const subscribe = (store, callback) => store.subscribe(callback);
24+
const Provider = ({ store, children }) => {
25+
const contextValue = useMemo(() => ({
26+
source: createMutableSource(store, () => store.getState()),
27+
dispatch: store.dispatch,
28+
}), [store]);
29+
return (
30+
<StoreContext.Provider value={contextValue}>
31+
{children}
32+
</StoreContext.Provider>
33+
);
34+
};
35+
const useDispatch = () => useContext(StoreContext).dispatch;
36+
const useSelector = (selector) => {
37+
const selectorRef = useRef(selector);
38+
useLayoutEffect(() => {
39+
selectorRef.current = selector;
40+
});
41+
const getSnapshot = useCallback((store) => selectorRef.current(store.getState()), []);
42+
const { source } = useContext(StoreContext);
43+
return useMutableSource(source, getSnapshot, subscribe);
44+
};
45+
46+
const store = createStore(reducer);
47+
48+
const Counter = React.memo(() => {
49+
const count = useSelector((state) => state.count);
50+
syncBlock();
51+
return <div className="count">{count}</div>;
52+
}, () => true);
53+
54+
const Main = () => {
55+
const dispatch = useDispatch();
56+
const count = useSelector((state) => state.count);
57+
useCheckTearing();
58+
useRegisterIncrementDispatcher(React.useCallback(() => {
59+
dispatch({ type: 'increment' });
60+
}, [dispatch]));
61+
const [localCount, localIncrement] = React.useReducer((c) => c + 1, 0);
62+
const normalIncrement = () => {
63+
dispatch({ type: 'increment' });
64+
};
65+
const [startTransition, isPending] = useTransition();
66+
const transitionIncrement = () => {
67+
startTransition(() => {
68+
dispatch({ type: 'increment' });
69+
});
70+
};
71+
return (
72+
<div>
73+
<button type="button" id="normalIncrement" onClick={normalIncrement}>Increment shared count normally (two clicks to increment one)</button>
74+
<button type="button" id="transitionIncrement" onClick={transitionIncrement}>Increment shared count in transition (two clicks to increment one)</button>
75+
<span id="pending">{isPending && 'Pending...'}</span>
76+
<h1>Shared Count</h1>
77+
{ids.map((id) => <Counter key={id} />)}
78+
<div className="count">{count}</div>
79+
<h1>Local Count</h1>
80+
{localCount}
81+
<button type="button" id="localIncrement" onClick={localIncrement}>Increment local count</button>
82+
</div>
83+
);
84+
};
85+
86+
const App = () => (
87+
<Provider store={store}>
88+
<Main />
89+
</Provider>
90+
);
91+
92+
export default App;

0 commit comments

Comments
 (0)