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

feat(core): unstable_createStore #922

Merged
merged 11 commits into from
Feb 11, 2022
Merged

feat(core): unstable_createStore #922

merged 11 commits into from
Feb 11, 2022

Conversation

dai-shi
Copy link
Member

@dai-shi dai-shi commented Jan 1, 2022

As discussed in #861, this adds unstable_createStore api and prop. This is an experimental feature and may change without notices.

@vercel
Copy link

vercel bot commented Jan 1, 2022

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/pmndrs/jotai/8URY6WAFLLJrn62GAdL94QAdtytL
✅ Preview: https://jotai-git-feat-unstablecreatestore-pmndrs.vercel.app

@codesandbox-ci
Copy link

codesandbox-ci bot commented Jan 1, 2022

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 99b7550:

Sandbox Source
React Configuration
React Typescript Configuration
React Browserify Configuration
React Snowpack Configuration
Next.js Configuration
Next.js with custom Babel config Configuration
React with custom Babel config Configuration

@github-actions
Copy link

github-actions bot commented Jan 1, 2022

Size Change: +721 B (+1%)

Total Size: 98.1 kB

Filename Size Change
dist/esm/index.js 5.48 kB +154 B (+3%)
dist/index.js 6.31 kB +199 B (+3%)
dist/system/index.js 5.77 kB +159 B (+3%)
dist/umd/index.js 6.48 kB +209 B (+3%)
ℹ️ View Unchanged
Filename Size
dist/babel/plugin-debug-label.js 794 B
dist/babel/plugin-react-refresh.js 1.01 kB
dist/babel/preset.js 1.26 kB
dist/devtools.js 3.27 kB
dist/esm/babel/plugin-debug-label.js 622 B
dist/esm/babel/plugin-react-refresh.js 831 B
dist/esm/babel/preset.js 1.08 kB
dist/esm/devtools.js 2.49 kB
dist/esm/immer.js 616 B
dist/esm/optics.js 661 B
dist/esm/query.js 1.25 kB
dist/esm/redux.js 251 B
dist/esm/urql.js 1.49 kB
dist/esm/utils.js 4.73 kB
dist/esm/valtio.js 526 B
dist/esm/xstate.js 1.13 kB
dist/esm/zustand.js 284 B
dist/immer.js 732 B
dist/optics.js 938 B
dist/query.js 1.36 kB
dist/redux.js 314 B
dist/system/babel/plugin-debug-label.js 723 B
dist/system/babel/plugin-react-refresh.js 934 B
dist/system/babel/preset.js 1.18 kB
dist/system/devtools.js 2.68 kB
dist/system/immer.js 739 B
dist/system/optics.js 765 B
dist/system/query.js 1.4 kB
dist/system/redux.js 339 B
dist/system/urql.js 1.64 kB
dist/system/utils.js 5.05 kB
dist/system/valtio.js 639 B
dist/system/xstate.js 1.25 kB
dist/system/zustand.js 372 B
dist/umd/babel/plugin-debug-label.js 932 B
dist/umd/babel/plugin-react-refresh.js 1.15 kB
dist/umd/babel/preset.js 1.4 kB
dist/umd/devtools.js 3.41 kB
dist/umd/immer.js 875 B
dist/umd/optics.js 1.08 kB
dist/umd/query.js 1.5 kB
dist/umd/redux.js 449 B
dist/umd/urql.js 1.69 kB
dist/umd/utils.js 6.11 kB
dist/umd/valtio.js 719 B
dist/umd/xstate.js 1.33 kB
dist/umd/zustand.js 473 B
dist/urql.js 1.54 kB
dist/utils.js 5.93 kB
dist/valtio.js 586 B
dist/xstate.js 1.19 kB
dist/zustand.js 344 B

compressed-size-action

@dai-shi dai-shi marked this pull request as draft January 1, 2022 01:52
Comment on lines 908 to 909
const atomState = store[READ_ATOM](atom)
return 'v' in atomState ? atomState.v : undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an unfortunate erasure if type Value = OtherThing | undefined but the atom can also have a suspense promise. Perhaps the result of get can be a more interesting type like { value: Value } | undefined?

Copy link
Contributor

@justjake justjake Jan 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, what about the error case? Perhaps that should still throw, although I feel pretty strongly that store.get(atom) should never throw the suspense promise.

If I have a const errorAtom = atom(() => throw new Error('oop')), then store.get(errorAtom) should not return undefined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is unfortunate.
I think it should throw on error, and maybe suspense too.
We could have has method, hmm.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does has cause recompute?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: throwing promise: I think outside of React library authors it’s pretty unusual to think about throwing promises, whereas throwing errors is common for most JS styles. Given that the public store intends to expose Jotai for use outside React, I think it might be inappropriate to throw the suspense promise since that’s a kind of react specific, framework internal practice. But I am behind the times in terms of React best practices so maybe this is better known in the cutting edge JS community?

Copy link
Member Author

@dai-shi dai-shi Jan 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the typescript perspective, you can't know if an atom will suspend or not.
Do we want to check type=value, even if we never use async in our code?
So, after all, if we were to explain it to someone who don't know suspense (which is likely), the original return undefined idea would be easier (we throw on error). We would be simply explaining when the value is not ready, it returns undefined. It's like how Map.get is typed (but for a different reason).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this: 4fd5afa

Copy link
Contributor

@justjake justjake Jan 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a second method with the result type, for callers that are working with async and/or want the burden of checking the result for type safety? You had an option overload before, which is also fine too. I do think that returning undefined is better than throwing promise by default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the public interface be expressive enough to allow an alternative implementation of useAtom by consumers, including suspense, if they chose to have those semantics? It’s not important to me, though. Another thought exercise about the purpose of the public store API.

Copy link
Member Author

@dai-shi dai-shi Jan 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's the purpose of the RFC issue, to understand how people would use the public store API.
Exposing everything / having a second method would be all possible, but I don't think it's very good developer experience.

281a617 is reasonable in a sense that unstable_promise is already introduced in getter in atom.write. It's still very unstable though. Hm, it's too tricky for the public store API.

@a-eid
Copy link

a-eid commented Jan 8, 2022

@dai-shi I'm wondering how to patch up my node_modules package with this PR ?

notes after testing this:

  • doesn't seem that I'm able to pass the store to the provider components.
  • store is able to read the atom value just fine.
  • store doesn't seem to be able to set the atom value when the atom is set by the store it does not trigger a re-render.

@justjake
Copy link
Contributor

justjake commented Jan 8, 2022

@a-eid is the component reading the atom via store.get, or by useAtom? Store.get is non-reactive. I’ve been testing a similar change in my Jotai fork with a very similar implementation and it seems to be working fine — the only difference is in how the provider works.

@a-eid
Copy link

a-eid commented Jan 8, 2022

@a-eid is the component reading the atom via store.get, or by useAtom? Store.get is non-reactive. I’ve been testing a similar change in my Jotai fork with a very similar implementation and it seems to be working fine — the only difference is in how the provider works.

useAtom and/oruseAtomValue

  • how to pass the store to the <Provider /> tag ?

I used yarn add https://pkg.csb.dev/pmndrs/jotai/commit/4fd5afaa/jotai to test the PR, not really sure how to patch it up .

@dai-shi
Copy link
Member Author

dai-shi commented Jan 8, 2022

I'm wondering how to patch up my node_modules package with this PR ?

See: #922 (comment)

Basically, find "Local Install Instructions" in: https://ci.codesandbox.io/status/pmndrs/jotai/pr/922

doesn't seem that I'm able to pass the store to the provider components.

I haven't implemented this part yet. Will do shortly.

@a-eid
Copy link

a-eid commented Jan 16, 2022

@dai-shi what happens if you're getting an async storage atom on startup with the store.
would it return a promise or just the default value ?

store.get("ASYNC_ATOM")

@dai-shi
Copy link
Member Author

dai-shi commented Jan 16, 2022

@a-eid That's we had been discussing. Currently, it returns undefined, because outside React, it's not easy to understand Suspense behavior. What's your use case?

@a-eid
Copy link

a-eid commented Jan 16, 2022

@a-eid That's we had been discussing. Currently, it returns undefined, because outside React, it's not easy to understand Suspense behavior. What's your use case?

one use case that we can't get around is when running headlessJS task with react native.

@a-eid
Copy link

a-eid commented Jan 16, 2022

I'm not sure how would onMount behave in case the store.get method is called before the atom is used ?

@dai-shi
Copy link
Member Author

dai-shi commented Jan 16, 2022

one use case that we can't get around is when running headlessJS task with react native.

hm, not sure i get the real issue. we can consider store.asyncGet(atom). would that help?

I'm not sure how would onMount behave in case the store.get method is called before the atom is used ?

good point. we wouldn't be able to finalize the behavior very soon, but store.get is not treated as "used". thus, not mounted and no onMount. #961 is related to make this behavior more consistent.

@a-eid
Copy link

a-eid commented Jan 16, 2022

I think store.asyncGet(atom) would work,

hm, not sure i get the real issue.

currently to access an async atom outside react, we need to get it via asyncStorage, it would be nice to get it via the store.

@dai-shi
Copy link
Member Author

dai-shi commented Jan 16, 2022

currently to access an async atom outside react, we need to get it via asyncStorage, it would be nice to get it via the store.

If you "sub"scribe, you can "get" even the async value which is ready. I'm not sure if asyncGet is really appropriate for your case. Let's add it for experiments.

@a-eid
Copy link

a-eid commented Jan 16, 2022

currently to access an async atom outside react, we need to get it via asyncStorage, it would be nice to get it via the store.

If you "sub"scribe, you can "get" even the async value which is ready. I'm not sure if asyncGet is really appropriate for your case. Let's add it for experiments.

"sub"scribe would not work unless the atom is mounted.

@dai-shi
Copy link
Member Author

dai-shi commented Jan 16, 2022

☝️ asyncGet is here. Please experiment with it.

"sub"scribe would not work unless the atom is mounted.

subscribe actually mounts the atom (and you need to unsubscribe to unmount it).

@dai-shi dai-shi added this to the v1.6.0 milestone Jan 18, 2022
@dai-shi dai-shi linked an issue Jan 24, 2022 that may be closed by this pull request
@dai-shi dai-shi changed the title wip: unstable_createStore feat: unstable_createStore Jan 24, 2022
@dai-shi dai-shi marked this pull request as ready for review January 24, 2022 01:15
@dai-shi dai-shi changed the title feat: unstable_createStore feat(core): unstable_createStore Feb 10, 2022
@dai-shi dai-shi merged commit e5a01f0 into main Feb 11, 2022
@dai-shi dai-shi deleted the feat/unstable_createStore branch February 11, 2022 12:35
@dai-shi dai-shi mentioned this pull request Feb 20, 2022
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.

[RFC] unstable_createStore
3 participants