Git Product home page Git Product logo

jotai-devtools's Introduction

Jotai DevTools

Build Status Version Version

πŸš€ Features

  • Debug 🐞 atom values with ease
  • ⏳ Time-travel through your atoms and find bugs faster than before (recommended jotai >=2.2.0)
  • Out-of-the-box πŸ”Œ support for async/suspendible atoms
  • Built-in Dark mode πŸŒ—
  • βœ… Supports custom store
  • βœ… Works with provider-less mode
  • βœ… Works with Next.js
  • βœ… Supports custom nonce for CSP
  • βœ… Hides private atoms with ability to configure (requires Jotai >=2.0.3)
  • βœ… Tree-shakable and built for non-production environments
  • βœ… Parses all the JavaScript values with JSON Tree view
  • βœ… Diff checking with additions and deletion highlights

πŸ“Ί Preview

Jotai DevTools Screenshot

☝️ Prerequisites

  • Jotai version >=1.11.0 (highly recommended to use 2.x.x)
  • React version >=17.0.0

πŸ“¦ Setup

(See complete setup guide for UI-based devtools below)

# yarn
yarn add jotai-devtools

# npm
npm install jotai-devtools --save

✨ UI DevTools

Enhance your development experience with the UI based Jotai DevTool

Demo

Babel plugin setup - (Optional but highly recommended)

Use Jotai babel plugins for optimal debugging experience. Find the complete guide on jotai.org

Eg.

{
  "plugins": [
    // Enables hot reload for atoms
    "jotai/babel/plugin-react-refresh",
    // Automatically adds debug labels to the atoms
    "jotai/babel/plugin-debug-label"
  ]
}

Next JS setup

You may skip this section if you're not using Next.js.

Enable transpilePackages for the UI CSS and components to be transpiled correctly.

// next.config.ts

const nextConfig = {
  // Learn more here - https://nextjs.org/docs/advanced-features/compiler#module-transpilation
  // Required for font css to be imported correctly πŸ‘‡
  transpilePackages: ['jotai-devtools'],
};

module.exports = nextConfig;

Available props

type DevToolsProps = {
  // Defaults to false
  isInitialOpen?: boolean;
  // pass a custom store
  store?: Store;
  // Defaults to light
  theme?: 'dark' | 'light';
  // Defaults to 'bottom-left'
  position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
  // Custom nonce to allowlist jotai-devtools specific inline styles via CSP
  nonce?: string;
  // We recommend keeping these options static. i.e. set it only once. Avoid connecting it to re-renderable state
  options?: {
    // Private atoms are used internally in atoms like `atomWithStorage` or `atomWithLocation`, etc. to manage state.
    // Defaults to `false`
    shouldShowPrivateAtoms?: boolean;
    // Expands the JSON tree view on initial render on Atom Viewer tab, Timeline tab, etc.
    // Defaults to `false`
    shouldExpandJsonTreeViewInitially?: boolean;
    // The interval (in milliseconds) between each step of the time travel playback.
    // Defaults to `750ms`
    timeTravelPlaybackInterval?: number;
    // The maximum number of snapshots to keep in the history.
    // The higher the number the more memory it will consume.
    // Defaults to `Infinity`. Recommended: `~30`
    snapshotHistoryLimit?: number;
  };
};

Provider-less

import { DevTools } from 'jotai-devtools';
// Note that this may get included in your production builds. Please import it conditionally if you want to avoid that
import 'jotai-devtools/styles.css';

const App = () => {
  return (
    <>
      <DevTools />
      {/* your app */}
    </>
  );
};

With Provider

import { createStore } from 'jotai';
import { DevTools } from 'jotai-devtools';
// Note that this may get included in your production builds. Please import it conditionally if you want to avoid that
import 'jotai-devtools/styles.css';

const customStore = createStore();

const App = () => {
  return (
    <Provider store={customStore}>
      <DevTools store={customStore} />
      {/* your app */}
    </Provider>
  );
};

Hooks

Detailed documentation is available on https://jotai.org/docs/api/devtools

import {
  useAtomsSnapshot,
  useGotoAtomsSnapshot,
  useAtomsDebugValue,
  // Redux devtool hooks
  useAtomDevtools,
  useAtomsDevtools,
} from 'jotai-devtools';

Migration guides

Migrate Ζ’rom @emotion/react to native CSS

With the latest release, Jotai DevTools no longer depends on @emotion/react and is replaced it with native CSS.

  1. Remove @emotion/react from your dependencies

    # yarn
    yarn remove @emotion/react
    
    # npm
    npm uninstall @emotion/react
  2. Replace @emotion/react with jotai-devtools/styles.css in your code

Note that this css file may get included in your production builds please import it conditionally if you want to avoid that.

import { DevTools } from 'jotai-devtools';
+ import 'jotai-devtools/styles.css';

Migrate Jotai to V2

Find the official migration guide on jotai.org

Migrate jotai/react/devtools to jotai-devtools

  1. Install this package

    # npm
    npm install jotai-devtools --save
    
    # yarn
    yarn add jotai-devtools
  2. Update imports from jotai/react/devtools to jotai-devtools

    import {
     useAtomsSnapshot,
     useGotoAtomsSnapshot,
     useAtomsDebugValue,
     // Redux devtool integration hooks
     useAtomDevtools,
     useAtomsDevtools,
    - } from 'jotai/react/devtools';
    + } from 'jotai-devtools';

Inspirations

Redux DevTools React Query DevTools

Other announcements

✨ First announcement

jotai-devtools's People

Contributors

arjunvegda avatar axel-havukangas-tt avatar dai-shi avatar firede avatar github-actions[bot] avatar hyoban avatar jastor11 avatar pablocubico avatar prettycoffee avatar renrizzolo avatar yf-yang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

jotai-devtools's Issues

[Feature Request] Improve `jotai-scope` support

image

Due to the current implementation of jotai-scope, which creates a copy of an atom regardless of its scope (as shown in this code), duplicated atoms appear in the dev-tool panel when an atom is accessed within multiple scopes.

So I would like to suggest the following enhancements:

  1. Prevent displaying duplicated atoms in the devtool panel when they are duplicated by a ScopeProvider that they are not scoped in.
  2. Include a feature in the devtool to show which ScopeProvider each atom is associated with.

Thank you for considering these improvements.

DevTools as browser extension

πŸ‘‹ folks! A Jotai newbie here πŸ‘»

I just installed DevTools into my first Jotai project and saw a somewhat noticeable diff in my yarn.lock. Overall, this is fine because project sub-dependencies are not going to affect the production bundle. However, I wonder if it is feasible to make DevTools available as a browser extension, similar to Apollo Client DevTools and Redux DevTools. I remember using those when they just got released and they were only available as project imports. The maintainers eventually released the extensions and thus helped lots of developers with keeping their node_modules small.

How feasible is a browser extension for Jotai DevTools?

Either way, thanks for working on this repo – the UX in the DevTools is quite smooth πŸ’―

Missing style.css

The documentation reports that the style can be imported and the dependency to @emotion/react can be removed. I installed latest version and this is not working. I also noticed the package has mantine v6 as dependency while in the repo it's already v7. Is this something syet to be released?

CSS leaking override mantine styles

CSS from "jotai-devtools/styles.css" leaks and overrides my current project mantine styles (downgraded to v0.8.0 works fine)
Package version: v0.10.0

image

Before importing "jotai-devtools/styles.css":
image

After importing "jotai-devtools/styles.css":
image

Chrome Extension?

I was curious if there were any plans to make a chrome extension based implementation of these tools?
I find them helpful but sometimes its hard to manage screen real estate with the actual application.

I was considering just creating an extension that got a dynamic reference the store like the redux extension and then just re-used this react code but I didn't want to re-invent the wheel if this group was looking to support that eventually.

Uncaught ReferenceError: process is not defined (jsondiffpatch)

Whenever i add the component, my app gets an error.
using version -- "jotai-devtools": "^0.6.2"

runtime.js:39 Uncaught ReferenceError: process is not defined at eval (index.js:6:13) at ./node_modules/jsondiffpatch/node_modules/supports-color/index.js (main.js:139097:1) at options.factory (runtime.js:620:31) at __webpack_require__ (runtime.js:36:33) at fn (runtime.js:277:21) at eval (index.js:3:52) at ./node_modules/jsondiffpatch/node_modules/chalk/source/index.js (main.js:139023:1) at options.factory (runtime.js:620:31) at __webpack_require__ (runtime.js:36:33) at fn (runtime.js:277:21)

I faced an issue when clicking on the Time Travel.

TypeError: T1.floating.floating is not a function
at z91 (webpack-internal:///(app-pages-browser)/./node_modules/@chaibuilder/sdk/dist/index-rktN6W1Y.js:38157:142)
at renderWithHooksAgain (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:11272:16)
at renderWithHooks (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:11135:18)
at mountIndeterminateComponent (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:16869:13)
at beginWork$1 (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:18458:16)

Atom rendering hangs on complex objects (such as the Firebase User object)

The dev tools are choking for me on the Firebase Auth user object, causing the page to go unresponsive.

Looks like javascript-stringify recursively descends into the Firebase User object and finds references to functions, for which it stringifies and inlines the code, no matter how bulky. This eventually become a beast to render and hangs my browser.

// with whitespace
javascriptStringify(firebaseUser, null, 2).length
288227

// without whitespace
javascriptStringify(firebaseUser).length
158985

// without functions
javascriptStringify(
  firebaseUser,
  ((value, indent, stringify) => typeof value === 'function' ? 'function' : stringify(value)),
  2
).length
105180

// without whitespace and functions
javascriptStringify(
  firebaseUser,
  ((value, indent, stringify) => typeof value === 'function' ? 'function' : stringify(value))
).length
43842

// without javascriptStringify
JSON.stringify(firebaseUser).length
1324

A few suggestions:

  1. Use JSON.stringify() if atomValue.toJSON() is present. The Firebase User object implements this, which is why the result in my final example is so tiny.
  2. Stringify functions without bodies, maybe via: `${function.name}() { … }`
  3. Userland escape hatch: Maybe look for atom.valueToString() and use that instead if available?

[Feature Request] Trace source of atom updates

When your state becomes large enough, effects tend to overlap – especially in applications with a lot of visual/input interactions (mouse movement, tracking positions of elements on the screen, dragging and dropping, etc). This can lead to render loops that can be hard to track down. If you have 10 buttons/sliders/keyboard shortcuts that all perform the same state update, it's really time consuming to figure out which one of them is causing issues.

Redux devtools has a nice feature that lets you "trace" the source of updates:

Trace feature

I think this would be a nice addition to the devtools, perhaps in the Time Travel tab behind a (default off) feature flag or setting.

DevTools in production

Hello,
I there any issue with last version of jotai-devtools (0.3.1) when building app in production mode?

I can see from the documentation

All the components and the hooks are optimized to be tree-shakable for production builds and only works in a non-production environment

But when I build & deploy my app, I still can see the Jotai Devtools console button. My stores are all labelled <unlabelled-atom> (whereas have proper label in development mode)

I'm also using react-query DevTools, which is well excluded from production (so yes, I'm in production mode :) )

Just wanted to check if i'm doing something wrong, if someone else have this issue, or if I have to add a condition myself to exclude DevTools on production?

jotai + vite + react: Unable to Use jotai-devtools

Reproduction Steps:

  1. Create a project with vite:
   npm create vite@latest client
   βœ” Select a framework: β€Ί React
   βœ” Select a variant: β€Ί TypeScript
  1. Install the necessary libraries:
   npm i jotai jotai-devtools @emotion/react
  1. Add <Devtools /> and start the app.

  2. Nothing is displayed in the browser; the following error appears in Chrome Developer Tools console:

   Uncaught ReferenceError: process is not defined
      at node_modules/chalk/index.js (index.js:8:29)
      at __require (chunk-7HP44VBA.js?v=a2bfacf5:8:50)
      at chunk-G6BETQYW.esm.mjs:6935:26

Expected Behavior:
I want to use jotai-devtools.

Environment:

  • OS: macOS Ventura 13.4
  • Node: 18.17.1
  • Npm: 9.6.7
  • Google Chrome: 115.0.5790.170

Other Information:

package.json:

{
  "name": "client",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "@emotion/react": "^11.11.1",
    "jotai": "^2.3.1",
    "jotai-devtools": "^0.6.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "@vitejs/plugin-react": "^4.0.3",
    "eslint": "^8.45.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.3",
    "typescript": "^5.0.2",
    "vite": "^4.4.5"
  }
}

vite.config.ts:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import jotaiDebugLabel from 'jotai/babel/plugin-debug-label'
import jotaiReactRefresh from 'jotai/babel/plugin-react-refresh'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({ babel: { plugins: [jotaiDebugLabel, jotaiReactRefresh] } }),
  ],
})

App.tsx:

import './App.css'
import { DevTools } from 'jotai-devtools';

function App() {
  return (
    <>
      <DevTools />
      <h1>Hello jotai Dev tool</h1>
    </>
  )
}

export default App

0.6.0 broke Vite dev mode

If i update jotai-devtools to 0.6.0 my UI breaks with the following message in the console:

image

Chalk is an npm package which works only in a Node environment, hence process is not defined.

If I look at the stacktrace, the error comes from this origin:

image

Time travel is a new feature in devtools 0.6 if I understand correctly.

Could it be that some of the imported npm packages there use chalk internally? Maybe @mantine/core?

run time error after updating to version 0.3.0

after updating to version 0.3.0 I'm getting this error

SyntaxError: Named export 'IconAlertCircle' not found. The requested module '@tabler/icons' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '@tabler/icons';

I'm using nextjs v 13.1.6 with nx mono repo

jotai-devtools not working with PNPM (declare jotai as a dependency)

https://github.com/jotaijs/jotai-devtools/blob/9db195108ce82fa44f3dd72b0515d8a94d480048/package.json#L140C6-L140C11

Some context. I'm working in a bazel + PNPM app and I created a package that includes both jotai and jotai-devtools as regular dependencies.

When I try to use that package in my app, I was getting the following error:

Module not found: Error: Can't resolve 'jotai/react' in '/private/var/tmp/_bazel_william.heberer/5267ba2fd8a6fbaf9ba9f8f7d732ebcd/execroot/rh/bazel-out/darwin_arm64-fastbuild/bin/node_modules/.aspect_rules_js/[email protected]_1687856300/node_modules/jotai-devtools/dist'
resolve 'jotai/react' in '/private/var/tmp/_bazel_william.heberer/5267ba2fd8a6fbaf9ba9f8f7d732ebcd/execroot/rh/bazel-out/darwin_arm64-fastbuild/bin/node_modules/.aspect_rules_js/[email protected]_1687856300/node_modules/jotai-devtools/dist'

Seems to be an issue w/ how PNPM handles module resolution. It expects jotai to be included as a regular dependency rather than a devDependency. Would there be any downside to moving jotai over to a regular dependency? We were forced to use a PNPM package extension to work around this:

// package.json
"pnpm": {
    "packageExtensions": {
      "jotai-devtools": {
        "dependencies": {
          "jotai": "*"
        }
      }
    }

Unable to import jotai-devtools within an expo + react native app

Discussed in #41

Unable to import hooks from jotai-devtools/utils in an expo + react native app.

This should work

import { useAtomsDevtools } from 'jotai-devtools/utils';

export const AtomsDevtools = () => {
  useAtomsDevtools('store-name-here');
  return null;
};

See discussions/41#discussioncomment-5393898

Originally posted by madsbuch March 12, 2023
We just had to stop using jotai-devtools as it includes a dependency on the https://mantine.dev/ framework.

Generally, it would be nice if packages like this one were headless and do not expect a document or to be run in a browser.

Anyways, nice work and thanks for the time we got to have together!

Error when building in next.js

SyntaxError: Named export 'IconAlertCircle' not found. The requested module '@tabler/icons' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from '@tabler/icons';
const {useEffect: rt,useRef: qo}from"react";import{Tabs: nt}from"@mantine/core";import{useAtomValue: ko}from"jotai/react";import{atom: xt}from"jotai/vanilla";var de="<unlabeled-atom>",h={minHeight:200,maxHeight:"90%",defaultHeight:370};var g=xt({height:h.defaultHeight,isDragging:!1});import{useAtomValue: ht,useSetAtom: Lt}from"jotai/react";import{atom: Ht}from"jotai/vanilla";var fe=Ht(void 0),ce=()=>ht(fe,s()),Te=()=>Lt(fe,s());import*as U from"react";import{Flex: Qe}from"@mantine/core";import{Panel: we,PanelGroup: zo}from"react-resizable-panels";import F from"react";import{Box: be}from"@mantine/core";import{PanelResizeHandle: vt}from"react-resizable-panels";var Xt={display:"flex",alignItems:"center","._jotai-devtools-internal-panel-resize-handle":{transition:"max-height, min-height, height, 0.2s ease-out"},"[data-resize-handle-active] &, &:hover":{"._jotai-devtools-internal-panel-resize-handle":{height:"90%",minHeight:"90%",maxHeight:"90%"}}},gt={borderRadius:"2rem",verticalAlign:"middle"},ye=()=>F.createElement(vt,null,F.createElement(be,{p:"5",h:"100%",sx:Xt},F.createElement(be,{className:"_jotai-devtools-internal-panel-resize-handle",mah:100,mih:50,h:"20%",w:5,m:5,bg:c("gray.3","gray.7"),sx:gt})));import*as R from"react";import{Box: no,LoadingOverlay: so,Text: mo}from"@mantine/core";import{useAtomValue: lo}from"jotai/react";import{atom: Q}from"jotai/vanilla";import{atomWithDefault: Bt}from"jotai/vanilla/utils";import{useAtom: Pt,useAtomValue: mn}from"jotai/react";import{atom: At}from"jotai/vanilla";var I=At([]),Je=()=>Pt(I,s());var z=e=>e||de;var Ue=(e,t)=>{let o=e.trim().toLocaleLowerCase();return o?t.filter(([r])=>z(r?.debugLabel).toLocaleLowerCase().includes(o)):t};var Se=e=>e.filter(([t])=>!t?.debugPrivate);var N=Q(void 0),Y=Q(""),Re=Bt(e=>{let t=Ue(e(Y),e(I)),{shouldShowPrivateAtoms:o}=e(X);return o?t:Se(t)}),Ve=Q(e=>e(Y),(e,t,o)=>{t(Y,o)});import E from"react";import{Stack: ro}from"@mantine/core";var Dt=e=>typeof e?.read=="function"||typeof e?.write=="function"||!!e?.init||!!e?.debugLabel,L=e=>e instanceof Promise?"promise":Array.isArray(e)?"array":e===null?"null":Dt(e)?"atom":typeof e;import{useEffect: Et,useReducer: Zt}from"react";import{useSetAtom: jt}from"jotai/react";import{useStore: Wt}from"jotai/react";var P=()=>{let e=ce();return Wt(e?{store:e}:void 0)};import Kt from"react";var Me=e=>e instanceof Promise,ze=Kt.use||(e=>{if(e.status==="pending")throw e;if(e.status==="fulfilled")return e.value;throw e.status==="rejected"?e.reason:(e.status="pending",e.then(t=>{e.status="fulfilled",e.value=t},t=>{e.status="rejected",e.reason=t}),e)});var Ft=e=>e?.isJotaiDevTools,Ne=void 0;function xe(e){let t=P(),o=j(),r=jt(N,s()),[[p,f,d],n]=Zt(u=>{let M=t.get(e);return Object.is(u[0],M)&&u[1]===t&&u[2]===e?u:[M,t,e]},void 0,()=>[t.get(e),t,e]),i=p;return(f!==t||d!==e)&&(n(),i=t.get(e)),Et(()=>{if(!t.dev_subscribe_state)return;let u=t.dev_subscribe_state;typeof t.dev_subscribe_store=="function"&&(u=t.dev_subscribe_store);let M=()=>{if(typeof Ne=="number"){setTimeout(n,Ne);return}n()};M.isJotaiDevTools=!0;let Tt=u?.(yt=>{if(yt!=="unsub")return;let re=o.get(N);if(re){let{l:Jt=[],t:ne}=t.dev_get_mounted?.(re.atom)||{};if(Array.from(Jt).every(Ft)&&ne&&ne?.size<=1)return r(void 0)}}),bt=t.sub(e,M);return n(),()=>{Tt?.(),bt()}},[t,r,o,e]),Me(i)?ze(i):i}import*as S from"react";import{Box: Qt,Code: wt,List: Le,Text: He}from"@mantine/core";import{useEffect: It,useMemo: Yt}from"react";var w=()=>{let t={store:P()};return se(t)},he=()=>{let e=w(),[t,o]=Je(),r=Yt(()=>Array.from(e.values),[e.values]);return It(()=>{o(r)},[o,r]),t};var ve=({atom:e})=>{let{dependents:t}=w(),o=ue(),r=S.useMemo(()=>{let d=Array.from(t.get(e)||[]).filter(n=>n.toString()!==e.toString());return o.shouldShowPrivateAtoms?d:d.filter(i=>!i?.debugPrivate)},[t,o.shouldShowPrivateAtoms,e]),p=S.useMemo(()=>r.map((f,d)=>{let n=z(f?.debugLabel);return S.createElement(Le.Item,{key:`${d}-${f.toString()}-dependents-list`},S.createElement(wt,{"data-testid":`dependents-list-item-${n}-${d}`},n))}),[r]);return S.createElement(Qt,null,S.createElement(He,{fw:"bold",mb:10,mt:20},"Dependents"),p.length?S.createElement(Le,{type:"ordered",mb:10},p):S.createElement(He,{size:"sm",mb:10},"No dependents"))};import*as T from"react";import{Box: Xe,Code: Ot,Text: ge,Title: Gt}from"@mantine/core";var O=({label:e,value:t,color:o})=>T.createElement(Xe,{mb:10},T.createElement(ge,{tt:"uppercase",fz:10,fw:"bold",color:"gray","data-testid":`display-detail-item-label-${e}`},e),T.createElement(Ot,{"data-testid":`display-detail-item-value-${t}`,color:o},t)),G=T.memo(({debugLabel:e,atomValueType:t,isAtomPrivate:o})=>T.createElement(Xe,null,T.createElement(Gt,{size:"h3",mb:10},"Atom Details"),T.createElement(ge,{fw:"bold",mb:10},"Meta"),T.createElement(O,{label:"Debug Label",value:z(e)}),T.createElement(O,{label:"Value type",value:t}),o&&T.createElement(O,{label:"Private",value:"Yes",color:"red"})));G.displayName="AtomMetaDetails";import K from"react";import{Box: to,Text: oo}from"@mantine/core";import{serialize: qt}from"superjson";var kt=["bigint","symbol","undefined","function"],q=Symbol("parsing-error"),Pe=e=>{let t=L(e);if(kt.includes(t))return String(e);let{json:o}=qt(e);try{let r=JSON.stringify(o,null,2);return typeof r>"u"?String(e):r}catch{return q}};import{createEmotionCache: Ct}from"@mantine/core";var A,Ae=e=>()=>{if(A)return A;let t={key:"jotai-devtools"};return e&&(t.nonce=e),A=Ct(t),A};import*as B from"react";import{Text: Be}from"@mantine/core";import{IconAlertCircle: $t}from"@tabler/icons";var De={display:"flex",alignItems:"center"},We=({message:e})=>{let t=c("red.8","red.5");return B.createElement(Be,{size:"sm",fw:"500",color:t,sx:De},B.createElement(Be,{mr:5,sx:De},B.createElement($t,{size:16})),e)};import*as W from"react";import*as Ke from"react";import{Prism: _t}from"@mantine/prism";var D=({children:e,...t})=>Ke.createElement(_t,{...t},e);var eo=["object","array","null","undefined","function","symbol"],Ee=e=>{let t=L(e);return eo.includes(t)?"javascript":"markdown"},Ze=W.memo(({prismLanguageType:e,value:t})=>W.createElement(D,{language:e,mb:10,copyLabel:"Copy value","data-testid":"atom-parsed-value"},t));var je=({atomValue:e})=>{let t=Ee(e),o=Pe(e);return K.createElement(to,null,K.createElement(oo,{fw:"bold",mb:"sm"},"Raw value"),o===q?K.createElement(We,{message:"Failed to parse the value of the atom"}):K.createElement(Ze,{value:o,prismLanguageType:t}))};var Fe=({atom:e})=>{let t=xe(e),o=L(t);return E.createElement(ro,{h:"auto"},E.createElement(G,{debugLabel:e?.debugLabel,atomValueType:o,isAtomPrivate:e?.debugPrivate}),E.createElement(je,{atomValue:t}),E.createElement(ve,{atom:e}))};var ao={position:"relative",top:"50%",transform:"translateY(-50%)"},k=R.memo(()=>{let e=lo(N,s()),t={color:c("dark","white")};return e?R.createElement(R.Suspense,{fallback:R.createElement(so,{visible:!0,overlayBlur:2,loaderProps:t})},R.createElement(Fe,{atom:e.atom})):R.createElement(no,{sx:ao},R.createElement(mo,{w:"100%",ta:"center"},"Select an atom from the left panel to view the details"," "))});k.displayName="AtomDetail";import*as a from"react";import{Box: To,Group: bo,Text: yo,TextInput: Jo}from"@mantine/core";import{IconAlertCircle: Uo}from"@tabler/icons";import{useAtom: Ie,useAtomValue: So}from"jotai/react";import*as J from"react";import{NavLink: io,Text: uo}from"@mantine/core";import{IconChevronRight: po}from"@tabler/icons";var fo=e=>({fontFamily:e.fontFamilyMonospace||"JetBrains Mono"}),co=()=>({borderRadius:5}),C=J.memo(({label:e,onClick:t,pos:o,isActive:r})=>J.createElement(io,{label:J.useMemo(()=>J.createElement(uo,{sx:fo},z(e)),[e]),variant:"filled",sx:co,active:r,color:c("dark","gray"),onClick:J.useCallback(()=>t(o),[t,o]),rightSection:J.useMemo(()=>J.createElement(po,{size:12,stroke:1.5}),[])}));C.displayName="AtomListItem";var Ro={position:"sticky",top:0},Vo=a.memo(()=>{let[e,t]=Ie(Ve,s());return a.createElement(Jo,{label:"Search",placeholder:"atom debug label",pt:10,pb:10,sx:Ro,value:e,onChange:r=>{let{target:{value:p}}=r;t(p)}})}),Mo={overflow:"auto"},Ye=()=>{he();let e=So(Re,s()),[t,o]=Ie(N,s()),r=a.useRef(e);a.useEffect(()=>{r.current=e},[e]);let p=a.useCallback(n=>{if(!r.current[n])throw new Error(`Unable to find atom at ${n} index`);o(i=>{let u=r.current[n]?.[0];if(!(!u||i?.atomKey===u?.toString()))return{atomKey:u?.toString(),atom:u}})},[o]),f=a.useMemo(()=>e.map(([n],i)=>a.createElement(C,{key:`atom-list-item-${n.toString()+i}`,label:n.debugLabel,onClick:p,pos:i,isActive:t?.atomKey===n.toString(),atomKey:n.toString()})),[e,t,p]),d=!e.length;return a.createElement(a.Fragment,null,a.createElement(Vo,null),a.createElement(To,{sx:Mo},f),d&&a.createElement(bo,{mt:20,position:"center"},a.createElement(Uo,{size:16}),a.createElement(yo,{fz:"sm",ml:0,"data-testid":"atom-list-no-atoms-found-message"},"No Atoms found!")))};var Oe={overflow:"auto"},No=e=>({background:e.colorScheme==="dark"?e.colors.dark[8]:e.colors.gray[2]}),Ge=U.memo(()=>U.createElement(zo,{direction:"horizontal"},U.createElement(we,{defaultSize:50,minSize:30,style:Oe},U.createElement(Qe,{p:10,pt:0,h:"100%",direction:"column",sx:No},U.createElement(Ye,null))),U.createElement(ye,null),U.createElement(we,{defaultSize:50,minSize:30,style:Oe},U.createElement(Qe,{p:10,h:"100%",direction:"column",pos:"relative"},U.createElement(k,null)))));import*as V from"react";import{Anchor: xo,Box: ho,Flex: Lo,Text: $}from"@mantine/core";import{IconAlertCircle: Ho}from"@tabler/icons";import{ErrorBoundary: vo}from"react-error-boundary";var qe={display:"flex",alignItems:"center"},Xo=({error:e})=>{Error?.stackTraceLimit&&(Error.stackTraceLimit=5),Error?.captureStackTrace?.(e);let t=c("red.8","red.5");return V.createElement(Lo,{role:"alert",justify:"center",align:"center",h:"90%",sx:{overflow:"scroll"},"data-testid":"jotai-devtools-error-boundary"},V.createElement(ho,{w:"100%",maw:"80%",mah:"80%"},V.createElement($,{size:"md",fw:"500",color:t,sx:qe,mb:5},V.createElement($,{mr:5,sx:qe},V.createElement(Ho,{size:16})),"Uh-oh, something went wrong."),V.createElement($,{size:"sm",color:t,mb:"sm"},"If you believe this to be a bug, please file an issue on"," ",V.createElement(xo,{href:"https://github.com/jotaijs/jotai-devtools/issues",color:t,td:"underline",target:"_blank",rel:"noreferrer noopener"},"Jotai DevTool's GitHub repo")," ","with a minimal reproduction and the following error"),V.createElement(D,{language:"javascript"},e.stack?.toString()||e.message)))},ke=({children:e})=>V.createElement(vo,{FallbackComponent:Xo},e);import*as b from"react";import{ActionIcon: Do,Badge: Wo,Box: Ko,Flex: $e,Group: Eo,Title: Zo}from"@mantine/core";import{IconMinus: jo}from"@tabler/icons";import{useSetAtom: Fo}from"jotai/react";import _ from"react";import{ActionIcon: go,useMantineColorScheme: Po}from"@mantine/core";import{IconMoonStars: Ao,IconSun: Bo}from"@tabler/icons";var Ce=()=>{let{colorScheme:e,toggleColorScheme:t}=Po(),o=e==="dark";return _.createElement(go,{variant:"filled",color:o?"gray":"dark",onClick:()=>t(),title:"Toggle color scheme"},o?_.createElement(Bo,{size:16}):_.createElement(Ao,{size:16}))};var Io={position:"sticky",top:0,zIndex:1,width:"100%"},Yo={userSelect:"none"},ee=b.memo(()=>{let e=Fo(v,s());return b.createElement(Ko,{sx:Io},b.createElement($e,{justify:"space-between",align:"center",p:10},b.createElement(Eo,{mr:10},b.createElement(Zo,{size:"h4",sx:Yo},"\u{1F47B}\xA0J\u014Dtai DevTools"),b.createElement(Wo,{color:"orange",size:"xs"},"Alpha")),b.createElement($e,{align:"center"},b.createElement(Ce,null),b.createElement(Do,{ml:10,title:"Minimize panel",radius:"md",onClick:()=>e(!1)},b.createElement(jo,{size:16})))))});ee.displayName="Header";import*as _e from"react";import{Box: Qo}from"@mantine/core";import{useSetAtom: wo}from"jotai/react";var Oo={width:"100%",height:5,cursor:"row-resize",zIndex:2,position:"absolute",top:-2},et=({shellRef:e})=>{let t=wo(g,s());return _e.createElement(Qo,{sx:Oo,onMouseDown:r=>{let p=r.clientY,{height:f=500}=e?.current?.getBoundingClientRect()||{},d=i=>{i.preventDefault();let u=f+p-i.clientY;t(M=>({...M,isDragging:!0,height:Math.max(u,h.minHeight)}))},n=()=>{t(i=>({...i,isDragging:!1})),document.removeEventListener("mousemove",d,!1),document.removeEventListener("mouseUp",n,!1)};document.addEventListener("mousemove",d,!1),document.addEventListener("mouseup",n,!1)},"data-testid":"shell-resize-bar"})};import*as H from"react";import{Tabs: tt}from"@mantine/core";import{IconLayoutList: Go}from"@tabler/icons";var te=H.memo(()=>H.createElement(tt.List,null,H.createElement(tt.Tab,{value:"atom-viewer",icon:H.createElement(Go,{size:14})},"Atom Viewer")));te.displayName="TabsHeader";var ot=e=>({position:"fixed",left:0,bottom:0,width:"calc(100% - 20px)",borderColor:e.colorScheme==="dark"?e.colors.dark[4]:e.colors.gray[3],borderWidth:1,borderStyle:"solid",borderRadius:"8px",background:e.colorScheme==="dark"?e.colors.dark[7]:e.white,display:"flex",flexDirection:"column",zIndex:99999});var st=({store:e})=>{let t=Te();rt(()=>{t(e)},[t,e]);let o=qo(null),{height:r}=ko(g,s());return rt(()=>(document.body.style.paddingBottom=r+"px",()=>{document.body.style.paddingBottom="0px"}),[r]),x.createElement(nt,{keepMounted:!1,variant:"default",defaultValue:"atom-viewer",m:10,sx:ot,h:r,mah:h.maxHeight,ref:o,className:"jotai-devtools-shell","data-testid":"jotai-devtools-shell"},x.createElement(et,{shellRef:o}),x.createElement(ee,null),x.createElement(ke,null,x.createElement(te,null),x.createElement(nt.Panel,{value:"atom-viewer",h:"100%",sx:{overflow:"hidden",borderBottomLeftRadius:"7px",borderBottomRightRadius:"7px"}},x.createElement(Ge,null))))};var er=()=>({position:"fixed",left:10,bottom:10,borderRadius:"50%",width:"4rem",height:"4rem",zIndex:99999,img:{height:"2rem"}}),tr=y.forwardRef((e,t)=>{let o=_o(v,s());return y.createElement(Co,{variant:"filled",color:c("dark","gray.3"),onClick:()=>o(!0),sx:er,ref:t,title:"Open Jotai Devtools",className:"jotai-devtools-trigger-button"},y.createElement("img",{src:pe,alt:"Jotai Mascot"}))}),mt=({isInitialOpen:e=!1,store:t})=>{let[o,r]=$o(v,s());return y.useEffect(()=>{typeof o!="boolean"&&r(e)},[]),y.createElement(y.Fragment,null,o?y.createElement(st,{store:t}):y.createElement(tr,null))};import{css: ir} = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:190:5) {
  type: 'SyntaxError'
}
info  - Collecting page data .%

TypeError: context.stylis is not a function

I installed DevTools and I'm seeing an error. Any idea what might be happening?

I found this tip, but removing node_modules didn't help.

storybookjs/storybook#6093

import React from "react";
import { createStore } from "jotai/vanilla";
import { Provider as JotaiProvider } from "jotai/react";
import { DevTools } from "jotai-devtools";

export const store = createStore();

export const Provider = ({ children }) => {
    return (
        <JotaiProvider store={store}>
            <DevTools store={store} />
            {children}
        </JotaiProvider>
    );
};
Γ—
TypeError: context.stylis is not a function
insertStyles
src/cra/node_modules/@emotion/utils/dist/utils.browser.esm.js:29
  26 |   context.registered[context.key + "-" + insertable.name] = insertable.styles;
  27 | }
  28 | if (context.inserted[insertable.name] === undefined) {
> 29 |   var rules = context.stylis("." + context.key + "-" + insertable.name, insertable.styles);
     | ^  30 |   context.inserted[insertable.name] = true;
  31 |   {
  32 |     rules.forEach(context.sheet.insert, context.sheet);
View compiled
css
src/cra/node_modules/jotai-devtools/node_modules/@mantine/core/node_modules/@mantine/styles/esm/tss/use-css.js:77
  74 |     args
  75 |   } = getRef(styles);
  76 |   const serialized = serializeStyles(args, cache.registered);
> 77 |   insertStyles(cache, serialized, false);
     | ^  78 |   return `${cache.key}-${serialized.name}${ref === void 0 ? "" : ` ${ref}`}`;
  79 | };
  80 | const cx = function () {

React 16 compatibility

Hello, recently in a React 16 project using jotai-devtools we started getting error due to undefined useId function.

Debugging a bit I discovered that it appeared after upgrading jotai-devtools from 0.7.1 to 0.10.

I saw that internally it doesn't use React 18 useId, so it probably a lib used here installed between 0.7.1 and 0.10.0

CSS leaking

There is CSS leaking after updating from 0.8.0 to 0.9.0. It's setting background color, font, and others on components that are not Jotai Dev tools.

I noticed that it's adding the whole @mantine css to my project.

Issue with NextJs 13 and Mantine v7

I'm getting the following errors when using mantine v7

β¨― node_modules\@mantine\prism\cjs\Prism\Prism.styles.js (7:0) @ eval
β¨― TypeError: core.createStyles is not a function

⚠ ./node_modules/@mantine/prism/esm/PrismTabs/PrismTabs.styles.js
Attempted import error: 'createStyles' is not exported from '@mantine/core' (imported as 'createStyles').

After checking docs I found this

createStyles function is no longer available in 7.0. Use CSS Modules instead.

is support for mantine v7 planned or we should use v6?
migration guide

[Feature Request] Performance Profiling Tools

React & Chrome devtools have a number of profiling tools to help debug performance issues, e.g. flamegraphs, render charts:

React Profiler Filtering

While these are super helpful I realize they're a big undertaking. Even some preliminary bar charts or histograms would be helpful.

Render time is probably not the right metric for us here. Just to brainstorm a few:

  • Number of set calls to an atom in the last X milliseconds (how many components are writing to this atom?)
  • Data volume in the last X milliseconds (are we setting a HUGE object or binary data every time?)
  • Diff volume in the last X milliseconds (are we setting one tiny property on a HUGE object every time?)
  • Number of listeners/observer updates in the last X milliseconds (how many components are re-rendering as a result of this atom's change)?

Maybe these are not exactly right but it would be useful to have something.

@tabler/icons-react dependency is huge

Hi, thanks for this great project.

Is is possible to rejigger the build process a bit so that installing this dependency doesn't force installation of @tabler/icons-react, which is 64MB? I noticed you are only using a few icons, is it possible to make @tabler/icons-react a devDependency and distribute a built artifact that only has the icons you need (or something like that)?
Things are better now than when using @tabler/icons which was 100MB, but would be great to cut it down even more.

Our project has a big node_modules footprint which hurts our CI times, and while there are many offenders, this package is one of the biggest ones.

Thanks for you consideration!

Atoms are not visible in Redux Devtools if the atom value is not used

I am using useAtomsDevtools().

If I use useSetAtom() for atom in the react component and do not useAtom() or useAtomValue(), the atom is not displayed in the Redux Devtools.

As a workaround, I was able to display the atom in Redux Devtools by using the store obtained with useStore() and setting store.sub(atom, () => {}).

  const store = useStore();
  useEffect(() => {
    if (process.env.NODE_ENV !== 'production') {
      return store.sub(myAtom, () => {
        // nop
      });
    }
    return undefined;
  }, [store]);

Although useAtomDevtools() will allow you to display with the Redux Devtools, we prefer not to use it because it causes unnecessary re-rendering.

It would be nice if useAtomsDevtools() would also display the atom when useSetAtomValue() is used.

`<DevTools>`: Make body padding optional

When testing the <DevTools> component, I noticed that it always adds padding to the <body> element.
This totally makes sense for "normal" websites that are scrollable vertically, but when having a web app that uses 100% height of the window, the padding can be interfering with the pages content and break the layout.

How about a prop like options.noBodyPadding or something like that to make this optional?

I think that should be pretty easy to do when looking at this code:

src/DevTools/Extension/components/Shell/Shell.tsx

  useEffect(() => {
    // Allocating more height at the end of the content allows users to scroll down fully
    // FIXME should we handle a use-case where there is padding set around `body`?
    document.body.style.paddingBottom = height + 'px';

    return () => {
      document.body.style.paddingBottom = `0px`;
    };
  }, [height]);

Let me know if you are interested and I can create a PR for that :)

Export <DevToolsPanel />

πŸ‘‹ folks!

In addition to rendering a toggle button in a corner, some DevTools provide an additional export for the embedded mode. For example: https://tanstack.com/query/v4/docs/react/devtools#embedded-mode

// normal mode
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

// embedded mode
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'

It would be nice if this was the case for jotai-devtools too:

+ import { DevToolsPanel } from 'jotai-devtools';

In the meantime, here is my workaround:

import { css, Global } from '@emotion/react';
import { DevTools as JotaiDevTools } from 'jotai-devtools';

// Context: https://github.com/jotaijs/jotai-devtools/issues/58
// To be removed if JotaiDevToolsPanel is exported upstream
const jotaiDevToolsStyleHack = css`
  /* hide trigger button */
  .jotai-devtools-trigger-button {
    display: none !important;
  }

  /* position the panel relative to its container */
  .jotai-devtools-shell {
    position: relative !important;
    z-index: auto !important;
  }

  /* hide panel header to save space and to make 'minimize' unreachable */
  .jotai-devtools-shell > div:nth-child(2) {
    display: none !important;
  }
`;

export default function App() {
  return (
    <div>
      <div>my content</div>

      <div className="my-custom-jotai-devtools-container">
        <Global styles={jotaiDevToolsStyleHack} />
        <JotaiDevTools isInitialOpen={true} />
      </div>
    </div>
  );
}

Side note: it’d be nice to rename DevTools into JotaiDevTools in module exports. Otherwise, if a component renders several DevTool panels it is necessary to use as to avoid confusion. The old export can be left as is or marked as deprecated.

- import { DevTools as JotaiDevTools } from 'jotai-devtools';
+ import { JotaiDevTools } from 'jotai-devtools';

The same could apply to the new panel export, if it is implemented:

+ import { JotaiDevToolsPanel } from 'jotai-devtools';

Cheers! πŸ™Œ

Build breaking in NextJS 13.2

Hi there, I'm using NextJS 13.2 and it seems that the build breaks.

At first install, I got this error (expected):
image

Which is expected, it means I haven't added:

transpilePackages: ['jotai-devtools'],

After adding it, the build failed repeatedly saying that it couldn't find the corresponding chunk (sorry, didn't get a screenshot of that!).

I updated react-resizable-panels to the latest version in my project and started next again: it started working.

However, now I'm trying to reproduce the error back and I can't get it to fail again... πŸ€”

So, I'm not sure what happened, I created a PR to update react-resizable-panels just in case, since I think updating the package won't hurt.

Also, I'm a bit confused about the NPM Caret Version docs: https://github.com/npm/node-semver#caret-ranges-123-025-004
(check the "leftmost zero" rules...)

But the version of react-resizable-panels that I got when I installed jotai-devtools was not the latest, it was the one in jotai-devtools's package.json.

Related PR: #84

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.