├── .prettierignore ├── benchmarks ├── .gitignore ├── simple-read.ts ├── simple-write.ts └── subscribe-write.ts ├── website ├── src │ ├── styles │ │ ├── base.css │ │ ├── utilities.css │ │ ├── index.css │ │ ├── fonts.css │ │ ├── pmndrs.css │ │ └── layout.css │ ├── components │ │ ├── external-link.js │ │ ├── main.js │ │ ├── wrapper.js │ │ ├── footer.js │ │ ├── client-only.js │ │ ├── headline.js │ │ ├── tabs.js │ │ ├── search-button.js │ │ ├── inline-code.js │ │ ├── stackblitz.js │ │ ├── support-modal.js │ │ ├── code.js │ │ ├── modal.js │ │ ├── code-sandbox.js │ │ ├── layout.js │ │ ├── shelf.js │ │ ├── mdx.js │ │ ├── extensions-demo.js │ │ ├── credits.js │ │ ├── intro.js │ │ ├── toggle.js │ │ ├── jotai.js │ │ ├── core-demo.js │ │ ├── menu.js │ │ ├── meta.js │ │ └── logo.js │ ├── pages │ │ ├── 404.js │ │ └── docs │ │ │ └── {Mdx.slug}.js │ ├── hooks │ │ └── index.js │ ├── atoms │ │ └── index.js │ ├── utils │ │ └── index.js │ └── api │ │ └── contact.js ├── public │ └── robots.txt ├── static │ ├── fonts │ │ ├── meslo.woff2 │ │ ├── inter-var.woff2 │ │ └── inter-italic-var.woff2 │ ├── robots.txt │ └── favicon.svg ├── gatsby-browser.js ├── postcss.config.js ├── reach-router.js ├── jsconfig.json ├── README.md ├── .babelrc ├── api │ └── contact.js ├── gatsby-ssr.js ├── .gitignore ├── gatsby-shared.js ├── tailwind.config.js └── package.json ├── tests ├── setup.ts ├── test-utils.ts ├── vanilla │ ├── utils │ │ ├── atomWithRefresh.test.ts │ │ ├── atomWithReset.test.ts │ │ ├── loadable.test.ts │ │ ├── atomWithLazy.test.ts │ │ └── types.test.tsx │ └── basic.test.tsx └── react │ ├── utils │ └── types.test.tsx │ └── provider.test.tsx ├── pnpm-workspace.yaml ├── examples ├── hello │ ├── src │ │ ├── vite-env.d.ts │ │ ├── index.tsx │ │ ├── style.css │ │ └── App.tsx │ ├── vite.config.ts │ ├── index.html │ ├── package.json │ ├── tsconfig.json │ ├── README.md │ └── public │ │ └── index.html ├── mega-form │ ├── src │ │ ├── vite-env.d.ts │ │ ├── index.tsx │ │ ├── useAtomSlice.ts │ │ ├── style.css │ │ └── initialValue.ts │ ├── vite.config.ts │ ├── index.html │ ├── public │ │ └── index.html │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── starter │ ├── src │ │ ├── vite-env.d.ts │ │ ├── assets │ │ │ └── jotai-mascot.png │ │ ├── index.css │ │ └── index.tsx │ ├── vite.config.ts │ ├── index.html │ ├── package.json │ ├── tsconfig.json │ └── README.md ├── todos │ ├── src │ │ ├── vite-env.d.ts │ │ ├── index.tsx │ │ └── styles.css │ ├── vite.config.ts │ ├── tsconfig.json │ ├── index.html │ ├── package.json │ ├── README.md │ └── public │ │ └── index.html ├── hacker_news │ ├── src │ │ ├── vite-env.d.ts │ │ ├── index.tsx │ │ ├── styles.css │ │ └── App.tsx │ ├── vite.config.ts │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── public │ │ └── index.html ├── text_length │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── index.tsx │ │ └── App.tsx │ ├── public │ │ ├── castle.jpg │ │ └── snippet.png │ ├── vite.config.ts │ ├── package.json │ ├── tsconfig.json │ ├── README.md │ └── index.html └── todos_with_atomFamily │ ├── src │ ├── vite-env.d.ts │ ├── index.tsx │ └── styles.css │ ├── vite.config.ts │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── public │ └── index.html ├── src ├── index.ts ├── utils.ts ├── types.d.ts ├── vanilla │ ├── utils │ │ ├── constants.ts │ │ ├── atomWithLazy.ts │ │ ├── atomWithReducer.ts │ │ ├── atomWithReset.ts │ │ ├── atomWithDefault.ts │ │ ├── atomWithRefresh.ts │ │ ├── selectAtom.ts │ │ ├── freezeAtom.ts │ │ └── loadable.ts │ ├── typeUtils.ts │ ├── utils.ts │ └── store.ts ├── react.ts ├── react │ ├── utils.ts │ ├── utils │ │ ├── useResetAtom.ts │ │ ├── useAtomCallback.ts │ │ ├── useReducerAtom.ts │ │ └── useHydrateAtoms.ts │ ├── useSetAtom.ts │ ├── Provider.ts │ └── useAtom.ts ├── vanilla.ts └── babel │ ├── preset.ts │ ├── utils.ts │ └── plugin-debug-label.ts ├── img ├── jotai-mascot.png ├── jotai-header-dark.png ├── jotai-opengraph.png ├── jotai-course-banner.jpg └── jotai-header-light.png ├── .github ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml ├── workflows │ ├── compressed-size.yml │ ├── preview-release.yml │ ├── livecodes-post-comment.yml │ ├── livecodes-preview.yml │ ├── publish.yml │ ├── test.yml │ ├── ecosystem-ci.yml │ └── test-multiple-versions.yml ├── DISCUSSION_TEMPLATE │ └── bug-report.yml └── FUNDING.yml ├── docs ├── recipes │ ├── atom-with-refresh.mdx │ ├── atom-with-toggle-and-storage.mdx │ ├── use-reducer-atom.mdx │ ├── atom-with-toggle.mdx │ ├── atom-with-compare.mdx │ ├── custom-useatom-hooks.mdx │ ├── use-atom-effect.mdx │ └── atom-with-broadcast.mdx ├── guides │ ├── waku.mdx │ ├── remix.mdx │ ├── vite.mdx │ ├── react-native.mdx │ ├── using-store-outside-react.mdx │ └── resettable.mdx ├── utilities │ ├── reducer.mdx │ └── callback.mdx ├── core │ ├── store.mdx │ └── provider.mdx ├── extensions │ ├── zustand.mdx │ ├── redux.mdx │ ├── optics.mdx │ └── trpc.mdx ├── basics │ ├── concepts.mdx │ └── showcase.mdx ├── index.mdx └── third-party │ └── history.mdx ├── .codesandbox └── ci.json ├── .gitignore ├── babel.config.mjs ├── tsconfig.json ├── LICENSE ├── vitest.config.mts └── .livecodes └── react.json /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | pnpm-lock.yaml 3 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | /*.json 2 | /*.html 3 | -------------------------------------------------------------------------------- /website/src/styles/base.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | -------------------------------------------------------------------------------- /website/src/styles/utilities.css: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | -------------------------------------------------------------------------------- /tests/setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest' 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - . 3 | minimumReleaseAge: 1440 4 | -------------------------------------------------------------------------------- /examples/hello/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/mega-form/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/starter/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/todos/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vanilla.ts' 2 | export * from './react.ts' 3 | -------------------------------------------------------------------------------- /examples/hacker_news/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/text_length/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /img/jotai-mascot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/img/jotai-mascot.png -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export * from './vanilla/utils.ts' 2 | export * from './react/utils.ts' 3 | -------------------------------------------------------------------------------- /examples/todos_with_atomFamily/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /img/jotai-header-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/img/jotai-header-dark.png -------------------------------------------------------------------------------- /img/jotai-opengraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/img/jotai-opengraph.png -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ImportMeta { 2 | env?: { 3 | MODE: string 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /img/jotai-course-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/img/jotai-course-banner.jpg -------------------------------------------------------------------------------- /img/jotai-header-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/img/jotai-header-light.png -------------------------------------------------------------------------------- /website/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Sitemap: https://jotai.org/sitemap/sitemap-index.xml 4 | -------------------------------------------------------------------------------- /website/static/fonts/meslo.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/website/static/fonts/meslo.woff2 -------------------------------------------------------------------------------- /website/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Sitemap: https://jotai.org/sitemap/sitemap-index.xml 4 | -------------------------------------------------------------------------------- /website/static/fonts/inter-var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/website/static/fonts/inter-var.woff2 -------------------------------------------------------------------------------- /examples/text_length/public/castle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/examples/text_length/public/castle.jpg -------------------------------------------------------------------------------- /examples/text_length/public/snippet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/examples/text_length/public/snippet.png -------------------------------------------------------------------------------- /examples/starter/src/assets/jotai-mascot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/examples/starter/src/assets/jotai-mascot.png -------------------------------------------------------------------------------- /website/static/fonts/inter-italic-var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/jotai/HEAD/website/static/fonts/inter-italic-var.woff2 -------------------------------------------------------------------------------- /website/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import './src/styles/index.css' 2 | 3 | export { wrapRootElement, wrapPageElement } from './gatsby-shared.js' 4 | -------------------------------------------------------------------------------- /src/vanilla/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const RESET: unique symbol = Symbol( 2 | import.meta.env?.MODE !== 'production' ? 'RESET' : '', 3 | ) 4 | -------------------------------------------------------------------------------- /website/static/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 👻 3 | 4 | -------------------------------------------------------------------------------- /examples/hello/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }) 7 | -------------------------------------------------------------------------------- /examples/todos/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }) 7 | -------------------------------------------------------------------------------- /examples/hacker_news/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }) 7 | -------------------------------------------------------------------------------- /examples/mega-form/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }) 7 | -------------------------------------------------------------------------------- /examples/text_length/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }) 7 | -------------------------------------------------------------------------------- /examples/todos_with_atomFamily/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }) 7 | -------------------------------------------------------------------------------- /website/src/styles/index.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | @import './fonts.css'; 3 | @import './pmndrs.css'; 4 | @import './layout.css'; 5 | @import './components.css'; 6 | @import './utilities.css'; 7 | -------------------------------------------------------------------------------- /website/src/styles/fonts.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=block'); 2 | @import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=block'); 3 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Related Bug Reports or Discussions 2 | 3 | Fixes # 4 | 5 | ## Summary 6 | 7 | ## Check List 8 | 9 | - [ ] `pnpm run fix` for formatting and linting code and docs 10 | -------------------------------------------------------------------------------- /website/postcss.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | module.exports = { 3 | plugins: { 4 | 'postcss-import': {}, 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /examples/starter/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react' 2 | import { defineConfig } from 'vite' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /website/reach-router.js: -------------------------------------------------------------------------------- 1 | exports.default = function (source) { 2 | if (source.includes('exports.BaseContext')) { 3 | return source 4 | } else { 5 | return source + 'exports.BaseContext = BaseContext;' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/src/components/external-link.js: -------------------------------------------------------------------------------- 1 | export const ExternalLink = ({ to, children, ...rest }) => { 2 | return ( 3 | 4 | {children} 5 | 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Assigned issue 3 | about: This is to create a new issue that already has an assignee. Please open a new discussion otherwise. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | -------------------------------------------------------------------------------- /src/react.ts: -------------------------------------------------------------------------------- 1 | export { Provider, useStore } from './react/Provider.ts' 2 | export { useAtomValue } from './react/useAtomValue.ts' 3 | export { useSetAtom } from './react/useSetAtom.ts' 4 | export { useAtom } from './react/useAtom.ts' 5 | -------------------------------------------------------------------------------- /examples/mega-form/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import App from './App' 3 | import './style.css' 4 | 5 | const rootElement = document.getElementById('root') 6 | createRoot(rootElement!).render() 7 | -------------------------------------------------------------------------------- /website/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "checkJs": true, 5 | "removeComments": true 6 | }, 7 | "include": ["**/*.js", "**/*.jsx"], 8 | "exclude": ["node_modules", "public"] 9 | } 10 | -------------------------------------------------------------------------------- /website/src/pages/404.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { navigate } from 'gatsby' 3 | 4 | export default function NotFoundPage() { 5 | useEffect(() => { 6 | navigate('/') 7 | }, []) 8 | 9 | return null 10 | } 11 | -------------------------------------------------------------------------------- /docs/recipes/atom-with-refresh.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: atomWithRefresh 3 | nav: 9.06 4 | keywords: creators,refresh 5 | --- 6 | 7 | `atomWithRefresh` has been provided by `jotai/utils` since v2.7.0. 8 | [Jump to the doc](../utilities/resettable.mdx#atomwithrefresh) 9 | -------------------------------------------------------------------------------- /examples/starter/src/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | height: 100%; 5 | } 6 | 7 | #root { 8 | display: flex; 9 | place-items: center; 10 | justify-content: center; 11 | 12 | color: #fff; 13 | background-color: hsl(0, 0%, 4%); 14 | } 15 | -------------------------------------------------------------------------------- /examples/todos/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import 'antd/dist/antd.css' 3 | import './styles.css' 4 | import App from './App' 5 | 6 | const rootElement = document.getElementById('root') 7 | createRoot(rootElement!).render() 8 | -------------------------------------------------------------------------------- /src/react/utils.ts: -------------------------------------------------------------------------------- 1 | export { useResetAtom } from './utils/useResetAtom.ts' 2 | export { useReducerAtom } from './utils/useReducerAtom.ts' 3 | export { useAtomCallback } from './utils/useAtomCallback.ts' 4 | export { useHydrateAtoms } from './utils/useHydrateAtoms.ts' 5 | -------------------------------------------------------------------------------- /examples/todos_with_atomFamily/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import 'antd/dist/antd.css' 3 | import './styles.css' 4 | import App from './App' 5 | 6 | const rootElement = document.getElementById('root') 7 | createRoot(rootElement!).render() 8 | -------------------------------------------------------------------------------- /website/src/components/main.js: -------------------------------------------------------------------------------- 1 | export const Main = ({ children, ...rest }) => { 2 | return ( 3 |
7 | {children} 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/text_length/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App' 4 | 5 | const rootElement = document.getElementById('root') 6 | createRoot(rootElement!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /docs/guides/waku.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Waku 3 | description: How to use Jotai with Waku 4 | nav: 8.04 5 | keywords: waku 6 | status: draft 7 | --- 8 | 9 | ### Hydration 10 | 11 | Jotai has support for hydration of atoms with `useHydrateAtoms`. The documentation for the hook can be seen [here](../utils/ssr.mdx). 12 | -------------------------------------------------------------------------------- /docs/guides/remix.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remix 3 | description: How to use Jotai with Remix 4 | nav: 8.05 5 | keywords: remix 6 | status: draft 7 | --- 8 | 9 | ### Hydration 10 | 11 | Jotai has support for hydration of atoms with `useHydrateAtoms`. The documentation for the hook can be seen [here](../utilities/ssr.mdx). 12 | -------------------------------------------------------------------------------- /website/src/components/wrapper.js: -------------------------------------------------------------------------------- 1 | export const Wrapper = ({ children, ...rest }) => { 2 | return ( 3 |
7 | {children} 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /website/src/components/footer.js: -------------------------------------------------------------------------------- 1 | import { Credits } from '../components/credits.js' 2 | 3 | export const Footer = () => { 4 | return ( 5 |
9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /examples/hacker_news/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './styles.css' 4 | import App from './App' 5 | 6 | const rootElement = document.getElementById('root') 7 | createRoot(rootElement!).render( 8 | 9 | 10 | , 11 | ) 12 | -------------------------------------------------------------------------------- /.codesandbox/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["dist"], 3 | "sandboxes": [ 4 | "new", 5 | "react-typescript-react-ts", 6 | "simple-react-browserify-x9yni", 7 | "simple-snowpack-react-o1gmx", 8 | "next-js-uo1h0", 9 | "next-js-with-custom-babel-config-komw9", 10 | "react-with-custom-babel-config-z1ebx" 11 | ], 12 | "node": "18" 13 | } 14 | -------------------------------------------------------------------------------- /website/src/components/client-only.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export const ClientOnly = ({ children }) => { 4 | const [hasMounted, setHasMounted] = useState(false) 5 | 6 | useEffect(() => { 7 | setHasMounted(true) 8 | }, []) 9 | 10 | if (!hasMounted) { 11 | return null 12 | } 13 | 14 | return children 15 | } 16 | -------------------------------------------------------------------------------- /examples/todos/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "jsx": "react-jsx" 10 | }, 11 | "include": ["./src/**/*"], 12 | "exclude": ["node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /website/src/components/headline.js: -------------------------------------------------------------------------------- 1 | import cx from 'classnames' 2 | 3 | export const Headline = ({ className = '', children }) => { 4 | return ( 5 |
11 | {children} 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | Install the dependencies: 4 | 5 | ```bash 6 | pnpm install --ignore-workspace 7 | ``` 8 | 9 | Make a cache directory: 10 | 11 | ```bash 12 | mkdir .cache 13 | ``` 14 | 15 | Run the development server: 16 | 17 | ```bash 18 | pnpm run dev 19 | ``` 20 | 21 | Open [http://localhost:9000](http://localhost:9000) with your browser to see the result. 22 | -------------------------------------------------------------------------------- /website/src/components/tabs.js: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | 3 | export const Tabs = ({ tabs = {} }) => { 4 | const tabContents = useMemo(() => Object.values(tabs), [tabs]) 5 | 6 | return ( 7 | <> 8 |
9 | {tabContents.map((content) => ( 10 |
{content}
11 | ))} 12 |
13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /examples/todos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Jotai Examples | Todos 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/src/components/search-button.js: -------------------------------------------------------------------------------- 1 | import { useSetAtom } from 'jotai' 2 | import { searchAtom } from '../atoms/index.js' 3 | import { Button } from '../components/button.js' 4 | 5 | export const SearchButton = (props) => { 6 | const setIsSearchOpen = useSetAtom(searchAtom) 7 | 8 | return ( 9 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/hacker_news/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Jotai Examples | Hacker News 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/test-utils.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export function sleep(ms: number): Promise { 4 | return new Promise((resolve) => setTimeout(resolve, ms)) 5 | } 6 | 7 | export function useCommitCount(): number { 8 | const commitCountRef = useRef(1) 9 | useEffect(() => { 10 | commitCountRef.current += 1 11 | }) 12 | // eslint-disable-next-line react-hooks/refs 13 | return commitCountRef.current 14 | } 15 | -------------------------------------------------------------------------------- /src/vanilla.ts: -------------------------------------------------------------------------------- 1 | export { atom } from './vanilla/atom.ts' 2 | export type { Atom, WritableAtom, PrimitiveAtom } from './vanilla/atom.ts' 3 | 4 | export { 5 | createStore, 6 | getDefaultStore, 7 | INTERNAL_overrideCreateStore, 8 | } from './vanilla/store.ts' 9 | 10 | export type { 11 | Getter, 12 | Setter, 13 | ExtractAtomValue, 14 | ExtractAtomArgs, 15 | ExtractAtomResult, 16 | SetStateAction, 17 | } from './vanilla/typeUtils.ts' 18 | -------------------------------------------------------------------------------- /examples/mega-form/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Jotai Examples | Mega Form 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/mega-form/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Jotai Examples | Mega Form 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/todos_with_atomFamily/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Jotai Examples | Todos with atomFamily 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/vanilla/utils/atomWithLazy.ts: -------------------------------------------------------------------------------- 1 | import { atom } from '../../vanilla.ts' 2 | import type { PrimitiveAtom } from '../../vanilla.ts' 3 | 4 | export function atomWithLazy( 5 | makeInitial: () => Value, 6 | ): PrimitiveAtom { 7 | const a = atom(undefined as unknown as Value) 8 | delete (a as { init?: Value }).init 9 | Object.defineProperty(a, 'init', { 10 | get() { 11 | return makeInitial() 12 | }, 13 | }) 14 | return a 15 | } 16 | -------------------------------------------------------------------------------- /website/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "babel-preset-gatsby", 5 | { 6 | "reactRuntime": "automatic", 7 | "targets": { 8 | "browsers": [ 9 | ">0.25%", 10 | "not dead", 11 | "not ie <=11", 12 | "not ie_mob <=11", 13 | "not op_mini all" 14 | ] 15 | } 16 | } 17 | ] 18 | ], 19 | "plugins": ["jotai/babel/plugin-react-refresh"] 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/compressed-size.yml: -------------------------------------------------------------------------------- 1 | name: Compressed Size 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | compressed_size: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v6 10 | - uses: pnpm/action-setup@v4 11 | - uses: actions/setup-node@v6 12 | with: 13 | node-version: 'lts/*' 14 | cache: 'pnpm' 15 | - uses: preactjs/compressed-size-action@v3 16 | with: 17 | pattern: './dist/**/*.{js,mjs}' 18 | -------------------------------------------------------------------------------- /src/babel/preset.ts: -------------------------------------------------------------------------------- 1 | import babel from '@babel/core' 2 | import pluginDebugLabel from './plugin-debug-label.ts' 3 | import pluginReactRefresh from './plugin-react-refresh.ts' 4 | import type { PluginOptions } from './utils.ts' 5 | 6 | export default function jotaiPreset( 7 | _: typeof babel, 8 | options?: PluginOptions, 9 | ): { plugins: babel.PluginItem[] } { 10 | return { 11 | plugins: [ 12 | [pluginDebugLabel, options], 13 | [pluginReactRefresh, options], 14 | ], 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /website/src/components/inline-code.js: -------------------------------------------------------------------------------- 1 | import cx from 'classnames' 2 | 3 | export const InlineCode = ({ dark = false, children }) => { 4 | return ( 5 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /tests/vanilla/utils/atomWithRefresh.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { createStore } from 'jotai/vanilla' 3 | import { atomWithRefresh } from 'jotai/vanilla/utils' 4 | 5 | describe('atomWithRefresh', () => { 6 | it('[DEV-ONLY] throws when refresh is called with extra arguments', () => { 7 | const atom = atomWithRefresh(() => {}) 8 | const store = createStore() 9 | const args = ['some arg'] as unknown as [] 10 | expect(() => store.set(atom, ...args)).throws() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Bug Reports 4 | url: https://github.com/pmndrs/jotai/discussions/new?category=bug-report 5 | about: Please post bug reports here. 6 | - name: Questions 7 | url: https://github.com/pmndrs/jotai/discussions/new?category=q-a 8 | about: Please post questions here. 9 | - name: Other Discussions 10 | url: https://github.com/pmndrs/jotai/discussions/new/choose 11 | about: Please post ideas and general discussions here. 12 | -------------------------------------------------------------------------------- /.github/workflows/preview-release.yml: -------------------------------------------------------------------------------- 1 | name: Preview Release 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | preview_release: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v6 10 | - uses: pnpm/action-setup@v4 11 | - uses: actions/setup-node@v6 12 | with: 13 | node-version: 'lts/*' 14 | cache: 'pnpm' 15 | - run: pnpm install 16 | - run: pnpm run build 17 | - run: pnpm dlx pkg-pr-new publish './dist' --compact --template './examples/*' 18 | -------------------------------------------------------------------------------- /website/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from 'react' 2 | 3 | export const useOnEscape = (handler) => { 4 | const handleEscape = useCallback( 5 | ({ code }) => { 6 | if (code === 'Escape') { 7 | handler() 8 | } 9 | }, 10 | [handler], 11 | ) 12 | 13 | useEffect(() => { 14 | document.addEventListener('keydown', handleEscape, false) 15 | 16 | return () => { 17 | document.removeEventListener('keydown', handleEscape, false) 18 | } 19 | }, [handleEscape]) 20 | } 21 | -------------------------------------------------------------------------------- /examples/starter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Jotai Examples | Starter 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /website/src/atoms/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/extensions */ 2 | import { atom } from 'jotai' 3 | import { atomWithStorage } from 'jotai/utils' 4 | import { atomWithImmer } from 'jotai-immer' 5 | 6 | export const menuAtom = atom(false) 7 | export const searchAtom = atom(false) 8 | export const helpAtom = atom(false) 9 | export const textAtom = atom('hello') 10 | export const uppercaseAtom = atom((get) => get(textAtom).toUpperCase()) 11 | export const darkModeAtom = atomWithStorage('darkMode', false) 12 | export const countAtom = atomWithImmer(0) 13 | -------------------------------------------------------------------------------- /src/react/utils/useResetAtom.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | import { useSetAtom } from '../../react.ts' 3 | import { RESET } from '../../vanilla/utils.ts' 4 | import type { WritableAtom } from '../../vanilla.ts' 5 | 6 | type Options = Parameters[1] 7 | 8 | export function useResetAtom( 9 | anAtom: WritableAtom, 10 | options?: Options, 11 | ): () => T { 12 | const setAtom = useSetAtom(anAtom, options) 13 | const resetAtom = useCallback(() => setAtom(RESET), [setAtom]) 14 | return resetAtom 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/livecodes-post-comment.yml: -------------------------------------------------------------------------------- 1 | name: LiveCodes Post Comment 2 | 3 | on: 4 | workflow_run: 5 | workflows: [LiveCodes Preview] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | upload: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | pull-requests: write 14 | if: > 15 | github.event.workflow_run.event == 'pull_request' && 16 | github.event.workflow_run.conclusion == 'success' 17 | steps: 18 | - uses: live-codes/pr-comment-from-artifact@v1 19 | with: 20 | GITHUB_TOKEN: ${{ github.token }} 21 | -------------------------------------------------------------------------------- /examples/starter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "jotai": "^2.10.4", 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.2.0", 18 | "@types/react-dom": "^18.2.0", 19 | "@vitejs/plugin-react": "^4.3.4", 20 | "typescript": "^5.0.0", 21 | "vite": "^6.0.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | 6 | # testing 7 | coverage 8 | 9 | # development 10 | .devcontainer 11 | .vscode 12 | 13 | # production 14 | dist 15 | build 16 | 17 | # dotenv environment variables file 18 | .env 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | # logs 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # misc 30 | .DS_Store 31 | .idea 32 | 33 | # examples 34 | examples/**/*/package-lock.json 35 | examples/**/*/yarn.lock 36 | examples/**/*/pnpm-lock.yaml 37 | examples/**/*/bun.lockb 38 | -------------------------------------------------------------------------------- /examples/hello/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | Jotai Examples | Hello 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/livecodes-preview.yml: -------------------------------------------------------------------------------- 1 | name: LiveCodes Preview 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build_and_prepare: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v6 10 | - uses: pnpm/action-setup@v4 11 | - uses: actions/setup-node@v6 12 | with: 13 | node-version: 'lts/*' 14 | cache: 'pnpm' 15 | - uses: live-codes/preview-in-livecodes@v1 16 | with: 17 | install-command: pnpm install 18 | build-command: pnpm run build 19 | base-url: 'https://{{LC::REF}}.preview-in-livecodes-demo.pages.dev' 20 | -------------------------------------------------------------------------------- /website/src/utils/index.js: -------------------------------------------------------------------------------- 1 | import { Children, isValidElement } from 'react' 2 | import kebabCase from 'just-kebab-case' 3 | 4 | export const getAnchor = (value) => { 5 | return kebabCase(getTextContent(value).toLowerCase().replaceAll("'", '')) 6 | } 7 | 8 | const getTextContent = (children) => { 9 | let text = '' 10 | 11 | Children.toArray(children).forEach((child) => { 12 | if (typeof child === 'string') { 13 | text += child 14 | } else if (isValidElement(child) && child.props.children) { 15 | text += getTextContent(child.props.children) 16 | } 17 | }) 18 | 19 | return text 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | id-token: write 9 | contents: read 10 | 11 | jobs: 12 | publish: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | - uses: pnpm/action-setup@v4 17 | - uses: actions/setup-node@v6 18 | with: 19 | node-version: 'lts/*' 20 | registry-url: 'https://registry.npmjs.org' 21 | cache: 'pnpm' 22 | - run: pnpm install 23 | - run: pnpm run build 24 | - run: npm publish 25 | working-directory: dist 26 | -------------------------------------------------------------------------------- /examples/hello/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "version": "2.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "jotai": "^2.10.4", 13 | "prismjs": "^1.23.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-prism": "4.3.2" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^18.2.0", 20 | "@types/react-dom": "^18.2.0", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "typescript": "^5.0.0", 23 | "vite": "^6.0.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/text_length/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "text_length", 3 | "version": "2.0.0", 4 | "description": "Count the length and show the uppercase of any text", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "jotai": "^2.10.4", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.2.0", 18 | "@types/react-dom": "^18.2.0", 19 | "@vitejs/plugin-react": "^4.3.4", 20 | "typescript": "^5.0.0", 21 | "vite": "^6.0.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/hello/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "jsx": "react-jsx", 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "module": "esnext", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true 18 | }, 19 | "include": ["./src/**/*"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/mega-form/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "jsx": "react-jsx", 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "module": "esnext", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true 18 | }, 19 | "include": ["./src/**/*"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/hacker_news/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "jsx": "react-jsx", 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "module": "esnext", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true 18 | }, 19 | "include": ["./src/**/*"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/text_length/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "jsx": "react-jsx", 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "module": "esnext", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true 18 | }, 19 | "include": ["./src/**/*"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /src/react/utils/useAtomCallback.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { useSetAtom } from '../../react.ts' 3 | import { atom } from '../../vanilla.ts' 4 | import type { Getter, Setter } from '../../vanilla.ts' 5 | 6 | type Options = Parameters[1] 7 | 8 | export function useAtomCallback( 9 | callback: (get: Getter, set: Setter, ...arg: Args) => Result, 10 | options?: Options, 11 | ): (...args: Args) => Result { 12 | const anAtom = useMemo( 13 | () => atom(null, (get, set, ...args: Args) => callback(get, set, ...args)), 14 | [callback], 15 | ) 16 | return useSetAtom(anAtom, options) 17 | } 18 | -------------------------------------------------------------------------------- /examples/hello/README.md: -------------------------------------------------------------------------------- 1 | # Hello [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-blue?style=flat-square&logo=stackblitz)](https://stackblitz.com/github/pmndrs/jotai/tree/main/examples/hello) 2 | 3 | ## Set up locally 4 | 5 | ```bash 6 | git clone https://github.com/pmndrs/jotai 7 | 8 | # install project dependencies & build the library 9 | cd jotai && pnpm install 10 | 11 | # move to the examples folder & install dependencies 12 | cd examples/hello && pnpm install 13 | 14 | # start the dev server 15 | pnpm dev 16 | ``` 17 | 18 | ## Set up on `StackBlitz` 19 | 20 | Link: https://stackblitz.com/github/pmndrs/jotai/tree/main/examples/hello 21 | -------------------------------------------------------------------------------- /examples/starter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "jsx": "react-jsx", 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "module": "esnext", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true 18 | }, 19 | "include": ["vite.config.ts", "./src/**/*"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/todos_with_atomFamily/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "jsx": "react-jsx", 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "module": "esnext", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true 18 | }, 19 | "include": ["./src/**/*"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/starter/README.md: -------------------------------------------------------------------------------- 1 | # Starter [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-blue?style=flat-square&logo=stackblitz)](https://stackblitz.com/github/pmndrs/jotai/tree/main/examples/starter) 2 | 3 | ## Set up locally 4 | 5 | ```bash 6 | git clone https://github.com/pmndrs/jotai 7 | 8 | # install project dependencies & build the library 9 | cd jotai && pnpm install 10 | 11 | # move to the examples folder & install dependencies 12 | cd examples/starter && pnpm install 13 | 14 | # start the dev server 15 | pnpm dev 16 | ``` 17 | 18 | ## Set up on `StackBlitz` 19 | 20 | Link: https://stackblitz.com/github/pmndrs/jotai/tree/main/examples/starter 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | - uses: pnpm/action-setup@v4 15 | - uses: actions/setup-node@v6 16 | with: 17 | node-version: 'lts/*' 18 | cache: 'pnpm' 19 | - run: pnpm install 20 | - run: pnpm run test:format 21 | - run: pnpm run test:types 22 | - run: pnpm run test:lint 23 | - run: pnpm run test:spec 24 | - run: pnpm run build # we don't have any other workflows to test build 25 | -------------------------------------------------------------------------------- /examples/mega-form/README.md: -------------------------------------------------------------------------------- 1 | # Mega form [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-blue?style=flat-square&logo=stackblitz)](https://stackblitz.com/github/pmndrs/jotai/tree/main/examples/mega-form) 2 | 3 | ## Set up locally 4 | 5 | ```bash 6 | git clone https://github.com/pmndrs/jotai 7 | 8 | # install project dependencies & build the library 9 | cd jotai && pnpm install 10 | 11 | # move to the examples folder & install dependencies 12 | cd examples/mega-form && pnpm install 13 | 14 | # start the dev server 15 | pnpm dev 16 | ``` 17 | 18 | ## Set up on `StackBlitz` 19 | 20 | Link: https://stackblitz.com/github/pmndrs/jotai/tree/main/examples/mega-form 21 | -------------------------------------------------------------------------------- /examples/mega-form/src/useAtomSlice.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { useAtom } from 'jotai' 3 | import type { PrimitiveAtom } from 'jotai' 4 | import { splitAtom } from 'jotai/utils' 5 | 6 | const useAtomSlice = (arrAtom: PrimitiveAtom) => { 7 | const [atoms, dispatch] = useAtom( 8 | useMemo(() => splitAtom(arrAtom), [arrAtom]), 9 | ) 10 | return useMemo( 11 | () => 12 | atoms.map( 13 | (itemAtom) => 14 | [ 15 | itemAtom, 16 | () => dispatch({ type: 'remove', atom: itemAtom }), 17 | ] as const, 18 | ), 19 | [atoms, dispatch], 20 | ) 21 | } 22 | 23 | export default useAtomSlice 24 | -------------------------------------------------------------------------------- /.github/DISCUSSION_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | labels: ['bug'] 2 | body: 3 | - type: markdown 4 | attributes: 5 | value: If you don't have a reproduction link, please choose a different category. 6 | - type: textarea 7 | attributes: 8 | label: Bug Description 9 | description: Describe the bug you encountered 10 | validations: 11 | required: true 12 | - type: input 13 | attributes: 14 | label: Reproduction Link 15 | description: A link to a [TypeScript Playground](https://www.typescriptlang.org/play), a [StackBlitz Project](https://stackblitz.com/) or something else with a minimal reproduction. 16 | validations: 17 | required: true 18 | -------------------------------------------------------------------------------- /examples/mega-form/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mega-form", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "serve": "vite preview" 9 | }, 10 | "dependencies": { 11 | "fp-ts": "^2.9.5", 12 | "io-ts": "^2.2.15", 13 | "jotai": "^2.10.4", 14 | "jotai-optics": "^0.4.0", 15 | "optics-ts": "^2.0.0", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.2.0", 21 | "@types/react-dom": "^18.2.0", 22 | "@vitejs/plugin-react": "^4.3.4", 23 | "typescript": "^5.0.0", 24 | "vite": "^6.0.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/hello/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | Jotai Examples | Hello 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/todos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todos", 3 | "version": "2.0.0", 4 | "description": "Record your todo list by typing them into this app", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@ant-design/icons": "^5.5.2", 13 | "@react-spring/web": "^9.2.3", 14 | "antd": "^4.16.2", 15 | "jotai": "^2.10.4", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.2.0", 21 | "@types/react-dom": "^18.2.0", 22 | "@vitejs/plugin-react": "^4.3.4", 23 | "typescript": "^5.0.0", 24 | "vite": "^6.0.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/hacker_news/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hacker_news", 3 | "version": "2.0.0", 4 | "description": "Demonstrate a news articles with jotai, hit next to see more articles.", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "serve": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@react-spring/web": "^9.2.3", 13 | "html-react-parser": "^1.2.6", 14 | "jotai": "^2.10.4", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^18.2.0", 20 | "@types/react-dom": "^18.2.0", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "typescript": "^5.0.0", 23 | "vite": "^6.0.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/utilities/reducer.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Reducer 3 | nav: 3.99 4 | keywords: reducer,action,dispatch 5 | published: false 6 | --- 7 | 8 | ## atomWithReducer 9 | 10 | Ref: https://github.com/pmndrs/jotai/issues/38 11 | 12 | ```js 13 | import { atomWithReducer } from 'jotai/utils' 14 | 15 | const countReducer = (prev, action) => { 16 | if (action.type === 'inc') return prev + 1 17 | if (action.type === 'dec') return prev - 1 18 | throw new Error('unknown action type') 19 | } 20 | 21 | const countReducerAtom = atomWithReducer(0, countReducer) 22 | ``` 23 | 24 | ### Stackblitz 25 | 26 | 27 | 28 | ## useReducerAtom 29 | 30 | See [useReducerAtom](../recipes/use-reducer-atom.mdx) recipe. 31 | -------------------------------------------------------------------------------- /babel.config.mjs: -------------------------------------------------------------------------------- 1 | export default (api, targets) => { 2 | // https://babeljs.io/docs/en/config-files#config-function-api 3 | const isTestEnv = api.env('test') 4 | 5 | return { 6 | babelrc: false, 7 | ignore: ['./node_modules'], 8 | presets: [ 9 | [ 10 | '@babel/preset-env', 11 | { 12 | loose: true, 13 | modules: isTestEnv ? 'commonjs' : false, 14 | targets: isTestEnv ? { node: 'current' } : targets, 15 | }, 16 | ], 17 | ], 18 | plugins: [ 19 | [ 20 | '@babel/plugin-transform-react-jsx', 21 | { 22 | runtime: 'automatic', 23 | }, 24 | ], 25 | ['@babel/plugin-transform-typescript', { isTSX: true }], 26 | ], 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/vanilla/typeUtils.ts: -------------------------------------------------------------------------------- 1 | import type { Atom, PrimitiveAtom, WritableAtom } from './atom.ts' 2 | 3 | export type Getter = Parameters['read']>[0] 4 | export type Setter = Parameters< 5 | WritableAtom['write'] 6 | >[1] 7 | 8 | export type ExtractAtomValue = 9 | AtomType extends Atom ? Value : never 10 | 11 | export type ExtractAtomArgs = 12 | AtomType extends WritableAtom 13 | ? Args 14 | : never 15 | 16 | export type ExtractAtomResult = 17 | AtomType extends WritableAtom 18 | ? Result 19 | : never 20 | 21 | export type SetStateAction = ExtractAtomArgs>[0] 22 | -------------------------------------------------------------------------------- /website/src/components/stackblitz.js: -------------------------------------------------------------------------------- 1 | export const Stackblitz = ({ id, file }) => { 2 | return ( 3 |
4 |