diff --git a/demo/index.jsx b/demo/index.jsx
index 36d557b35b..980b96de74 100644
--- a/demo/index.jsx
+++ b/demo/index.jsx
@@ -25,6 +25,8 @@ import SuspenseRouterBug from './suspense-router';
import NestedSuspenseBug from './nested-suspense';
import Contenteditable from './contenteditable';
import { MobXDemo } from './mobx';
+import Zustand from './zustand';
+import ReduxToolkit from './redux_toolkit';
let isBenchmark = /(\/spiral|\/pythagoras|[#&]bench)/g.test(
window.location.href
@@ -135,6 +137,12 @@ class App extends Component {
contenteditable
+
+ zustand
+
+
+ redux-toolkit
+
@@ -165,6 +173,8 @@ class App extends Component {
+
+
diff --git a/demo/package-lock.json b/demo/package-lock.json
index ccab05346e..af098f2051 100644
--- a/demo/package-lock.json
+++ b/demo/package-lock.json
@@ -7,6 +7,7 @@
"name": "demo",
"dependencies": {
"@material-ui/core": "4.9.5",
+ "@reduxjs/toolkit": "^2.2.3",
"d3-scale": "^1.0.7",
"d3-selection": "^1.2.0",
"htm": "2.1.1",
@@ -19,7 +20,8 @@
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"redux": "^4.0.1",
- "styled-components": "^4.2.0"
+ "styled-components": "^4.2.0",
+ "zustand": "^4.5.2"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.0.0-beta.55",
@@ -1050,6 +1052,42 @@
"react-dom": "^16.8.0 || ^17.0.0"
}
},
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.3.tgz",
+ "integrity": "sha512-76dll9EnJXg4EVcI5YNxZA/9hSAmZsFqzMmNRHvIlzw2WS/twfcVX3ysYrWGJMClwEmChQFC4yRq74tn6fdzRA==",
+ "dependencies": {
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.0.1"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@reduxjs/toolkit/node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
+ },
+ "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@@ -1619,6 +1657,15 @@
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
+ "node_modules/immer": {
+ "version": "10.0.4",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.4.tgz",
+ "integrity": "sha512-cuBuGK40P/sk5IzWa9QPUaAdvPHjkk1c+xYsd9oZw+YQQEV+10G0P5uMpGctZZKnyQ+ibRO08bD25nWLmYi2pw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
"node_modules/immutable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz",
@@ -2198,6 +2245,11 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
+ "node_modules/reselect": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz",
+ "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg=="
+ },
"node_modules/resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
@@ -2433,6 +2485,14 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
@@ -2485,6 +2545,33 @@
"optional": true
}
}
+ },
+ "node_modules/zustand": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz",
+ "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==",
+ "dependencies": {
+ "use-sync-external-store": "1.2.0"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/demo/package.json b/demo/package.json
index c3b1cdb31d..705b22d6ba 100644
--- a/demo/package.json
+++ b/demo/package.json
@@ -14,6 +14,7 @@
},
"dependencies": {
"@material-ui/core": "4.9.5",
+ "@reduxjs/toolkit": "^2.2.3",
"d3-scale": "^1.0.7",
"d3-selection": "^1.2.0",
"htm": "2.1.1",
@@ -26,7 +27,8 @@
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"redux": "^4.0.1",
- "styled-components": "^4.2.0"
+ "styled-components": "^4.2.0",
+ "zustand": "^4.5.2"
},
"volta": {
"extends": "../package.json"
diff --git a/demo/redux-toolkit.js b/demo/redux-toolkit.js
new file mode 100644
index 0000000000..f33657cd1b
--- /dev/null
+++ b/demo/redux-toolkit.js
@@ -0,0 +1,60 @@
+import { createElement } from 'preact';
+import { Provider, useSelector } from 'react-redux';
+import { configureStore, createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ value: 0
+};
+const counterSlice = createSlice({
+ name: 'counter',
+ initialState,
+ reducers: {
+ increment: state => {
+ state.value += 1;
+ },
+ decrement: state => {
+ state.value -= 1;
+ }
+ }
+});
+const store = configureStore({
+ reducer: {
+ counter: counterSlice.reducer
+ }
+});
+
+function Counter({ number }) {
+ const count = useSelector(state => state.counter.value);
+ return (
+
+ Counter #{number}:{count}
+
+ );
+}
+
+export default function ReduxToolkit() {
+ function increment() {
+ store.dispatch(counterSlice.actions.increment());
+ }
+ function decrement() {
+ store.dispatch(counterSlice.actions.decrement());
+ }
+ function incrementAsync() {
+ setTimeout(() => {
+ store.dispatch(counterSlice.actions.increment());
+ }, 1000);
+ }
+ return (
+
+
+
Redux Toolkit
+ Counter
+
+
+
+
+
+
+
+ );
+}
diff --git a/demo/zustand.js b/demo/zustand.js
new file mode 100644
index 0000000000..de06decb58
--- /dev/null
+++ b/demo/zustand.js
@@ -0,0 +1,53 @@
+import { createElement } from 'preact';
+import create from 'zustand';
+
+const useStore = create(set => ({
+ value: 0,
+ text: 'John',
+ setText: text => set(state => ({ ...state, text })),
+ increment: () => set(state => ({ value: state.value + 1 })),
+ decrement: () => set(state => ({ value: state.value - 1 })),
+ incrementAsync: async () => {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ set(state => ({ value: state.value + 1 }));
+ }
+}));
+
+function Counter({ number }) {
+ const value = useStore(state => state.value);
+ return (
+
+ Counter #{number}: {value}
+
+ );
+}
+function Text() {
+ const text = useStore(state => state.text);
+ const { setText } = useStore();
+ function handleInput(e) {
+ setText(e.target.value);
+ }
+ return (
+
+ Text: {text}
+
+
+ );
+}
+
+export default function ZustandComponent() {
+ const { increment, decrement, incrementAsync } = useStore();
+
+ return (
+
+
Zustand
+ Counter
+
+
+
+
+
+
+
+ );
+}