-
-
Notifications
You must be signed in to change notification settings - Fork 617
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
[RFC] unstable_createStore #861
Comments
would it also be possible to set an atom value from outside react ? |
Yeah, that's the plan. Updated description. |
@dai-shi that would be amazing. |
This would be great! It would let us use jotai with lit, haunted, and stencil via https://github.com/saasquatch/universal-hooks |
Is it possible to release an alpha version now? |
Please give us more time. It's still RFC. It's actually helpful if you can note why you need it. (And, maybe we can discuss current possible workaround.) |
Sorry, didn't mean to rush, just hope to use this feature soon. |
So, your comment is valuable, because this doesn't allow transient updates. |
Oh, it's a lucky thing I replied. I usually just read and don't reply because my English is not good. Related Codes: const offsetRef = useRef(useStore.getState().offsets[index]);
useEffect(
() =>
useStore.subscribe(
(state) => state.offsets[index],
(offset: Offset) => {
offsetRef.current = offset;
animation.animateTo({
transform: [{ translateX: offset.x }, { translateY: offset.y }],
});
},
),
[],
); |
jotai doesn't have a proper way for transient updates. Lines 15 to 16 in 685c8dc
but it's not something to recommend, and hope to revisit this in the future. I guess we should keep the mutable store outside of jotai, and jotai atom only keeps an identifier to it. Like, atom/useAtom is for useState, and for useRef, just module variable. |
I found that zustand/vanilla can be embedded nicely in jotai. And its code is very clear. Jotai's code is a bit larger. I don't know what's preventing it from implementing transient updates yet. |
zustand and jotai are based on different models. (otherwise, we wouldn't develop jotai.) |
@dai-shi I would like Here is my idea. Feel free to browse but please be aware that the code in this repo is under AGPLv3. You can think of this like building a Mobx-like system on top of Jotai, if Mobx used immutable writes. |
Interesting. I haven't thought about long-term migration and this api to help such use cases. Does the proposed api look okay? Do you have any question about the behavior? |
@dai-shi when/how does the createStore prop get called by Provider? I assume this is called to create new scopes - so does returning a static Store here disable the scope feature? |
Scope isn’t important to my use-case but it could affect 3rd-party libraries that happen to use Jotai internally and use scopes for isolation or something, right? |
I think I also need subscription capability like |
createStore will be called on the initial mount. It's the same how it works internally now. (I'm not super confident if this is intuitive for developers, which is one reason why it's
Hm, maybe not.
It's fair! Just added in the description. |
Should that read |
Good point. I was thinking about not notifying if invalidated ( |
If you want to move forward to a prototyping stage I think you could just make the existing createStore and the constants to access its methods “public” as a const like |
I thought about it once but my current plan is to provide a wrapped store with intuitively named methods. |
Another topic to discuss is how internals will interact with the newly public createStore result. We should sketch the public type of this value - will it have all the existing internal methods? Will it have a private symbol that must point to the internals, so that users can’t produce their own implementation? Or will the internals start to use the public methods directly, and consumers can create their own original implementations as long as they implement the interface? |
Here you go #922. Yeah, I have been thinking about making it a symbol for internal store or not. |
I agree, that seems to be the safest direction for a v1 of this API -- at this point it would over-constrain Jotai's evolution to make it possible for consumers to implement their own store types. |
|
Question @dai-shi my understanding was that if we want state management outside of React to use Valtio. Now that this functionality is coming to Jotai where does that leave Valtio? I feel like we're more than happy to have state outside React being a second class citizen, as long as we have some option of state outside React. This makes choosing between Valtio and Jotai very challenging and might require updating the decision matrix of why choose one framework over the other |
Great point. |
Is there anywhere I can read up on the mutation and render distinction? I thought Jotai was render optimised |
https://blog.axlight.com/posts/how-valtio-proxy-state-works-vanilla-part/ Yeah, both are render optimized. sorry for the confusion. jotai optimizes with atoms, valtio optimizes based on usage (property access). |
Is this referring to the different granularity of control for rendering optimization? For example, an object |
@njzydark Yes, exactly. |
Thanks for providing this API! Just a follow-up question: is it true that writing an atom via this store api wont trigger a component re-render? For example: import { atom, Provider, unstable_createStore } from 'jotai'
const countAtom = atom(0)
const store = unstable_createStore()
const App = () => (
<Provider unstable_createStore={() => store}>
<ComponentA />
</Provider>
)
// read atom value outside React
const value = store.get(countAtom)
// Now say I have a component
```ts
function ComponentA() {
const count = useAtomValue(countAtom);
return <div>Hello, {count}</div>
} And then outside of React (for example, in Chrome devtool console), if I update the atom like store.set(countAtom, 10); will componentA be re-rendered in this case? Many thanks! |
It will re-render even if the atom value is updated from outside React. |
The example code at the top of this issue doesn't make any sense to me: const countAtom = atom(0)
const store = unstable_createStore()
// read atom value outside React
const value = store.get(countAtom)
// write atom value outside React
store.set(countAtom, 1) How is the store getting / setting values if Or is |
Like, if the code was written like this, it would make more sense: const store = unstable_createStore({
count: 0
})
// read atom value outside React
const value = store.get('count')
// or without store, because why do we need store?
const countAtom = atom(0)
const value = countAtom.get() As it is right now, it doesn't make any sense what "store" is doing, because it seems like an indirect accessor / setter of an atom's value. Is it a manager? What is it? |
What you expect from "store" is different. |
Okay. Would it make sense to call it something other than "store"? Because then it isn't a store since it doesn't contain references to values. |
No, we should still call it "store". It contains values. store is mutable, and store holds (often) immutable states. redux storeredux state is a (big) object. const store = createStore(...)
store.getState() // returns a state object
store.dispatch() // to update the state object immutably zustand storezustand state is a (big) object too. const store = createStore(...)
store.getState() // returns a state object
store.setState(...) // to update the state object immutably (by convention) valtio storevaltio state is a mutable object, so it's a store. (but usually, I call it state instead of store) const store = proxy(...)
store // is a state object
snapshot(store) // return an immutable state object
store.foo = ... // you can only update part of the store jotai storejotai state is only accessible with atom references. state is not a single object, but it consists of pieces. const store = createStore()
store.get(fooAtom) // to return the piece of state by an atom reference
store.set(fooAtom, ...) // and to update it. |
I guess what I mean is: in Jotai, can I have multiple stores that consist of different sets of atoms? In all of those other examples you can. In every other example, those stores were setting up individual stores and explicitly setting what was in the store. In Jotai's example, you're not, and I feel like that omission is inherently confusing. In the semantic concept of a store, I should be identifying what's in the store. Your last example does not define or identify what's in the store. It just seems to set up an ethereal "concept of a store" which then gets items which were not identified as members of the store. The mental model of your example is like: const myHouse = createHouse()
const neighborsHouse = createHouse()
myHouse.get('Tesla') // returns a Tesla even though a Tesla was never added to my house
neighborsHouse.get('Tesla') // will this also return a Tesla? A different Tesla? It's not clear. What will this do in Jotai? const store1 = createStore()
const store2 = createStore()
store1.get(fooAtom)
store2.get(fooAtom) If only |
(Assuming your discussion is more in concept rather than in use cases) Yes, you can create multiple stores. This works: const store1 = createStore()
const store2 = createStore()
store1.get(fooAtom)
store2.get(fooAtom) Jotai uses atom references as key instead of a string key, but conceptually it's the same as string key, so in mental model, it's analogous to this: const store1 = createStore()
const store2 = createStore()
store1.get('foo')
store2.get('foo') (without "initial value", they'd return |
I think a pseudo implementation might help understanding it: const createStore = () => {
const map = new WeakMap()
const get = (atom) => map.get(atom) ?? atom.init
const set = (atom, value) => map.set(atom, value)
return { get, set }
} |
@dai-shi Okay, maybe I'm really dumb and I'm missing something really really obvious, but in regards to:
What I mean is: where are you setting the value of Let me rephrase: what is the output of this (from your example): const countAtom = atom(0)
const store = createStore()
const value = store.get(countAtom)
console.log(value) // If I run this code, will it print '0'?
// secondly, what would be the value of this?
const store2 = createStore()
const value2 = store2.get(countAtom)
console.log(value2) // If I run this code, will it also print '0'? How? What is happening? Where are you putting |
Okay, but in your original example, you demonstrated getting the value BEFORE SETTING IT. So I took this to imply that You literally wrote: import { atom, Provider, unstable_createStore } from 'jotai'
const countAtom = atom(0)
const store = unstable_createStore()
const App = () => (
<Provider unstable_createStore={() => store}>
...
</Provider>
)
// read atom value outside React
const value = store.get(countAtom) This is the only confusing thing to me. |
countAtom has an initial state of 0. That’s represented by |
Let me continue the pseudo code: (edit: to make createStore function a little more like the real impl) // -----------------------
// library code
// -----------------------
import { createContext, useContext, useRef } from 'react'
export const atom = (initialValue) => ({ init: initialValue })
const StoreContext = createContext()
export const Provider = ({ children, unstable_createStore }) => {
const storeRef = useRef()
if (!storeRef.current) storeRef.current = unstable_createStore()
return <StoreContext.Provider value={storeRef.current}>{children}</StoreContext.Provider>
}
export const useAtom = (anAtom) => {
const store = useContext(StoreContext)
return [store.get(anAtom), (value) => store.set(anAtom, value)]
}
export const unstable_createStore = () => {
const map = new WeakMap()
const get = (atom) => {
if (!map.has(atom)) map.set(atom, atom.init)
return map.get(atom)
}
const set = (atom, value) => map.set(atom, value)
return { get, set }
} // -----------------------
// example code
// -----------------------
import { Provider, atom, useAtom, unstable_createStore } from 'jotai'
const countAtom = atom(0)
const store = unstable_createStore()
const App = () => (
<Provider unstable_createStore={() => store}>
...
</Provider>
)
// read atom value outside React
const value = store.get(countAtom) // returns initially `0`. See `?? atom.init`
// write atom value outside React
store.set(countAtom, 1) // will create a new entry in the WeakMap at first. |
Wait so... const countAtom = atom(0) Sets up an object ( |
If #854 is successfully merged, we would like to consider exposing
unstable_createStore
.The text was updated successfully, but these errors were encountered: