Skip to content

Commit c46b8a4

Browse files
feat: add time-travel example for zustand (#34)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 74c8264 commit c46b8a4

13 files changed

Lines changed: 409 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @ts-check
2+
3+
/** @type {import('eslint').Linter.Config} */
4+
const config = {
5+
settings: {
6+
extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'],
7+
rules: {
8+
'react/no-children-prop': 'off',
9+
},
10+
},
11+
}
12+
13+
module.exports = config
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
pnpm-lock.yaml
15+
yarn.lock
16+
package-lock.json
17+
18+
# misc
19+
.DS_Store
20+
.env.local
21+
.env.development.local
22+
.env.test.local
23+
.env.production.local
24+
25+
npm-debug.log*
26+
yarn-debug.log*
27+
yarn-error.log*
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Example
2+
3+
To run this example:
4+
5+
- `npm install`
6+
- `npm run dev`
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" type="image/svg+xml" href="/emblem-light.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta name="theme-color" content="#000000" />
8+
<script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>
9+
<title>Basic Example - TanStack Devtools</title>
10+
</head>
11+
<body>
12+
<noscript>You need to enable JavaScript to run this app.</noscript>
13+
<div id="root"></div>
14+
<script type="module" src="/src/index.tsx"></script>
15+
</body>
16+
</html>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "@tanstack/devtools-example-react-time-travel",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite --port=3005",
7+
"build": "vite build",
8+
"preview": "vite preview",
9+
"test:types": "tsc"
10+
},
11+
"dependencies": {
12+
"@tanstack/devtools-event-client": "workspace:^",
13+
"@tanstack/react-devtools": "^0.3.0",
14+
"@tanstack/react-query": "^5.83.0",
15+
"@tanstack/react-query-devtools": "^5.83.0",
16+
"@tanstack/react-router": "^1.130.2",
17+
"@tanstack/react-router-devtools": "^1.130.2",
18+
"react": "^19.1.0",
19+
"react-dom": "^19.1.0",
20+
"zod": "^4.0.14",
21+
"zustand": "^5.0.7"
22+
},
23+
"devDependencies": {
24+
"@types/react": "^19.1.2",
25+
"@types/react-dom": "^19.1.2",
26+
"@vitejs/plugin-react": "^4.5.2",
27+
"vite": "^7.0.6"
28+
},
29+
"browserslist": {
30+
"production": [
31+
">0.2%",
32+
"not dead",
33+
"not op_mini all"
34+
],
35+
"development": [
36+
"last 1 chrome version",
37+
"last 1 firefox version",
38+
"last 1 safari version"
39+
]
40+
}
41+
}
Lines changed: 13 additions & 0 deletions
Loading
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { createRoot } from 'react-dom/client'
2+
import { useStore } from 'zustand'
3+
import Devtools from './setup'
4+
import { store } from './zustand-client'
5+
6+
function App() {
7+
const { count, increment, decrement } = useStore(store)
8+
return (
9+
<div>
10+
<h1>Zustand time-travel</h1>
11+
<h2>Current count: {count}</h2>
12+
<button onClick={increment}>Increment</button>
13+
<button onClick={decrement}>Decrement</button>
14+
<Devtools />
15+
</div>
16+
)
17+
}
18+
19+
const root = createRoot(document.getElementById('root')!)
20+
root.render(<App />)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2+
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
3+
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
4+
import {
5+
Link,
6+
Outlet,
7+
RouterProvider,
8+
createRootRoute,
9+
createRoute,
10+
createRouter,
11+
} from '@tanstack/react-router'
12+
import { TanstackDevtools } from '@tanstack/react-devtools'
13+
import { ZustandTimeTravel } from './zustand-time-travel'
14+
15+
const rootRoute = createRootRoute({
16+
component: () => (
17+
<>
18+
<div className="p-2 flex gap-2">
19+
<Link to="/" className="[&.active]:font-bold">
20+
Home
21+
</Link>{' '}
22+
<Link to="/about" className="[&.active]:font-bold">
23+
About
24+
</Link>
25+
</div>
26+
<hr />
27+
<Outlet />
28+
</>
29+
),
30+
})
31+
32+
const indexRoute = createRoute({
33+
getParentRoute: () => rootRoute,
34+
path: '/',
35+
component: function Index() {
36+
return (
37+
<div className="p-2">
38+
<h3>Welcome Home!</h3>
39+
</div>
40+
)
41+
},
42+
})
43+
function About() {
44+
return (
45+
<div className="p-2">
46+
<h3>Hello from About!</h3>
47+
</div>
48+
)
49+
}
50+
51+
const aboutRoute = createRoute({
52+
getParentRoute: () => rootRoute,
53+
path: '/about',
54+
component: About,
55+
})
56+
57+
const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])
58+
59+
const router = createRouter({ routeTree })
60+
61+
const queryClient = new QueryClient()
62+
63+
export default function DevtoolsExample() {
64+
return (
65+
<>
66+
<QueryClientProvider client={queryClient}>
67+
<TanstackDevtools
68+
plugins={[
69+
{
70+
name: 'Tanstack Query',
71+
render: <ReactQueryDevtoolsPanel />,
72+
},
73+
{
74+
name: 'Tanstack Router',
75+
render: <TanStackRouterDevtoolsPanel router={router} />,
76+
},
77+
{
78+
name: 'Zustand time-travel',
79+
render: <ZustandTimeTravel />,
80+
},
81+
]}
82+
/>
83+
<RouterProvider router={router} />
84+
</QueryClientProvider>
85+
</>
86+
)
87+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { EventClient } from '@tanstack/devtools-event-client'
2+
import { createStore } from 'zustand'
3+
4+
interface ZustandEventMap {
5+
'zustand:stateChange': any
6+
'zustand:revertSnapshot': any
7+
}
8+
export const eventClient = new EventClient<ZustandEventMap>({
9+
pluginId: 'zustand',
10+
})
11+
12+
export const store = createStore<{
13+
count: number
14+
increment: () => void
15+
decrement: () => void
16+
}>((set) => ({
17+
count: 0,
18+
increment: () => {
19+
return set((state) => {
20+
eventClient.emit('stateChange', { count: state.count + 1 })
21+
return { count: state.count + 1 }
22+
})
23+
},
24+
decrement: () => {
25+
return set((state) => {
26+
eventClient.emit('stateChange', { count: state.count - 1 })
27+
return { count: state.count - 1 }
28+
})
29+
},
30+
}))
31+
32+
eventClient.on('revertSnapshot', (snapshot) => {
33+
store.setState({
34+
count: snapshot.payload.count,
35+
})
36+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { useEffect, useState } from 'react'
2+
import { eventClient } from './zustand-client'
3+
4+
export function ZustandTimeTravel() {
5+
const [snapshots, setSnapshots] = useState<Array<any>>([])
6+
7+
useEffect(() => {
8+
const cleanup = eventClient.on('stateChange', (event) =>
9+
setSnapshots((prev) => [...prev, event.payload]),
10+
)
11+
return () => {
12+
cleanup()
13+
}
14+
}, [])
15+
16+
return (
17+
<div>
18+
{/* Snapshot slider to change the current state */}
19+
Drag Me to time travel through zustand states
20+
<hr />
21+
<input
22+
type="range"
23+
min={0}
24+
max={snapshots.length - 1}
25+
onChange={(e) => {
26+
const index = Number(e.target.value)
27+
eventClient.emit('revertSnapshot', snapshots[index])
28+
}}
29+
/>
30+
</div>
31+
)
32+
}

0 commit comments

Comments
 (0)