JotaiJotai

状態
Primitive and flexible state management for React
Atomikku, the Jotai mascot
v2
Welcome to Jotai v2!
Fully compatible with React 18 and the upcoming use hook. Now with a store interface that can be used outside of React.
Enjoy the new “Getting started” experience below!
Introduction

Jotai takes an atomic approach to global React state management.

Build state by combining atoms and renders are automatically optimized based on atom dependency. This solves the extra re-render issue of React context, eliminates the need for memoization, and provides a similar developer experience to signals while maintaining a declarative programming model.

It scales from a simple useState replacement to an enterprise TypeScript application with complex requirements. Plus there are plenty of utilities and extensions to help you along the way!

Jotai is trusted in production at innovative companies like these.

candycode alternative graphic design web development agency
Adobe
Ping Labs
TokTok
Uniswap
Getting started

This walks you through the process of creating a simple Jotai application. It starts with installation, then explores the basics of the core API, and ends with server-side rendering in a React framework.

Installation

First add Jotai as a dependency to your React project.

# npm
npm i jotai
# yarn
yarn add jotai
# pnpm
pnpm add jotai

Create atoms

First create primitive and derived atoms to build state.

Primitive atoms

A primitive atom can be any type: booleans, numbers, strings, objects, arrays, sets, maps, and so on.

import { atom } from 'jotai'
const countAtom = atom(0)
const countryAtom = atom('Japan')
const citiesAtom = atom(['Tokyo', 'Kyoto', 'Osaka'])
const animeAtom = atom([
{
title: 'Ghost in the Shell',
year: 1995,
watched: true
},
{
title: 'Serial Experiments Lain',
year: 1998,
watched: false
}
])

Derived atoms

A derived atom can read from other atoms before returning its own value.

const progressAtom = atom((get) => {
const anime = get(animeAtom)
return anime.filter((item) => item.watched).length / anime.length
})

Use atoms

Then use atoms within React components to read or write state.

Read and write from same component

When atoms are both read and written within the same component, use the combined useAtom hook for simplicity.

import { useAtom } from 'jotai'
const AnimeApp = () => {
const [anime, setAnime] = useAtom(animeAtom)
return (
<>
<ul>
{anime.map((item) => (
<li key={item.title}>{item.title}</li>
))}
</ul>
<button onClick={() => {
setAnime((anime) => [
...anime,
{
title: 'Cowboy Bebop',
year: 1998,
watched: false
}
])
}}>
Add Cowboy Bebop
</button>
<>
)
}

Read and write from separate components

When atom values are only read or written, use the separate useAtomValue and useSetAtom hooks to optimize re-renders.

import { useAtomValue, useSetAtom } from 'jotai'
const AnimeList = () => {
const anime = useAtomValue(animeAtom)
return (
<ul>
{anime.map((item) => (
<li key={item.title}>{item.title}</li>
))}
</ul>
)
}
const AddAnime = () => {
const setAnime = useSetAtom(animeAtom)
return (
<button onClick={() => {
setAnime((anime) => [
...anime,
{
title: 'Cowboy Bebop',
year: 1998,
watched: false
}
])
}}>
Add Cowboy Bebop
</button>
)
}
const ProgressTracker = () => {
const progress = useAtomValue(progressAtom)
return (
<div>{Math.trunc(progress * 100)}% watched</div>
)
}
const AnimeApp = () => {
return (
<>
<AnimeList />
<AddAnime />
<ProgressTracker />
</>
)
}

Server-side rendering

If server-side rendering with a framework such as Next.js or Waku, make sure to add a Jotai Provider component at the root.

Next.js (app directory)

Create the provider in a separate client component. Then import the provider into the root layout.js server component.

// ./components/providers.js
'use client'
import { Provider } from 'jotai'
export const Providers = ({ children }) => {
return (
<Provider>
{children}
</Provider>
)
}
// ./app/layout.js
import { Providers } from '../components/providers'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>
{children}
</Providers>
</body>
</html>
)
}

Next.js (pages directory)

Create the provider in _app.js.

// ./pages/_app.js
import { Provider } from 'jotai'
export default function App({ Component, pageProps }) {
return (
<Provider>
<Component {...pageProps} />
</Provider>
)
}

Waku

Create the provider in a separate client component. Then import the provider into the root layout.

// ./src/components/providers.js
'use client'
import { Provider } from 'jotai'
export const Providers = ({ children }) => {
return (
<Provider>
{children}
</Provider>
)
}
// ./src/pages/_layout.js
import { Providers } from '../components/providers'
export default async function RootLayout({ children }) {
return (
<Providers>
{children}
</Providers>
)
}
API overview

Core

Jotai has a very minimal API and is TypeScript oriented. It is as simple to use as React’s integrated useState hook, but all state is globally accessible, derived state is easy to implement, and unnecessary re-renders are automatically eliminated.

HELLO
import { atom, useAtom } from 'jotai'
// Create your atoms and derivatives
const textAtom = atom('hello')
const uppercaseAtom = atom(
(get) => get(textAtom).toUpperCase()
)
// Use them anywhere in your app
const Input = () => {
const [text, setText] = useAtom(textAtom)
const handleChange = (e) => setText(e.target.value)
return (
<input value={text} onChange={handleChange} />
)
}
const Uppercase = () => {
const [uppercase] = useAtom(uppercaseAtom)
return (
<div>Uppercase: {uppercase}</div>
)
}
// Now you have the components
const App = () => {
return (
<>
<Input />
<Uppercase />
</>
)
}

Utilities

The Jotai package also includes a jotai/utils bundle. These extra functions add support for persisting an atom in localStorage, hydrating an atom during server-side rendering, creating atoms with Redux-like reducers and action types, and much more.

import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
// Set the string key and the initial value
const darkModeAtom = atomWithStorage('darkMode', false)
const Page = () => {
// Consume persisted state like any other atom
const [darkMode, setDarkMode] = useAtom(darkModeAtom)
const toggleDarkMode = () => setDarkMode(!darkMode)
return (
<>
<h1>Welcome to {darkMode ? 'dark' : 'light'} mode!</h1>
<button onClick={toggleDarkMode}>toggle theme</button>
</>
)
}

Extensions

There are also separate packages for each official extension: tRPC, Immer, Query, XState, URQL, Optics, Relay, location, molecules, cache, and more.

Some extensions provide new atom types with alternate write functions such as atomWithImmer (Immer) or atomWithMachine (XState).

Others provide new atom types with two-way data binding such as atomWithLocation or atomWithHash.

0
import { useAtom } from 'jotai'
import { atomWithImmer } from 'jotai-immer'
// Create a new atom with an immer-based write function
const countAtom = atomWithImmer(0)
const Counter = () => {
const [count] = useAtom(countAtom)
return (
<div>count: {count}</div>
)
}
const Controls = () => {
// setCount === update: (draft: Draft<Value>) => void
const [, setCount] = useAtom(countAtom)
const increment = () => setCount((c) => (c = c + 1))
return (
<button onClick={increment}>+1</button>
)
}
Learn more

Check out the free Egghead course by Daishi, the creator of Jotai.

Jotai course