├── .gitignore ├── README.md ├── assets └── devtools.png ├── example └── index.ts ├── package-lock.json ├── package.json ├── src └── index.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .rts2_cache_cjs 5 | .rts2_cache_es 6 | .rts2_cache_umd 7 | dist 8 | .size-snapshot.json 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-zustand-devtools 2 | 3 | Inspect your [zustand](https://github.com/react-spring/zustand) store in React DevTools 🐻⚛️ 4 | 5 | 6 | 7 | ## Usage 8 | 9 | ```ts 10 | import create from 'zustand'; 11 | import { mountStoreDevtool } from 'simple-zustand-devtools'; 12 | 13 | export const useStore = create(set => { 14 | // create your zustand store here 15 | }); 16 | 17 | if (process.env.NODE_ENV === 'development') { 18 | mountStoreDevtool('Store', useStore); 19 | } 20 | ``` 21 | 22 | ### Mount more than one store 23 | 24 | `mountStoreDevtool` creates a new HTML element with `id`: `simple-zustand-devtools-${storeName}`, where `storeName` is a name provided as the first argument. You can mount more than one store, as long as store names remain unique. For example: 25 | 26 | ```ts 27 | import create from 'zustand'; 28 | import { mountStoreDevtool } from 'simple-zustand-devtools'; 29 | 30 | export const useStore1 = create((set, get) => { 31 | // create your zustand store here 32 | }); 33 | 34 | export const useStore2 = create((set, get) => { 35 | // create your zustand store here 36 | }); 37 | 38 | if (process.env.NODE_ENV === 'development') { 39 | mountStoreDevtool('Store1', useStore1); 40 | 41 | mountStoreDevtool('Store2', useStore2); 42 | } 43 | ``` 44 | 45 | ## Installation 46 | 47 | ### For React 18+ 48 | 49 | ```sh 50 | yarn add simple-zustand-devtools --dev 51 | ``` 52 | 53 | ### For React 17 54 | 55 | Use [a 1.0.1 release (or lower)](https://www.npmjs.com/package/simple-zustand-devtools?activeTab=versions) for React 17. 56 | 57 | ```sh 58 | npm install simple-zustand-devtools@1.0.1 --save-dev --legacy-peer-deps 59 | ``` 60 | 61 | ## Docs 62 | 63 | ### mountStoreDevtool 64 | 65 | ```ts 66 | import { StoreApi } from 'zustand'; 67 | 68 | type ZustandStore = StoreApi>; 69 | 70 | export function mountStoreDevtool( 71 | storeName: string, 72 | store: ZustandStore, 73 | rootElement?: HTMLElement 74 | ): void; 75 | ``` 76 | 77 | ## Local Development 78 | 79 | Below is a list of commands you will probably find useful. 80 | 81 | ### `npm start` or `yarn start` 82 | 83 | Runs the project in development/watch mode. Project will be rebuilt upon changes. 84 | 85 | ### `npm run build` or `yarn build` 86 | 87 | Bundles the package to the `dist` folder. 88 | The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module). 89 | 90 | ### `npm test` or `yarn test` 91 | 92 | Runs the test watcher (Jest) in an interactive mode. 93 | By default, runs tests related to files changed since the last commit. 94 | -------------------------------------------------------------------------------- /assets/devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beerose/simple-zustand-devtools/46fbf79f675c6e05eb53ff32f30e22dc7be5b91a/assets/devtools.png -------------------------------------------------------------------------------- /example/index.ts: -------------------------------------------------------------------------------- 1 | import create from 'zustand'; 2 | import { mountStoreDevtool } from '../src'; 3 | 4 | export const useStore1 = create((_set, _get) => { 5 | return {}; 6 | }); 7 | 8 | export const useStore2 = create((_set, _get) => { 9 | return {}; 10 | }); 11 | 12 | if (process.env.NODE_ENV === 'development') { 13 | mountStoreDevtool('Store1', useStore1); 14 | 15 | mountStoreDevtool('Store2', useStore2); 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-zustand-devtools", 3 | "author": "Aleksandra Sikora", 4 | "version": "1.1.0", 5 | "license": "MIT", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "umd:main": "dist/simple-zustand-devtools.umd.production.js", 9 | "module": "dist/simple-zustand-devtools.es.production.js", 10 | "typings": "dist/index.d.ts", 11 | "files": [ 12 | "dist" 13 | ], 14 | "description": "Devtools for Zustand", 15 | "repository": "beerose/simple-zustand-devtools", 16 | "keywords": [ 17 | "react", 18 | "zustand", 19 | "devtools" 20 | ], 21 | "scripts": { 22 | "start": "tsdx watch", 23 | "build": "tsdx build", 24 | "test": "tsdx test" 25 | }, 26 | "peerDependencies": { 27 | "@types/react": ">=18.0.0", 28 | "@types/react-dom": ">=18.0.0", 29 | "react": ">=18.0.0", 30 | "react-dom": ">=18.0.0", 31 | "zustand": ">=1.0.2" 32 | }, 33 | "prettier": { 34 | "printWidth": 80, 35 | "semi": true, 36 | "singleQuote": true, 37 | "trailingComma": "es5" 38 | }, 39 | "devDependencies": { 40 | "@types/react": "^18.0.9", 41 | "@types/react-dom": "^18.0.4", 42 | "prettier": "^1.17.0", 43 | "pretty-quick": "^1.10.0", 44 | "tsdx": "^0.5.6", 45 | "typescript": "^3.9.10", 46 | "zustand": "^3.5.10" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { UseStore } from 'zustand'; 4 | 5 | export function mountStoreDevtool>( 6 | storeName: string, 7 | store: UseStore, 8 | rootElement?: HTMLElement 9 | ) { 10 | type StoreState = ReturnType['getState']>; 11 | 12 | const externalUpdates = { 13 | count: 0, 14 | }; 15 | 16 | const ZustandDevtool: React.FC = props => { 17 | const allUpdatesCount = useRef(externalUpdates.count); 18 | 19 | useEffect(() => { 20 | allUpdatesCount.current += 1; 21 | if (allUpdatesCount.current === externalUpdates.count + 1) { 22 | allUpdatesCount.current -= 1; 23 | 24 | // DevTools update 25 | store.setState(props); 26 | } 27 | }); 28 | 29 | return null; 30 | }; 31 | 32 | (ZustandDevtool as any).displayName = `((${storeName})) devtool`; 33 | 34 | if (typeof document === 'undefined') { 35 | return; 36 | } 37 | 38 | if (!rootElement) { 39 | let root = document.getElementById(`simple-zustand-devtools-${storeName}`); 40 | if (!root) { 41 | root = document.createElement('div'); 42 | root.id = `simple-zustand-devtools-${storeName}`; 43 | } 44 | 45 | document.body.appendChild(root); 46 | rootElement = root; 47 | } 48 | const newRoot = createRoot(rootElement) 49 | const renderDevtool = (state: StoreState | void) => { 50 | if (!state) { 51 | return; 52 | } 53 | newRoot.render(); 54 | externalUpdates.count += 1; 55 | }; 56 | 57 | renderDevtool(store.getState()); 58 | store.subscribe(renderDevtool); 59 | } 60 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./", 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "baseUrl": "./", 24 | "paths": { 25 | "*": ["src/*", "node_modules/*"] 26 | }, 27 | "jsx": "react", 28 | "esModuleInterop": true 29 | } 30 | } 31 | --------------------------------------------------------------------------------