Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pmndrs/jotai
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.0.2
Choose a base ref
...
head repository: pmndrs/jotai
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.0.3
Choose a head ref
  • 12 commits
  • 20 files changed
  • 6 contributors

Commits on Feb 23, 2023

  1. Feat/doc atomWithDefault another way (#1781)

    * describe another implementation of atomWithDefault in doc
    
    * fix by prettier
    
    * move atomWithRefreshAndDefault section to atom-creators page from resettable page
    
    * chore: run prettier
    t6adev authored Feb 23, 2023
    1

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    95f1aeb View commit details

Commits on Feb 24, 2023

  1. docs: fix broken links (#1789)

    * docs: fix broken links
    
    * docs: update
    himself65 authored Feb 24, 2023
    1
    Copy the full SHA
    1e2a934 View commit details

Commits on Feb 25, 2023

  1. 1
    Copy the full SHA
    688ff22 View commit details
  2. 1
    Copy the full SHA
    d812d36 View commit details

Commits on Feb 28, 2023

  1. 1
    Copy the full SHA
    bd26f1c View commit details

Commits on Mar 2, 2023

  1. 1
    Copy the full SHA
    edbe14d View commit details
  2. feat: add 'debugPrivate' flag to atom (#1779)

    * feat: add 'debugPrivate' flag to atom
    
    * fix(atomWithStorage): mark internal atom in as private
    arjunvegda authored Mar 2, 2023
    1
    Copy the full SHA
    84defd8 View commit details
  3. 1
    Copy the full SHA
    cb54160 View commit details
  4. feat: add dev_subscribe_store (#1790)

    * feat: add dev_subscribe_store
    
    * attach notifiers on public apis
    
    * revert changes
    
    * notify on set
    
    * notify mount and unmount
    
    * fix: update unmount + state + backwards compatibility for dev_subscribe_store
    
    * fix store listeners, and deperecation message for state listener
    
    * recover original dev_subscribe_state
    
    * test: add tests for dev_* methods
    
    * test: add [DEV-ONLY] tag to tests
    
    Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
    
    * chore: adjust sed script for dev/prd only tests
    
    * test: fix test description
    
    * adding tests
    
    * test: add failing test to unmount tree dependencies on unsub
    
    * fix test styles
    
    * do not fire before flush pending
    
    ---------
    
    Co-authored-by: daishi <daishi@axlight.com>
    Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
    3 people authored Mar 2, 2023
    1
    Copy the full SHA
    e80be16 View commit details
  5. 1
    Copy the full SHA
    ad14ff1 View commit details
  6. chore(deps): update dev dependencies (#1807)

    * chore(deps): update dev dependencies
    
    * downgrade react experimental build
    
    * downgrade react experimental build 2
    
    * downgrade react experimental build 3
    
    * downgrade react experimental build 4
    
    * downgrade react experimental build 5
    
    * downgrade react experimental build 6
    
    * downgrade react experimental build 7
    
    * revert testing-library/react
    
    * upgrade react experimental builds
    dai-shi authored Mar 2, 2023
    1
    Copy the full SHA
    82a6848 View commit details
  7. 2.0.3

    dai-shi committed Mar 2, 2023
    1
    Copy the full SHA
    1e0de6c View commit details
8 changes: 4 additions & 4 deletions .github/workflows/test-multiple-builds.yml
Original file line number Diff line number Diff line change
@@ -30,13 +30,13 @@ jobs:
- name: Patch for DEV-ONLY
if: ${{ matrix.env == 'development' }}
run: |
sed -i~ "s/it[.a-zA-Z]*('\[DEV-ONLY\]/it('/" tests/*/*.tsx tests/*/*/*.tsx
sed -i~ "s/it[.a-zA-Z]*('\[PRD-ONLY\]/it.skip('/" tests/*/*.tsx tests/*/*/*.tsx
sed -i~ "s/\(it\|describe\)[.a-zA-Z]*('\[DEV-ONLY\]/\1('/" tests/*/*.tsx tests/*/*/*.tsx
sed -i~ "s/\(it\|describe\)[.a-zA-Z]*('\[PRD-ONLY\]/\1.skip('/" tests/*/*.tsx tests/*/*/*.tsx
- name: Patch for PRD-ONLY
if: ${{ matrix.env == 'production' }}
run: |
sed -i~ "s/it[.a-zA-Z]*('\[PRD-ONLY\]/it('/" tests/*/*.tsx tests/*/*/*.tsx
sed -i~ "s/it[.a-zA-Z]*('\[DEV-ONLY\]/it.skip('/" tests/*/*.tsx tests/*/*/*.tsx
sed -i~ "s/\(it\|describe\)[.a-zA-Z]*('\PRD-ONLY\]/\1('/" tests/*/*.tsx tests/*/*/*.tsx
sed -i~ "s/\(it\|describe\)[.a-zA-Z]*('\[DEV-ONLY\]/\1.skip('/" tests/*/*.tsx tests/*/*/*.tsx
- name: Patch for CJS
if: ${{ matrix.build == 'cjs' }}
run: |
4 changes: 2 additions & 2 deletions .github/workflows/test-multiple-versions.yml
Original file line number Diff line number Diff line change
@@ -33,8 +33,8 @@ jobs:
- 18.0.0
- 18.1.0
- 18.2.0
- 18.3.0-next-fccf3a9fb-20230213
- 0.0.0-experimental-fccf3a9fb-20230213
- 18.3.0-next-41110021f-20230301
- 0.0.0-experimental-41110021f-20230301
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
5 changes: 4 additions & 1 deletion docs/core/atom.mdx
Original file line number Diff line number Diff line change
@@ -58,9 +58,12 @@ Furthermore, it can't read unresolved async values in Jotai v1 API.
`set` in the write function is to write atom value.
It will invoke the write function of the target atom.

**Note**: Atom configs can be created anywhere, but referential equality is important.
### Note about creating an atom in render function

Atom configs can be created anywhere, but referential equality is important.
They can be created dynamically too.
To create an atom in render function, `useMemo` or `useRef` is required to get a stable reference. If in doubt about using `useMemo` or `useRef` for memoization, use `useMemo`.
Otherwise, it can cause infinite loop with `useAtom`.

```js
const Component = ({ value }) => {
3 changes: 2 additions & 1 deletion docs/core/store.mdx
Original file line number Diff line number Diff line change
@@ -18,9 +18,10 @@ const myStore = createStore()

const countAtom = atom(0)
myStore.set(countAtom, 1)
myStore.sub(countAtom, () => {
const unsub = myStore.sub(countAtom, () => {
console.log('countAtom value is changed to', myStore.get(countAtom))
})
// unsub() to unsubscribe

const Root = () => (
<Provider store={myStore}>
2 changes: 1 addition & 1 deletion docs/guides/debugging.mdx
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ if (process.env.NODE_ENV !== 'production') {
}
```

Jotai provides both a Babel and a SWC plugin, that adds a debugLabel automatically to every atom, which makes things easier for us. For more info, check out [jotai/babel](https://github.com/pmndrs/jotai/blob/main/docs/api/babel.mdx#plugin-debug-label) and [@swc-jotai/debug-label](https://github.com/pmndrs/jotai/blob/main/docs/api/swc.mdx)
Jotai provides both a Babel and a SWC plugin, that adds a debugLabel automatically to every atom, which makes things easier for us. For more info, check out [jotai/babel](https://github.com/pmndrs/jotai/blob/main/docs/tools/babel.mdx#plugin-debug-label) and [@swc-jotai/debug-label](https://github.com/pmndrs/jotai/blob/main/docs/tools/swc.mdx)

## Using React Dev Tools

4 changes: 3 additions & 1 deletion docs/guides/migrating-to-v2-api.mdx
Original file line number Diff line number Diff line change
@@ -209,6 +209,8 @@ const allAtom = waitForAll([fooAtom, barAtom])
const allAtom = atom((get) => Promise.all([get(fooAtom), get(barAtom)]))
```
Note that creating an atom in render function can cause [infinite loop](../api/atom.mdx#note-about-creating-an-atom-in-render-function)
### `splitAtom` util (or some other utils) with async atoms
`splitAtom` util only accepts sync atoms.
@@ -242,7 +244,7 @@ For more information, refer the following discussions:
### Utils
- `atomWithStorage` util's `delayInit` is removed as being default.
- `atomWithStorage` util's `delayInit` is removed as being default. Also it will always render `initialValue` on first render, and the stored value, if any, on subsequent renders. The new behavior differs from v1. See https://github.com/pmndrs/jotai/discussions/1737 for more information.
- `useHydrateAtoms` can only accept writable atoms.
### Import statements
72 changes: 72 additions & 0 deletions docs/recipes/atom-creators.mdx
Original file line number Diff line number Diff line change
@@ -216,6 +216,78 @@ const PostsList = () => {
}
```

## atomWithRefreshAndDefault

> This is for an another implementation of [atomWithDefault](../utilities/resettable.mdx#atomwithdefault)
### Look back to atomWithDefault behavior

As you can see in the example code in atomWithDefault section, the two atoms' relation is disconnected after updating created one, `count2Atom = atomWithDefault((get) => get(count1Atom) * 2)`.
Let's confirm what's occurred,

- 1. Click "increment count1", then count1 is 2 and count2 is 4
- 2. Click "increment count2", then count1 is 2 and count2 is 5 (Disconnected!!)

Those atoms have no relation after updating count2Atom. So,

- Click "increment count1", count1 is incremented only
- Even if you reset count2Atom, these dependency relation never come back

### Motivation

In some cases,

- After disconnecting and resetting, they should come back to their relation
- Derived atoms should be reset based on updated the original atom
- We'd like to reset all derived atoms but just want to operate as simply as possible

How do we make those cases?
Here is a declarative way to create a function to provide a refreshable atom instead of atomWithDefault.

```js
const refreshCountAtom = atom(0)

const baseDataAtom = atom(1) // original data, e.g. base count1Atom
const dataAtom = atom(
(get) => {
get(refreshCountAtom) // it's introduced at atomWithRefresh
return get(baseDataAtom)
},
(get, set, update) => {
set(baseDataAtom, update)
}
)

const atomWithRefreshAndDefault = (refreshAtom, getDefault) => {
const overwrittenAtom = atom(null)
return atom(
(get) => {
const lastState = get(overwrittenAtom)
if (lastState && lastState.refresh === get(refreshAtom)) {
return lastState.value
}
return getDefault(get)
},
(get, set, update) => {
set(overwrittenAtom, { refresh: get(refreshAtom), value: update })
}
)
}

// This is an alternative of `atomWithDefault((get) => get(count1Atom) * 2)`
const refreshableAtom = atomWithRefreshAndDefault(
refreshCountAtom,
(get) => get(dataAtom) * 2
)

// You can reset by updating just one atom
const resetRootAtom = atom(null, (get, set) => {
set(refreshCountAtom, get(refreshCountAtom) + 1)
})
```

<CodeSandbox id="tcx2mk" />

## atomWithListeners

> `atomWithListeners` creates an atom and a hook. The hook can be called to
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jotai",
"private": true,
"version": "2.0.2",
"version": "2.0.3",
"description": "👻 Primitive and flexible state management for React",
"main": "./index.js",
"types": "./index.d.ts",
@@ -152,12 +152,12 @@
]
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/plugin-transform-react-jsx": "^7.20.13",
"@babel/plugin-transform-typescript": "^7.20.13",
"@babel/core": "^7.21.0",
"@babel/plugin-transform-react-jsx": "^7.21.0",
"@babel/plugin-transform-typescript": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/template": "^7.20.7",
"@babel/types": "^7.20.7",
"@babel/types": "^7.21.2",
"@redux-devtools/extension": "^3.2.5",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^15.0.1",
@@ -168,13 +168,13 @@
"@testing-library/user-event": "^14.4.3",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"benny": "^3.7.1",
"concurrently": "^7.6.0",
"downlevel-dts": "^0.11.0",
"esbuild": "^0.17.8",
"eslint": "^8.34.0",
"esbuild": "^0.17.10",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.27.5",
@@ -189,7 +189,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"redux": "^4.2.1",
"rollup": "^3.15.0",
"rollup": "^3.18.0",
"rollup-plugin-esbuild": "^5.0.0",
"rxjs": "^7.8.0",
"shx": "^0.3.4",
5 changes: 4 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
@@ -60,7 +60,10 @@ function createESMConfig(input, output) {
? {
'import.meta.env?.MODE': 'process.env.NODE_ENV',
}
: {}),
: {
'import.meta.env?.MODE':
'(import.meta.env && import.meta.env.MODE)',
}),
delimiters: ['\\b', '\\b(?!(\\.|/))'],
preventAssignment: true,
}),
7 changes: 6 additions & 1 deletion src/vanilla/atom.ts
Original file line number Diff line number Diff line change
@@ -34,8 +34,13 @@ type OnMount<Args extends unknown[], Result> = <

export interface Atom<Value> {
toString: () => string
debugLabel?: string
read: Read<Value>
debugLabel?: string
/**
* To ONLY be used by Jotai libraries to mark atoms as private. Subject to change.
* @private
*/
debugPrivate?: boolean
}

export interface WritableAtom<Value, Args extends unknown[], Result>
22 changes: 21 additions & 1 deletion src/vanilla/store.ts
Original file line number Diff line number Diff line change
@@ -109,6 +109,7 @@ type Mounted = {

// for debugging purpose only
type StateListener = () => void
type StoreListener = (type: 'state' | 'sub' | 'unsub') => void
type MountedAtoms = Set<AnyAtom>

/**
@@ -135,9 +136,11 @@ export const createStore = () => {
AtomState /* prevAtomState */ | undefined
>()
let stateListeners: Set<StateListener>
let storeListeners: Set<StoreListener>
let mountedAtoms: MountedAtoms
if (import.meta.env?.MODE !== 'production') {
stateListeners = new Set()
storeListeners = new Set()
mountedAtoms = new Set()
}

@@ -582,6 +585,7 @@ export const createStore = () => {
}
if (import.meta.env?.MODE !== 'production') {
stateListeners.forEach((l) => l())
storeListeners.forEach((l) => l('state'))
}
}

@@ -590,9 +594,16 @@ export const createStore = () => {
flushPending()
const listeners = mounted.l
listeners.add(listener)
if (import.meta.env?.MODE !== 'production') {
storeListeners.forEach((l) => l('sub'))
}
return () => {
listeners.delete(listener)
delAtom(atom)
if (import.meta.env?.MODE !== 'production') {
// devtools uses this to detect if it _can_ unmount or not
storeListeners.forEach((l) => l('unsub'))
}
}
}

@@ -601,13 +612,22 @@ export const createStore = () => {
get: readAtom,
set: writeAtom,
sub: subscribeAtom,
// store dev methods (these are tentative and subject to change)
// store dev methods (these are tentative and subject to change without notice)
dev_subscribe_state: (l: StateListener) => {
console.warn(
'[DEPRECATED] dev_subscribe_state is deprecated and will be removed in the next minor version. use dev_subscribe_store instead.'
)
stateListeners.add(l)
return () => {
stateListeners.delete(l)
}
},
dev_subscribe_store: (l: StoreListener) => {
storeListeners.add(l)
return () => {
storeListeners.delete(l)
}
},
dev_get_mounted_atoms: () => mountedAtoms.values(),
dev_get_atom_state: (a: AnyAtom) => atomStateMap.get(a),
dev_get_mounted: (a: AnyAtom) => mountedMap.get(a),
5 changes: 5 additions & 0 deletions src/vanilla/utils/atomWithDefault.ts
Original file line number Diff line number Diff line change
@@ -37,6 +37,11 @@ export function atomWithDefault<Value>(
) {
const EMPTY = Symbol()
const overwrittenAtom = atom<Value | typeof EMPTY>(EMPTY)

if (import.meta.env?.MODE !== 'production') {
overwrittenAtom.debugPrivate = true
}

const anAtom: WritableAtom<
Value,
[SetStateAction<Awaited<Value>> | typeof RESET],
9 changes: 9 additions & 0 deletions src/vanilla/utils/atomWithObservable.ts
Original file line number Diff line number Diff line change
@@ -135,6 +135,11 @@ export function atomWithObservable<Data>(
start()

const resultAtom = atom(lastResult || initialResult)

if (import.meta.env?.MODE !== 'production') {
resultAtom.debugPrivate = true
}

resultAtom.onMount = (update) => {
setResult = update
if (lastResult) {
@@ -156,6 +161,10 @@ export function atomWithObservable<Data>(
return [resultAtom, observable, makePending, start, isNotMounted] as const
})

if (import.meta.env?.MODE !== 'production') {
observableResultAtom.debugPrivate = true
}

const observableAtom = atom(
(get) => {
const [resultAtom] = get(observableResultAtom)
4 changes: 4 additions & 0 deletions src/vanilla/utils/atomWithStorage.ts
Original file line number Diff line number Diff line change
@@ -108,6 +108,10 @@ export function atomWithStorage<Value>(
): WritableAtom<Value, [SetStateActionWithReset<Value>], void> {
const baseAtom = atom(initialValue)

if (import.meta.env?.MODE !== 'production') {
baseAtom.debugPrivate = true
}

baseAtom.onMount = (setAtom) => {
const value = storage.getItem(key)
if (value instanceof Promise) {
12 changes: 11 additions & 1 deletion src/vanilla/utils/loadable.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ const cache1 = new WeakMap()
const memo1 = <T>(create: () => T, dep1: object): T =>
(cache1.has(dep1) ? cache1 : cache1.set(dep1, create())).get(dep1)

type Loadable<Value> =
export type Loadable<Value> =
| { state: 'loading' }
| { state: 'hasError'; error: unknown }
| { state: 'hasData'; data: Awaited<Value> }
@@ -16,6 +16,11 @@ export function loadable<Value>(anAtom: Atom<Value>): Atom<Loadable<Value>> {
return memo1(() => {
const loadableCache = new WeakMap<Promise<void>, Loadable<Value>>()
const refreshAtom = atom(0)

if (import.meta.env?.MODE !== 'production') {
refreshAtom.debugPrivate = true
}

const derivedAtom = atom(
(get, { setSelf }) => {
get(refreshAtom)
@@ -44,6 +49,11 @@ export function loadable<Value>(anAtom: Atom<Value>): Atom<Loadable<Value>> {
set(refreshAtom, (c) => c + 1)
}
)

if (import.meta.env?.MODE !== 'production') {
derivedAtom.debugPrivate = true
}

return atom((get) => get(derivedAtom))
}, anAtom)
}
Loading