JotaiJotai

ηŠΆζ…‹
Primitive and flexible state management for React

v3 migration

Roadmap: https://github.com/pmndrs/jotai/discussions/2889

Jotai v3 focuses on modernizing the package. The core API and its behavior are unchanged. If your app runs on Jotai v2 without deprecation warnings, it should run on v3 as is.

Requirement changes

The minimum requirements are raised:

  • React 18 (v2 supports React 17)
  • TypeScript 5.5 (v2 supports TypeScript 3.8)
  • Node.js ^20.19.0 || >=22.12.0 (v2 supports Node.js 12.20)

Packaging changes

ESM only

The CJS, UMD and SystemJS builds are no longer provided. Modern bundlers work with the ESM build out of the box. The supported Node.js versions can load it even from CJS code with require().

The package exports only expose the public entry points:

  • jotai
  • jotai/utils
  • jotai/vanilla
  • jotai/vanilla/utils
  • jotai/vanilla/internals
  • jotai/react
  • jotai/react/utils

Importing other files in the package directly no longer works.

process.env.NODE_ENV is used directly

The published files read process.env.NODE_ENV directly to omit development-only checks in production. Bundlers handle it by default. If you load Jotai in a browser without a bundler (for example, with import maps), you need to define it by yourself.

globalThis.process ??= { env: { NODE_ENV: 'production' } }

ES2020 syntax

The published files are compiled with the ES2020 target. If you need to support older browsers, transpile the package in your build.

Removed features

atomFamily util

It's moved to the jotai-family package.

Previous API
import { atomFamily } from 'jotai/utils'
New API
// npm install jotai-family
import { atomFamily } from 'jotai-family'

loadable util

Use the unwrap util instead. The previous behavior can be implemented in userland:

import { atom } from 'jotai'
import { unwrap } from 'jotai/utils'
function loadable(anAtom) {
const LOADING = { state: 'loading' }
const unwrappedAtom = unwrap(anAtom, () => LOADING)
return atom((get) => {
try {
const data = get(unwrappedAtom)
if (data === LOADING) {
return LOADING
}
return { state: 'hasData', data }
} catch (error) {
return { state: 'hasError', error }
}
})
}

jotai/babel plugins

They are moved to the jotai-babel package.

Previous API
// babel config
plugins: ['jotai/babel/plugin-react-refresh']
New API
// npm install jotai-babel
plugins: ['jotai-babel/plugin-react-refresh']

setSelf option in the read function

Previous API
const anAtom = atom(async (get, { setSelf }) => {
// ...
})
New API

There is no direct replacement. Depending on the use case, onMount or jotai-effect would cover it.

delay option in useAtom and useAtomValue

Previous API
const value = useAtomValue(anAtom, { delay: 100 })
New API

Create a custom hook:

import { useEffect, useState } from 'react'
import { useStore } from 'jotai'
function useAtomValueWithDelay(anAtom, { delay }) {
const store = useStore()
const [value, setValue] = useState(() => store.get(anAtom))
useEffect(() => {
const unsub = store.sub(anAtom, () => {
setTimeout(() => setValue(store.get(anAtom)), delay)
})
return unsub
}, [store, anAtom, delay])
return value
}

New hooks for advanced users

v3 adds useAtomValueRaw and useAtomValueRawSync, which don't resolve promises. They are not required for migration, but they are escape hatches when the default useAtomValue behavior with async atoms doesn't fit your use case.