Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

breaking(utils): predictable atomWithDefault #1939

Merged
merged 2 commits into from
Jun 12, 2023
Merged

breaking(utils): predictable atomWithDefault #1939

merged 2 commits into from
Jun 12, 2023

Conversation

dai-shi
Copy link
Member

@dai-shi dai-shi commented May 17, 2023

Related Issues or Discussions

Fixes #1937

Summary

As it turns out, the conversion of atomWithDefault from jotai v1 to jotai v2 wasn't very ideal. It's unnecessary complex and the behavior isn't very predictable.

This fixes it. However, it comes with breaking change.

Migration guide

// suppose we have this
const asyncAtom = atom(() => Promise.resolve(1))
const countAtom = atomWithDefault((get) => get(asyncAtom))
// and in component
const setCount = useSetAtom(countAtom)

// previously,
setCount((c) => c + 1) // it worked, but it will no longer work

// instead, you need to do this
setCount((countPromise) => countPromise.then((c) => c + 1))

Check List

  • yarn run prettier for formatting code and docs

@vercel
Copy link

vercel bot commented May 17, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
jotai ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 3, 2023 3:11pm

@codesandbox-ci
Copy link

codesandbox-ci bot commented May 17, 2023

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit a206800:

Sandbox Source
React Configuration
React Typescript Configuration
React Browserify Configuration
React Snowpack Configuration
Next.js with custom Babel config Configuration
React with custom Babel config Configuration
With jotai 2 (forked) Issue #1937

@github-actions
Copy link

github-actions bot commented May 17, 2023

Size Change: -147 B (0%)

Total Size: 66.2 kB

Filename Size Change
dist/system/vanilla/utils.development.js 4.41 kB -42 B (-1%)
dist/system/vanilla/utils.production.js 2.7 kB -20 B (-1%)
dist/umd/vanilla/utils.development.js 5.14 kB -39 B (-1%)
dist/umd/vanilla/utils.production.js 3.17 kB -9 B (0%)
dist/vanilla/utils.js 5.01 kB -37 B (-1%)
ℹ️ View Unchanged
Filename Size
dist/babel/plugin-debug-label.js 916 B
dist/babel/plugin-react-refresh.js 1.15 kB
dist/babel/preset.js 1.39 kB
dist/index.js 229 B
dist/react.js 1.05 kB
dist/react/utils.js 1.23 kB
dist/system/babel/plugin-debug-label.development.js 1.09 kB
dist/system/babel/plugin-debug-label.production.js 764 B
dist/system/babel/plugin-react-refresh.development.js 1.29 kB
dist/system/babel/plugin-react-refresh.production.js 935 B
dist/system/babel/preset.development.js 1.57 kB
dist/system/babel/preset.production.js 1.14 kB
dist/system/index.development.js 236 B
dist/system/index.production.js 167 B
dist/system/react.development.js 1.17 kB
dist/system/react.production.js 701 B
dist/system/react/utils.development.js 677 B
dist/system/react/utils.production.js 424 B
dist/system/utils.development.js 241 B
dist/system/utils.production.js 175 B
dist/system/vanilla.development.js 3.56 kB
dist/system/vanilla.production.js 1.91 kB
dist/umd/babel/plugin-debug-label.development.js 1.07 kB
dist/umd/babel/plugin-debug-label.production.js 850 B
dist/umd/babel/plugin-react-refresh.development.js 1.29 kB
dist/umd/babel/plugin-react-refresh.production.js 1.02 kB
dist/umd/babel/preset.development.js 1.53 kB
dist/umd/babel/preset.production.js 1.25 kB
dist/umd/index.development.js 371 B
dist/umd/index.production.js 321 B
dist/umd/react.development.js 1.18 kB
dist/umd/react.production.js 785 B
dist/umd/react/utils.development.js 1.4 kB
dist/umd/react/utils.production.js 1.01 kB
dist/umd/utils.development.js 386 B
dist/umd/utils.production.js 334 B
dist/umd/vanilla.development.js 4.34 kB
dist/umd/vanilla.production.js 2.11 kB
dist/utils.js 236 B
dist/vanilla.js 4.25 kB

compressed-size-action

@dai-shi dai-shi added this to the v2.2.0 milestone Jun 6, 2023
@dai-shi dai-shi merged commit 242eb87 into main Jun 12, 2023
29 checks passed
@dai-shi dai-shi deleted the fix/issue-1937 branch June 12, 2023 10:17
kodiakhq bot pushed a commit to sullivanpj/open-system that referenced this pull request Jun 26, 2023
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [jotai](https://togithub.com/pmndrs/jotai) | [`2.1.0` -> `2.2.1`](https://renovatebot.com/diffs/npm/jotai/2.1.0/2.2.1) | [![age](https://badges.renovateapi.com/packages/npm/jotai/2.2.1/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/npm/jotai/2.2.1/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/npm/jotai/2.2.1/compatibility-slim/2.1.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/npm/jotai/2.2.1/confidence-slim/2.1.0)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>pmndrs/jotai (jotai)</summary>

### [`v2.2.1`](https://togithub.com/pmndrs/jotai/releases/tag/v2.2.1)

[Compare Source](https://togithub.com/pmndrs/jotai/compare/v2.2.0...v2.2.1)

This includes some improvements in `jotai/utils`. Especially, `unstable_unwrap` is getting to be stable.

##### What's Changed

-   feat(utils/useHydrateAtoms) - Optionally rehydrate with force hydrate by [@&#8203;SariSabouh](https://togithub.com/SariSabouh) in [pmndrs/jotai#1990
-   fix(utils): revert atomWithStorage typing by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1994
-   fix(utils): improve selectAtom typing by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1995
-   fix(utils): improve unstable_unwrap for sometimes async atom by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1996
-   fix(utils): unstable_unwrap to support writable atom by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1997

##### New Contributors

-   [@&#8203;SariSabouh](https://togithub.com/SariSabouh) made their first contribution in [pmndrs/jotai#1990

**Full Changelog**: pmndrs/jotai@v2.2.0...v2.2.1

### [`v2.2.0`](https://togithub.com/pmndrs/jotai/releases/tag/v2.2.0)

[Compare Source](https://togithub.com/pmndrs/jotai/compare/v2.1.1...v2.2.0)

It includes a few improvements. Some utils are rewritten as there was a misconception when migrating from v1. ESM builds are optimized for Vite users.

#### Migration Guide for `jotai/utils`

##### `atomWithDefault`

```js
// suppose we have this
const asyncAtom = atom(() => Promise.resolve(1))
const countAtom = atomWithDefault((get) => get(asyncAtom))
// and in component
const setCount = useSetAtom(countAtom)

// previously,
setCount((c) => c + 1) // it worked, but it will no longer work

// instead, you need to do this
setCount((countPromise) => countPromise.then((c) => c + 1))
```

##### `atomWithStorage`

```js
// suppose we have async storage
const storage = createJSONStorage(() => AsyncStorage)
const countAtom = atomWithStorage('count-key', 0, storage)
  // in component
  const [count, setCount] = useAtom(countAom)

  // previously, countAtom is a sync atom, so you could update like this:
  setCount((c) => c + 1)

  // with the new version, it becomes async occasionally, so you need to resolve it:
  setCount(async (c) => (await c) + 1)
```

#### What's Changed

-   breaking(utils): predictable atomWithDefault by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1939
-   breaking(utils): improve atomWithStorage by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1958
-   feat(vanilla): new store listeners for devtools by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1966
-   fix(build): mode env for "import" condition" by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1978

#### New Contributors

-   [@&#8203;reinierkaper-carewell](https://togithub.com/reinierkaper-carewell) made their first contribution in [pmndrs/jotai#1980

**Full Changelog**: pmndrs/jotai@v2.1.1...v2.2.0

### [`v2.1.1`](https://togithub.com/pmndrs/jotai/releases/tag/v2.1.1)

[Compare Source](https://togithub.com/pmndrs/jotai/compare/v2.1.0...v2.1.1)

This version fixes some issues in edge cases.

#### What's Changed

-   fix(vanilla): Stable promise by [@&#8203;backbone87](https://togithub.com/backbone87) in [pmndrs/jotai#1933
-   fix(vanilla): update atoms with tree structure dependencies (regression from v1) by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1959
-   fix: prefer PromiseLike where appropriate by [@&#8203;dai-shi](https://togithub.com/dai-shi) in [pmndrs/jotai#1967

#### New Contributors

-   [@&#8203;blissdev](https://togithub.com/blissdev) made their first contribution in [pmndrs/jotai#1945
-   [@&#8203;hwanyoungChoi](https://togithub.com/hwanyoungChoi) made their first contribution in [pmndrs/jotai#1957
-   [@&#8203;alexhad6](https://togithub.com/alexhad6) made their first contribution in [pmndrs/jotai#1971
-   [@&#8203;backbone87](https://togithub.com/backbone87) made their first contribution in [pmndrs/jotai#1933

**Full Changelog**: pmndrs/jotai@v2.1.0...v2.1.1

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/sullivanpj/open-system).
@rikisamurai
Copy link

I encountered some confusion regarding a breaking change while upgrading from version 2.1.x. Does this imply that if atomWithDefault is initially asynchronous, it will remain asynchronous consistently?

I find the ability of atomWithDefault to transition from asynchronous to synchronous quite impressive. However, the current behavior seems weird:

const asyncAtom = atomWithDefault(async () => {
  await sleep(1000);
  return "async";
});

const setAsyncAtom = atom(null, async (get, set) => {
  await sleep(1000);
  set(asyncAtom, Promise.resolve("update"));
});

here is codesandbox: https://codesandbox.io/p/sandbox/async-atom-and-update-hdp67v?file=%2Fsrc%2FApp.tsx%3A9%2C4

before this change, i could write in this way:

const asyncAtom = atomWithDefault(async () => {
  await sleep(1000);
  return "async";
});

const setAsyncAtom = atom(null, async (get, set) => {
  await sleep(1000);
  set(asyncAtom, "update");
});

Did i do something wrong?

@dai-shi
Copy link
Member Author

dai-shi commented Dec 13, 2023

Does this imply that if atomWithDefault is initially asynchronous, it will remain asynchronous consistently?

Yes, and it's how Jotai v2 is designed for all async atoms, not specific only for atomWithDefault. And, unwrap is a solution to convert an async atom to sync atom.
You can even use unwrap conditionally. https://fosstodon.org/@daishi/111379830300795523

Though, I think you can set a non-promise value.

const anAtom = atomWithDefault<Promise<number> | number>(() => Promise.resolve(1));
  const set = useSetAtom(anAtom);
  set(2);

I haven't looked your codesandbox, but is it a type only issue?

@rikisamurai
Copy link

Does this imply that if atomWithDefault is initially asynchronous, it will remain asynchronous consistently?

Yes, and it's how Jotai v2 is designed for all async atoms, not specific only for atomWithDefault. And, unwrap is a solution to convert an async atom to sync atom. You can even use unwrap conditionally. https://fosstodon.org/@daishi/111379830300795523

Though, I think you can set a non-promise value.

const anAtom = atomWithDefault<Promise<number> | number>(() => Promise.resolve(1));
  const set = useSetAtom(anAtom);
  set(2);

I haven't looked your codesandbox, but is it a type only issue?

Yes, i can write like this set(asyncAtom, "update"); , but it will emit a typescript error. Is this a type issue that needs to be fixed?

@dai-shi
Copy link
Member Author

dai-shi commented Dec 15, 2023

https://tsplay.dev/wjlZlm

image

I don't see any type errors. Please make a reproduction with modifying the typescript playground and open a new discussion.

@rikisamurai
Copy link

https://tsplay.dev/wjlZlm

image I don't see any type errors. Please make a reproduction with modifying the typescript playground and open a new discussion.

Thank you for your response! Now I understand that I can solve this problem using TypeScript generics.
Before this pr:

const asyncAtom = atomWithDefault(async () => {
  await sleep(1000);
  return 'async';
});

const setSyncAtom = atom(null, async (_, set) => {
  await sleep(1000);
  set(asyncAtom, 'update');
});

Now i should add generics for compatiblity

const asyncAtom = atomWithDefault<<Promise<string> | string>>(async () => {
  await sleep(1000);
  return 'async';
});

const setSyncAtom = atom(null, async (_, set) => {
  await sleep(1000);
  set(asyncAtom, 'update');
});

🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Weird behaviours when using atomWithDefault with async atom
2 participants