Skip to content

Commit f61c97e

Browse files
authored
Merge pull request #14 from harry-whorlow/docs-custom-plugin
docs(react): custom plugin docs and custom plugin example
2 parents 23574d6 + 3b58247 commit f61c97e

File tree

20 files changed

+594
-34
lines changed

20 files changed

+594
-34
lines changed
File renamed without changes.
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
---
2+
title: Custom plugins
3+
id: custom-plugins
4+
---
5+
6+
Tanstack devtools allows you to create your own custom plugins by emitting and listening to our event bus.
7+
8+
## Prerequisite
9+
10+
This guide will walk you through a simple example where our library is a counter with a count history. A working example can be found in our [custom-plugin example](https://tanstack.com/devtools/latest/docs/framework/react/examples/custom-plugin).
11+
12+
This is our library code:
13+
14+
counter.ts
15+
```tsx
16+
export function createCounter() {
17+
let count = 0;
18+
const history = [];
19+
20+
return {
21+
getCount: () => count,
22+
increment: () => {
23+
history.push(count);
24+
count++;
25+
},
26+
decrement: () => {
27+
history.push(count);
28+
count--;
29+
},
30+
};
31+
}
32+
```
33+
34+
## Event Client Setup
35+
36+
Install the [TanStack Devtools Event Client](https://tanstack.com/devtools/) utils.
37+
38+
```bash
39+
npm i @tanstack/devtools-event-client
40+
```
41+
42+
First you will need to setup the `EventClient`.
43+
44+
eventClient.ts
45+
```tsx
46+
import { EventClient } from '@tanstack/devtools-event-client'
47+
48+
49+
type EventMap = {
50+
// The key of the event map is a combination of {pluginId}:{eventSuffix}
51+
// The value is the expected type of the event payload
52+
'custom-devtools:counter-state': { count: number, history: number[], }
53+
}
54+
55+
class CustomEventClient extends EventClient<EventMap> {
56+
constructor() {
57+
super({
58+
// The pluginId must match that of the event map key
59+
pluginId: 'custom-devtools',
60+
})
61+
}
62+
}
63+
64+
// This is where the magic happens, it'll be used throughout your application.
65+
export const DevtoolsEventClient = new FormEventClient()
66+
```
67+
68+
## Event Client Integration
69+
70+
Now we need to hook our `EventClient` into out application code. This can be done in many way's, a UseEffect that emits the current state, or a subscription to an observer, all that matters is that when you want to emit the current state you do the following.
71+
72+
Our new library code will looks as follows:
73+
74+
counter.ts
75+
```tsx
76+
import { DevtoolsEventClient } from './eventClient.ts'
77+
78+
export function createCounter() {
79+
let count = 0
80+
const history: Array<number> = []
81+
82+
return {
83+
getCount: () => count,
84+
increment: () => {
85+
history.push(count)
86+
87+
// The emit eventSuffix must match that of the EventMap defined in eventClient.ts
88+
DevtoolsEventClient.emit('counter-state', {
89+
count: count++,
90+
history: history,
91+
})
92+
},
93+
decrement: () => {
94+
history.push(count)
95+
96+
DevtoolsEventClient.emit('counter-state', {
97+
count: count--,
98+
history: history,
99+
})
100+
},
101+
}
102+
}
103+
```
104+
105+
> **Important** `EventClient` is framework agnostic so this process will be the same regardless of framework or even in vanilla JavaScript.
106+
107+
## Consuming The Event Client
108+
109+
Now we need to create our devtools panel, for a simple approach write the devtools in the framework that the adapter is, be aware that this will make the plugin framework specific.
110+
111+
> Because TanStack is framework agnostic we have taken a more complicated approach that will be explained in coming docs (if framework agnosticism is not a concern to you you can ignore this).
112+
113+
DevtoolsPanel.ts
114+
```tsx
115+
import { DevtoolsEventClient } from './eventClient.ts'
116+
117+
export function DevtoolPanel() {
118+
const [state,setState] = useState();
119+
120+
useEffect(() => {
121+
// subscribe to the emitted event
122+
const cleanup = client.on("counter-state", e => setState(e.payload)
123+
return cleanup
124+
}, []);
125+
126+
return (
127+
<div>
128+
<div>{state.count}</div>
129+
<div>{JSON.stringify(state.history)}</div>
130+
<div/>
131+
)
132+
}
133+
```
134+
135+
## Application Integration
136+
137+
This step follows what's shown in [../basic-setup] for a more documented guide go check it out. As well as the complete [custom-plugin example](https://tanstack.com/devtools/latest/docs/framework/react/examples/custom-plugin) in our examples section.
138+
139+
Main.tsx
140+
```tsx
141+
import { DevtoolPanel } from './DevtoolPanel'
142+
143+
createRoot(document.getElementById('root')!).render(
144+
<StrictMode>
145+
<App />
146+
147+
<TanstackDevtools
148+
plugins={[
149+
{
150+
// Name it what you like, this is how it will appear in the Menu
151+
name: 'Custom devtools',
152+
render: <DevtoolPanel />,
153+
},
154+
]}
155+
/>
156+
</StrictMode>,
157+
)
158+
159+
```
160+
161+
## Debugging
162+
163+
Both the TansTack `TanstackDevtools` component and the TanStack `EventClient` come with built in debug mode which will log to the console the emitted event as well as the EventClient status.
164+
165+
TanstackDevtool's debugging mode can be activated like so:
166+
```tsx
167+
<TanstackDevtools
168+
eventBusConfig={{ debug: true }}
169+
plugins={[
170+
{
171+
// call it what you like, this is how it will appear in the Menu
172+
name: 'Custom devtools',
173+
render: <DevtoolPanel />,
174+
},
175+
]}
176+
/>
177+
```
178+
179+
Where as the EventClient's debug mode can be activated by:
180+
```tsx
181+
class CustomEventClient extends EventClient<EventMap> {
182+
constructor() {
183+
super({
184+
pluginId: 'custom-devtools',
185+
debug: true,
186+
})
187+
}
188+
}
189+
```
190+
191+
Activating the debug mode will log to the console the current events that emitter has emitted or listened to. The EventClient will have appended `[tanstack-devtools:${pluginId}]` and the client will have appended `[tanstack-devtools:client-bus]`.
192+
193+
Heres an example of both:
194+
```
195+
🌴 [tanstack-devtools:client-bus] Initializing client event bus
196+
197+
🌴 [tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state
198+
```
File renamed without changes.

examples/react/basic/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<meta name="viewport" content="width=device-width, initial-scale=1" />
77
<meta name="theme-color" content="#000000" />
88
<script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>
9-
<title>TanStack Devtools Example</title>
9+
<title>Basic Example - TanStack Devtools</title>
1010
</head>
1111
<body>
1212
<noscript>You need to enable JavaScript to run this app.</noscript>

examples/react/basic/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"devDependencies": {
2121
"@types/react": "^19.1.2",
2222
"@types/react-dom": "^19.1.2",
23-
"@vitejs/plugin-react": "^4.4.1",
23+
"@vitejs/plugin-react": "^4.5.2",
2424
"vite": "^7.0.6"
2525
},
2626
"browserslist": {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @ts-check
2+
3+
/** @type {import('eslint').Linter.Config} */
4+
const config = {
5+
extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'],
6+
rules: {
7+
'react/no-children-prop': 'off',
8+
},
9+
}
10+
11+
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+
9+
<title>Custom Devtools - 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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@tanstack/devtools-custom-devtools",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite --port=3001",
7+
"build": "vite build",
8+
"preview": "vite preview",
9+
"test:types": "tsc"
10+
},
11+
"dependencies": {
12+
"@tanstack/devtools-event-client": "https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-event-client@11",
13+
"@tanstack/react-devtools": "https://pkg.pr.new/TanStack/devtools/@tanstack/react-devtools@0a0219b",
14+
"react": "^19.1.0",
15+
"react-dom": "^19.1.0"
16+
},
17+
"devDependencies": {
18+
"@types/react": "^19.1.2",
19+
"@types/react-dom": "^19.1.2",
20+
"@vitejs/plugin-react": "^4.5.2",
21+
"vite": "^7.0.6"
22+
},
23+
"browserslist": {
24+
"production": [
25+
">0.2%",
26+
"not dead",
27+
"not op_mini all"
28+
],
29+
"development": [
30+
"last 1 chrome version",
31+
"last 1 firefox version",
32+
"last 1 safari version"
33+
]
34+
}
35+
}

0 commit comments

Comments
 (0)