GithubHelp home page GithubHelp logo

Nesting stores example about hookstate HOT 16 CLOSED

lishine avatar lishine commented on August 22, 2024
Nesting stores example

from hookstate.

Comments (16)

avkonst avatar avkonst commented on August 22, 2024

Is there an example to nest stores?
For example, if I need all stores to be nested under one store.

const store = createStateLink({ settings: {...}, user: {...} }, dynamicStores: {})

function useWholeStore() {
    return useStateLink(store)
}

function useSettingsStore() {
    return useStateLink(store, l => l.nested.settings)
}

function useUserStore() {
    return useStateLink(store, l => l.nested.user)
}

function useDynamicStore(id: string) {
    return useStateLink(store, l => l.nested.dynamicStores.nested[id])
}

attach a store dynamically.

const state = useWholeStore() // PS: here it uses mounted state, but you can do the same outside of a React component using useStateLinkUnmounted
const id_for_new_store = Math.random().toString()
state.nested.dynamicStores.nested[id_for_new_store ].set({...})

something along these lines. Does it answer your question?

from hookstate.

lishine avatar lishine commented on August 22, 2024

Great to know that setting an object, then becomes actually a store by itself.
All is great. What is missing to me, is an example how to include methods relevant to substore. Is it with transform? So you will set dynamic store and then useStateLink to attach methods?
Ok, I get it, you actually attach methods inside you useUserStore() for example. But the what happens of you want to use it outside a component?
BTW by dynamic, I just mean attaching some known store at some stage and deleting it later. For example for dynamic component loading

from hookstate.

avkonst avatar avkonst commented on August 22, 2024

Is it with transform?

Yes, it is with transform. Example is here https://hookstate.js.org/global-getting-started-interface

So you will set dynamic store and then useStateLink to attach methods?

Yes, if you wish to add methods. I frequently use plain StateLink object it is fine for most of the cases.

But the what happens if you want to use it outside a component?

If you want to access the store outside of a component, use useStateLinkUnmounted function. Example is here: https://hookstate.js.org/global-getting-started

This function returns the same StateLink object, which you can use to create, delete, update substores or any other segment of the data in the store. You can also apply transform the same way.

BTW by dynamic, I just mean attaching some known store at some stage and deleting it later. For example for dynamic component loading

It is easy. Have a look into the source code of this example: https://hookstate-example-app.netlify.com/ Two stores used here. They can be defined at completely different modules. They can be loaded separately. Hookstate StateRef is just a variable with subscribers on it's state, which can be managed the same ways as any other javascript variable.

Does it answer your question?

from hookstate.

lishine avatar lishine commented on August 22, 2024

I would like to do createStateLink({})
Then do .set({ someStore })
And add transform functions.
Essentially build a Store tree of Stores.
It is like with easy-peasy or mobx-state-tree.
And this store to be used in mounted or unmounted.
I have a thinking how to do it , but is not clear to me. There is also,
useUnmountedStateLink doesn't accept StateLink where useStateLink does accept it.
I think, I can do like this.

useStateLink(unmountedStateLink(createStateLink().nested.newStore.set({}), ()=>...))

from hookstate.

lishine avatar lishine commented on August 22, 2024

Correction

UserStore = unmountedStateLink(createStateLink().nested.set({ newStore }), ()=>...)
Outside of the component will use UserStore.
In a component:
useStateLink(UserStore)

Is it correct?

from hookstate.

avkonst avatar avkonst commented on August 22, 2024

The correct way would be as per the example I provided above. Sorry, mate, I am not sure I understand what you are trying to achieve. Could you please explain it a bit more?

from hookstate.

lishine avatar lishine commented on August 22, 2024

I am confused about this syntax

const transform = (s: StateLink<Task[]>) => ({
    addTask = (t: Task) => s.set(tasks => tasks.concat([t]))
})

useStateLinkUnmounted(stateRef, transform).addTask({ name: 'Untitled' })

Vs this syntax

 useStateLink(store, l => l.nested.user)

And looking at the code, how the first syntax returns the original statelink, it looks like it is overriden with the object...

from hookstate.

avkonst avatar avkonst commented on August 22, 2024

The first useStateLinkUnmounted(stateRef, transform) returns an object, which has got a method addTask. This object effectively wraps the original statelink by a custom interface, as a result, hides the details of the StateLink layout, and only return an object of type {addTask()}.

The second useStateLink(store, l => l.nested.user) also transforms the original statelink l. It selects and returns only the nested user statelink. So, no new object is constructed, just part of a state is selected. It hides the fact that the returned statelink is a nested property of the store. Return type will be StateLink<User>. You can extend this to something like the following:

// assuming User object has got property name of type string.
useStateLink(store, l => ({
     getName() {  return l.nested.user.nested.name.get() }, // or the same as l.get().user.name
     setName(newName: string) { l.nested.user.nested.name.set(newName) }
}))

Return type of this call will be: { getName(): string, setName(name: string) }

It is up to you to wrap the StateLink by a custom store access interface or return the original StateLink without transform, or return nested StateLink with a 'select-like' transform function. If you develop a library and do not want to expose StateLink details, wrap by a custom store-access interface. Otherwise, it is up to you. You have got freedom of choice here.

from hookstate.

avkonst avatar avkonst commented on August 22, 2024

More explanation:

The following

function MyComponent() {
    const stateAccessInterface = useStateLink(store, transform)
    ....
}

Is the same as:

function MyComponent() {
    const statelink = useStateLink(store)
    const stateAccessInterface = transform(statelink)
    ....
}

transform function can be anything, which receives statelink and returns anything.

There are 2 cases where the first option of applying the transform (i.e. as a second argument) is the only option. The first case is when you want to use StateMemo transform function. Read about it in the docs. It is essentially for additional performance improvement, which you might not always need. The second case when you apply the transform not at the useStateLink call but earlier, i.e. when you create the original Store object. For example:

const Store: StateInf<{ getName(): string, setName(name: string): void }> = createStateLink({ user: { name: 'Bob' } }, l => ({
     getName() {  return l.nested.user.nested.name.get() }, // or the same as l.get().user.name
     setName(newName: string) { l.nested.user.nested.name.set(newName) }
}))

const userStateAccessInterface = useStateLinkUnmounted(Store) // will return your custom access interface straight away
userStateAccessInterface.setName('John')

function MyComponent() {
    const userStateAccessInterface = useStateLink(Store) // will return your custom access interface straight away
    return <input value={userStateAccessInterface.getName()} onChange={e => userStateAccessInterface.setName(e.target.value)} />
}

from hookstate.

lishine avatar lishine commented on August 22, 2024

Got you

from hookstate.

lishine avatar lishine commented on August 22, 2024

So the createStateLink doesn't create stateLink or is it? It seems to create stateRef.
Can I then use the linkstate from useUnmountedStateLink in useStateLink?

ref = createStateLink({})
s = useUnmountedStateLink(ref)
s.nested.user.set({})
User = s.nested.user
User.notReservedMethod = () => doSomething(User)
useStateLink(User)

from hookstate.

avkonst avatar avkonst commented on August 22, 2024

So the createStateLink doesn't create stateLink or is it?

You are right. I see there is a confusion. Names are probably for historical reasons. In fact, StateRef<S> is the same as StateInf<StateLink<S>>. It means createStateLink<S>(v) returns StateInf<StateLink<S>>, and createStateLink<S, R>(v, transform: (l: StateLink<S> => R)) returns StateInf<R>. It means better name for createStateLink is actually createStateInf.

useStateLinkUnmounted is deprecated on master and replaced by easier to use and warning free alternative - access method of StateInf: StateInf<R>.access(): R. R can be StateLink<S>, if transform is not applied or returns StateLink instance.

useStateLink either:

  1. accepts StateInf and returns the observed version of StateInf.access() result,
  2. accepts StateLink (and optional transform) and returns StateLink (or optionally transformed StateLink) pointing to the same data, but observed differently for scoped state updates for performance
  3. or accepts initialValue (and optional transform) and returns StateLink (or result of the optional transform of the StateLink) - internally it creates the analogy of StateInf from the initialValue.

So good name for useStateLink is probably useStateInf. I am also thinking, it can be named just useState (and createStateLink could be named just createState, and StateInf just State), but I am a bit concerned about the conflict with React.useState. Alternative combo is StateInf -> Store, useStateLink -> useStore and createStateLink -> createStore, but I am not sure if the name implies that Store is global. It is not always global, useStateLink can auto-create local state as per 3) above. What do you think is the best combo names?

Can I then use the linkstate from useUnmountedStateLink in useStateLink?

As per 2) above, type system will allow you do so, but it does not make sense and will not give you the expected result. Non-transformed result of useStateLinkUnmounted returns StateLink which does not observe state changes. It will still always return you the latest state data when you use it, but it will not react on state changes. It means the derivative of it, like useStateLink(useStateLinkUnmounted(Store)) will not trigger re-render of you component.

The correct way is:

GlobalStore = createStateLink({ user: {}})
UserStore = GlobalStore.wrap(l => l.nested.user) // you can wrap by a custom state access interface, like we discussed above

useStateLinkUnmounted(UserStore).set({ /* new user data */ })
// in the coming version, it will be UserStore.access().set(...)

function MyComponent() {
    const userState = useStateLink(UserStore)
    userState.get().name // example
}

// setting props of the StateLink or StateLink.value is not correct,
// it will either throw or is not observed by components for rerender. 
// Always use set() method (or coming merge) to mutate the data in the state
User.notReservedMethod = () => doSomething(User) // <!-- do not do this

Hope it helps.

from hookstate.

lishine avatar lishine commented on August 22, 2024

It helps, thanks!

from hookstate.

avkonst avatar avkonst commented on August 22, 2024

What do you think about names to remove the confusion?

from hookstate.

lishine avatar lishine commented on August 22, 2024

createStore and useStore are good names.
Because can be many global stores, it is explicit enough to write useStore (someGlobalStore).
But maybe it is wise to differentiate
useStore
useFeatureStore
useLocalStore

from hookstate.

avkonst avatar avkonst commented on August 22, 2024

from hookstate.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.