1 | <br>
|
2 |
|
3 | ![Jotai (light mode)](./img/jotai-header-light.png#gh-light-mode-only)
|
4 |
|
5 |
|
6 | <br>
|
7 |
|
8 | visit [jotai.org](https://jotai.org) or `npm i jotai`
|
9 |
|
10 | [![Build Status](https://img.shields.io/github/actions/workflow/status/pmndrs/jotai/lint-and-type.yml?branch=main&style=flat&colorA=000000&colorB=000000)](https://github.com/pmndrs/jotai/actions?query=workflow%3ALint)
|
11 | [![Build Size](https://img.shields.io/bundlephobia/minzip/jotai?label=bundle%20size&style=flat&colorA=000000&colorB=000000)](https://bundlephobia.com/result?p=jotai)
|
12 | [![Version](https://img.shields.io/npm/v/jotai?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/jotai)
|
13 | [![Downloads](https://img.shields.io/npm/dt/jotai.svg?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/jotai)
|
14 | [![Discord Shield](https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=ffffff)](https://discord.gg/poimandres)
|
15 | [![Open Collective](https://img.shields.io/opencollective/all/jotai?style=flat&colorA=000000&colorB=000000)](https://opencollective.com/jotai)
|
16 |
|
17 | Jotai scales from a simple useState replacement to an enterprise TypeScript application.
|
18 |
|
19 | - Minimal core API (2kb)
|
20 | - Many utilities and integrations
|
21 | - No string keys (compared to Recoil)
|
22 |
|
23 | Examples: [Demo 1](https://codesandbox.io/s/jotai-demo-47wvh) |
|
24 | [Demo 2](https://codesandbox.io/s/jotai-demo-forked-x2g5d)
|
25 |
|
26 | ### First, create a primitive atom
|
27 |
|
28 | An atom represents a piece of state. All you need is to specify an initial
|
29 | value, which can be primitive values like strings and numbers, objects, and
|
30 | arrays. You can create as many primitive atoms as you want.
|
31 |
|
32 | ```jsx
|
33 | import { atom } from 'jotai'
|
34 |
|
35 | const countAtom = atom(0)
|
36 | const countryAtom = atom('Japan')
|
37 | const citiesAtom = atom(['Tokyo', 'Kyoto', 'Osaka'])
|
38 | const mangaAtom = atom({ 'Dragon Ball': 1984, 'One Piece': 1997, Naruto: 1999 })
|
39 | ```
|
40 |
|
41 | ### Use the atom in your components
|
42 |
|
43 | It can be used like `React.useState`:
|
44 |
|
45 | ```jsx
|
46 | import { useAtom } from 'jotai'
|
47 |
|
48 | function Counter() {
|
49 | const [count, setCount] = useAtom(countAtom)
|
50 | return (
|
51 | <h1>
|
52 | {count}
|
53 | <button onClick={() => setCount((c) => c + 1)}>one up</button>
|
54 | ...
|
55 | ```
|
56 |
|
57 | ### Create derived atoms with computed values
|
58 |
|
59 | A new read-only atom can be created from existing atoms by passing a read
|
60 | function as the first argument. `get` allows you to fetch the contextual value
|
61 | of any atom.
|
62 |
|
63 | ```jsx
|
64 | const doubledCountAtom = atom((get) => get(countAtom) * 2)
|
65 |
|
66 | function DoubleCounter() {
|
67 | const [doubledCount] = useAtom(doubledCountAtom)
|
68 | return <h2>{doubledCount}</h2>
|
69 | }
|
70 | ```
|
71 |
|
72 | ### Creating an atom from multiple atoms
|
73 |
|
74 | You can combine multiple atoms to create a derived atom.
|
75 |
|
76 | ```jsx
|
77 | const count1 = atom(1)
|
78 | const count2 = atom(2)
|
79 | const count3 = atom(3)
|
80 |
|
81 | const sum = atom((get) => get(count1) + get(count2) + get(count3))
|
82 | ```
|
83 |
|
84 | Or if you like fp patterns ...
|
85 |
|
86 | ```jsx
|
87 | const atoms = [count1, count2, count3, ...otherAtoms]
|
88 | const sum = atom((get) => atoms.map(get).reduce((acc, count) => acc + count))
|
89 | ```
|
90 |
|
91 | ### Derived async atoms <img src="https://img.shields.io/badge/-needs_suspense-black" alt="needs suspense" />
|
92 |
|
93 | You can make the read function an async function too.
|
94 |
|
95 | ```jsx
|
96 | const urlAtom = atom('https://json.host.com')
|
97 | const fetchUrlAtom = atom(async (get) => {
|
98 | const response = await fetch(get(urlAtom))
|
99 | return await response.json()
|
100 | })
|
101 |
|
102 | function Status() {
|
103 | // Re-renders the component after urlAtom is changed and the async function above concludes
|
104 | const [json] = useAtom(fetchUrlAtom)
|
105 | ...
|
106 | ```
|
107 |
|
108 | ### You can create a writable derived atom
|
109 |
|
110 | Specify a write function at the second argument. `get` will return the current
|
111 | value of an atom. `set` will update the value of an atom.
|
112 |
|
113 | ```jsx
|
114 | const decrementCountAtom = atom(
|
115 | (get) => get(countAtom),
|
116 | (get, set, _arg) => set(countAtom, get(countAtom) - 1)
|
117 | )
|
118 |
|
119 | function Counter() {
|
120 | const [count, decrement] = useAtom(decrementCountAtom)
|
121 | return (
|
122 | <h1>
|
123 | {count}
|
124 | <button onClick={decrement}>Decrease</button>
|
125 | ...
|
126 | ```
|
127 |
|
128 | ### Write only derived atoms
|
129 |
|
130 | Just do not define a read function.
|
131 |
|
132 | ```jsx
|
133 | const multiplyCountAtom = atom(null, (get, set, by) =>
|
134 | set(countAtom, get(countAtom) * by)
|
135 | )
|
136 |
|
137 | function Controls() {
|
138 | const [, multiply] = useAtom(multiplyCountAtom)
|
139 | return <button onClick={() => multiply(3)}>triple</button>
|
140 | }
|
141 | ```
|
142 |
|
143 | ### Async actions
|
144 |
|
145 | Just make the write function an async function and call `set` when you're ready.
|
146 |
|
147 | ```jsx
|
148 | const fetchCountAtom = atom(
|
149 | (get) => get(countAtom),
|
150 | async (_get, set, url) => {
|
151 | const response = await fetch(url)
|
152 | set(countAtom, (await response.json()).count)
|
153 | }
|
154 | )
|
155 |
|
156 | function Controls() {
|
157 | const [count, compute] = useAtom(fetchCountAtom)
|
158 | return (
|
159 | <button onClick={() => compute('http://count.host.com')}>compute</button>
|
160 | ...
|
161 | ```
|
162 |
|
163 | ## Links
|
164 |
|
165 | - [website](https://jotai.org)
|
166 | - [documentation](https://jotai.org/docs)
|
167 | - [course](https://egghead.io/courses/manage-application-state-with-jotai-atoms-2c3a29f0)
|