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

+ + + + + + +
+ ); +}