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

Context #171

Open
Tracked by #172
ai opened this issue Mar 9, 2023 · 6 comments
Open
Tracked by #172

Context #171

ai opened this issue Mar 9, 2023 · 6 comments

Comments

@ai
Copy link
Member

ai commented Mar 9, 2023

Let’s discuss a way to solve a few problems.

Problem 1: Cache API

Many stores have cache:

  • Logux Client caches resource’s instances
  • Nano Stores Query caches responses

Current Nano Stores has MapTemplate for such things. But it has a few problems:

  1. Can use only a string ID, which doesn’t fit Nano Stores Query needs.
  2. People do not really understand it. There are many questions how to use MapTemplate.
  3. The API is almost private. It is hard to use it.

Problem 2: SSR

In SSR, Node.js will render HTML for multiple users in the same time (in the same Node.js environment). If you just export store from a file, all users will share the same store. It could be a very dangerous.

For instance, if we have something like import { authStore } from '../store/auth.js' will be the same store for all users.

We need a way to separate stores from user’s environment

Problem 3: Mocks

It will be nice to be able to replace any store with a mock for tests or to create an environment for Storybook.

Right now we can use atom#set() or some stores provide custom API. But it doesn’t disable events, and it will be nice to have some common API.

Problem 4: DevTools

It will be nice to have some sort of Map or Set with all stores on the current page, so we will be able to use them in DevTools.

It will be also nice to be able to dump the whole state to a file or reproduce of the state of user reported a bug.

@ai ai mentioned this issue Mar 9, 2023
5 tasks
@TrySound
Copy link
Contributor

TrySound commented Apr 17, 2023

Let's figure out how to store data. I see two approaches

  1. Store all data and access with namespaces
const container: Map<NamespaceString, Map<StoreIdString, AtomStore>>

const enableGlobalNanostores = () => {
  globalThis.__nanostores__ = container
}

// some builtin namespace to work out off the box
const defaultNamespace = 'default'

// access store and manipulate its content in atom methods
const atomStore = container.get(getCurrentNamespace())?.get(currentStoreId)
  • container is accessed with just string
  • user have to search state in 2 dimensional map in devtools
  • user don't need to manage container
  1. Store containers and access by reference
/// builtin container to work out of the box
const defaultContainer: Map<StoreIdString, AtomStore>

// access store and manipulate its content in atom methods
const atomStore = getCurrentContainer().get(currentStoreId)

const userContainer = createContainer()

globalThis.__myNanostores__ = userContainer
  • context (async storage or react context) store whole container, not just a namespace
  • user can expose own container
  • user has to maintain map of containers for example in ssr

@geminigeek
Copy link

hi,
just came across Problem 2, i am using it with astro + vue , for SSR using ( Astro node adaptor as middleware ), any solution for the issue ?

@ai
Copy link
Member Author

ai commented Apr 20, 2023

@geminigeek the best solutions is to export store creators and not stores themselves

export function createProfileStore () {
  return atom()
}

And then create a single instance in application root.

TrySound added a commit to TrySound/nanostores that referenced this issue May 9, 2023
Ref nanostores#171

Here's initial attempt to move all stores state into global context
object. Context is basically a listeners queue and a map of stores
states.

Context management api is still yet to explore. For now it's for
internal usage now.
TrySound added a commit to TrySound/nanostores that referenced this issue May 27, 2023
Ref nanostores#171

Here's initial attempt to move all stores state into global context
object. Context is basically a listeners queue and a map of stores
states.

Context management api is still yet to explore. For now it's for
internal usage now.
@ai
Copy link
Member Author

ai commented May 29, 2023

My suggestion of Context API:

  1. Nano Stores exports function to create a context: import { createContext } from 'nanostores'
  2. The one global context will be created by default : import { globalContext } from 'nanostores'
  3. All events ('onMount, etc) get current context to the callback.
  4. Store’s access API for stores will have a 2 forms:
    1. Global context: store.get(), store.listen()
    2. User’s context: withContext(store, context).get(), withContext(store, context).listen. Life cycle and listen callbacks will have current context in an argument.
  5. Context has an API to:
    1. Get all registered stores
    2. Mock any store’s value without calling events
  6. All libraries much use withContext(). In the app, developer could choose between global or user’s context (it will be illegal to mix global and user’s to avoid mistakes).
    6. Calling createContext will “poison” global context and any calls on global context will throw an error
  7. React’s useStore() will look for Context from special React’s context.

@TrySound
Copy link
Contributor

Looks great. One more use case I have is clearing context when switch routes on client. Maybe as additional method or helper.

@btakita
Copy link
Contributor

btakita commented Jun 19, 2023

I have already been using nanostores, svelte stores, & solid-js signals isomorphically using an api which I made in @ctx-core/object which handles all of the use cases mentioned in #171. The only caveat is that the ctx is explicitly passed in as the first argument & not typically implicitly used via global state. It could have implicit sugar but I chose not to use it that way. A Ctx can also be a nested array to overlay context.

export declare type MapCtx = Map<Be<any>|string|symbol, unknown>
export interface NestedMapCtx extends Array<NestedMapCtx|MapCtx> {
}
export type Ctx = MapCtx|NestedMapCtx

The ctx simply follows garbage collection idioms.

Here is a small code sample of how I have been using the api:

import { nullish_check_ } from '@ctx-core/function'
import { be_ } from '@ctx-core/object'
import { val__be_atom_triple_, val__be_computed_pair_ } from '@ctx-core/nanostores'
const default_user_id_ = be_(() => -1) // just showing the primitive be_ function here
const [
  user_id$_,
  user_id_,
  user_id__set
] = val__be_atom_triple_(ctx => default_user_id_(ctx))
const [
  user$_,
  user_
] = val__be_computed_pair_(ctx =>
  nullish_check_([user_id_(ctx)], () =>
    fetch(`https://my-api/users/${user_id_(ctx)}`).then(response => response.json())))

Perhaps in the interest of keeping nanostores focused on the stores themself, it would make sense to create a separate library to manage context in an generalized & isomorphic way? The added benefit is that it would also work for any other type state management. I can shamelessly plug ctx-core but I'm also using a vector name system which is different from camelCaseNaming which works great for my use cases but is not the dominant trend...so I would be happy to assist on making a new library with more standard naming conventions & would better fit the mold of how nanostores is used.

If this is not the direction you want to head in, then please disregard this comment. I just wanted to share in case it is something worth pursuing or there is anything useful from the solution.


Edit

I see there are already PR's in place for an implicit Context which looks great & probably makes sense in the context (no pun intended) of this project.

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

No branches or pull requests

4 participants