A tiny (258 bytes) state manager for React/RN/Preact/Vue/Svelte with many atomic tree-shakable stores

Overview

Nano Stores

A tiny state manager for React, React Native, Preact, Vue, Svelte, and vanilla JS. It uses many atomic stores and direct manipulation.

  • Small. Between 266 and 969 bytes (minified and gzipped). Zero dependencies. It uses Size Limit to control size.
  • Fast. With small atomic and derived stores, you do not need to call the selector function for all components on every store change.
  • Tree Shakable. The chunk contains only stores used by components in the chunk.
  • Was designed to move logic from components to stores.
  • It has good TypeScript support.
// store/users.ts
import { atom } from 'nanostores'

export const users = atom<User[]>([])

export function addUser(user: User) {
  users.set([...users.get(), user]);
}
// store/admins.ts
import { computed } from 'nanostores'
import { users } from './users.js'

export const admins = computed(users, list =>
  list.filter(user => user.isAdmin)
)
// components/admins.tsx
import { useStore } from '@nanostores/react'
import { admins } from '../stores/admins.js'

export const Admins = () => {
  const list = useStore(admins)
  return (
    <ul>
      {list.map(user => <UserItem user={user} />)}
    </ul>
  )
}
Sponsored by Evil Martians

Table of Contents

Install

npm install nanostores

Tools

  • Persistent store to save data to localStorage and synchronize changes between browser tabs.
  • Router store to parse URL and implements SPA navigation.
  • I18n library based on stores to make application translatable.
  • Logux Client: stores with WebSocket sync and CRDT conflict resolution.

Guide

Atoms

Atom store can be used to store strings, numbers, arrays.

You can use it for objects too if you want to prohibit key changes and allow only replacing the whole object (like we do in router).

To create it call atom(initial) and pass initial value as a first argument.

import { atom } from 'nanostores'

export const counter = atom(0)

In TypeScript, you can optionally pass value type as type parameter.

export type LoadingStateValue = 'empty' | 'loading' | 'loaded'
export const loadingState = atom<LoadingStateValue>('empty')

store.get() will return store’s current value. store.set(nextValue) will change value.

counter.set(counter.get() + 1)

store.subscribe(cb) and store.listen(cb) can be used to subscribe for the changes in vanilla JS. For React/Vue we have extra special helpers to re-render the component on any store changes.

const unbindListener = counter.subscribe(value => {
  console.log('counter value:', value)
})

store.subscribe(cb) in contrast with store.listen(cb) also call listeners immediately during the subscription.

Maps

Map store can be used to store objects and change keys in this object.

To create map store call map(initial) function with initial object.

import { map } from 'nanostores'

export const profile = map({
  name: 'anonymous'
})

In TypeScript you can pass type parameter with store’s type:

export interface ProfileValue {
  name: string,
  email?: string
}

export const profile = map<ProfileValue>({
  name: 'anonymous'
})

store.set(object) or store.setKey(key, value) methods will change the store.

profile.setKey('name', 'Kazimir Malevich')

Store’s listeners will receive second argument with changed key.

profile.listen((value, changed) => {
  console.log(`${changed} new value ${value[changed]}`)
})

You can use store.notify() to trigger listeners without changing value in the key for performance reasons.

store.get().bigList.push(newItem)
store.notify('bigList')

Maps Templates

Map templates was created for similar stores like for the store for each post in the blog where you have many posts. It is like class in ORM frameworks.

This is advanced tool, which could be too complicated to be used on every case. But it will be very useful for creating libraries like react-query. See Logux Client for example.

Nano Stores has map templates, to use a separated store for each item because of:

  1. Performance: components can subscribe to the changes on specific post.
  2. Lists can’t reflect that only specific subset of posts was loaded from the server.

mapTemplate(init) creates template. init callback will receive item’s store and ID.

import { mapTemplate } from 'nanostores'

export interface PostValue {
  id: string
  title: string
  updatedAt: number
}

export const Post = mapTemplate<PostValue>((newPost, id) => {
  newPost.setKey('title', 'New post')
  newPost.setKey('updatedAt', Date.now())
})

Each item of the template must have value.id.

let post1 = Post('1')
post1.get().id //=> '1'

Lazy Stores

Nano Stores unique feature is that every state have 2 modes:

  • Mount: when one or more listeners was mount to the store.
  • Disabled: when store has no listeners.

Nano Stores was created to move logic from components to the store. Stores can listen URL changes or establish network connections. Mount/disabled modes allow you to create lazy stores, which will use resources only if store is really used in the UI.

onMount sets callback for mount and disabled states.

import { onMount } from 'nanostores'

onMount(profile, () => {
  // Mount mode
  return () => {
    // Disabled mode
  }
})

For performance reasons, store will move to disabled mode with 1 second delay after last listener unsubscribing.

Map templates can use init callback for code for mount and disabled modes:

mapTemplate((post, id) => {
  // Mount mode
  let unsubscribe = loadDataAndSubscribe(`/posts/${id}`, data => {
    post.set(data)
  })
  return () => {
    // Disabled mode
    unsubscribe()
  }
})

Call keepMount() to test store’s lazy initializer in tests and cleanStores to unmount them after test.

import { cleanStores, keepMount } from 'nanostores'
import { Post } from './profile.js'

afterEach(() => {
  cleanStores(Post)
})

it('is anonymous from the beginning', () => {
  let post = Post(1)
  keepMount(post)
  // Checks
})

Map template will keep cache of all mount stores:

postA = Post('same ID')
postB = Post('same ID')
postA === postB //=> true

Computed Stores

Computed store is based on other store’s value.

import { computed } from 'nanostores'
import { users } from './users.js'

export const admins = computed(users, all => {
  // This callback will be called on every `users` changes
  return all.filter(user => user.isAdmin)
})

You can combine a value from multiple stores:

import { lastVisit } from './lastVisit.js'
import { posts } from './posts.js'

export const newPosts = computed([lastVisit, posts], (when, allPosts) => {
  return allPosts.filter(post => post.publishedAt > when)
})

Actions

Action is a function that changes a store. It is a good place to move business logic like validation or network operations.

Wrapping functions with action() can track who changed the store in the logger.

import { action } from 'nanostores'

export const increase = action(counter, 'increase', (store, add) => {
  if (validateMax(store.get() + add)) {
    store.set(store.get() + add)
  }
  return store.get()
})

increase(1) //=> 1
increase(5) //=> 6

Actions for map template can be created with actionFor():

import { actionFor } from 'nanostores'

export const rename = actionFor(Post, 'rename', async (store, newTitle) => {
  await api.updatePost({
    id: store.get().id,
    title: newTitle
  })
  store.setKey('title', newTitle)
  store.setKey('updatedAt', Date.now())
})

await rename(post, 'New title')

All running async actions are tracked by allTasks(). It can simplify tests with chains of actions.

import { allTasks } from 'nanostores'

renameAllPosts()
await allTasks()

Tasks

startTask() and task() can be used to mark all async operations during store initialization.

import { task } from 'nanostores'

onMount(post, () => {
  task(async () => {
    post.set(await loadPost())
  })
})

You can wait for all ongoing tasks end in tests or SSR with await allTasks().

import { allTasks } from 'nanostores'

post.listen(() => {}) // Move store to active mode to start data loading
await allTasks()

const html = ReactDOMServer.renderToString(<App />)

Async actions will be wrapped to task() automatically.

rename(post1, 'New title')
rename(post2, 'New title')
await allTasks()

Store Events

Each store has a few events, which you listen:

  • onStart(store, cb): first listener was subscribed.
  • onStop(store, cb): last listener was unsubscribed.
  • onMount(store, cb): shortcut to use both onStart and onStop. We recommend to always use onMount instead of onStart + onStop, because it has a short delay to prevent flickering behavior.
  • onSet(store, cb): before applying any changes to the store.
  • onNotify(store, cb): before notifying store’s listeners about changes.

onSet and onNotify events has abort() function to prevent changes or notification.

import { onSet } from 'nanostores'

onSet(store, ({ newValue, abort }) => {
  if (!validate(newValue)) {
    abort()
  }
})

Same event listeners can communicate with payload.shared object.

Integration

React & Preact

Use @nanostores/react or @nanostores/preact package and useStore() hook to get store’s value and re-render component on store’s changes.

import { useStore } from '@nanostores/react' // or '@nanostores/preact'
import { profile } from '../stores/profile.js'
import { Post } from '../stores/post.js'

export const Header = ({ postId }) => {
  const user = useStore(profile)
  const post = useStore(Post(postId))
  return <header>{post.title} for {user.name}</header>
}

Vue

Use @nanostores/vue and useStore() composable function to get store’s value and re-render component on store’s changes.

<template>
  <header>{{ post.title }} for {{ user.name }}</header>
</template>

<script>
  import { useStore } from '@nanostores/vue'

  import { profile } from '../stores/profile.js'
  import { Post } from '../stores/post.js'

  export default {
    setup (props) {
      const user = useStore(profile)
      const post = useStore(Post(props.postId))
      return { user, post }
    }
  }
</script>

Svelte

Every store implements Svelte's store contract. Put $ before store variable to get store’s value and subscribe for store’s changes.

<script>
  import { profile } from '../stores/profile.js'
  import { Post } from '../stores/post.js'

  export let postId

  const post = Post(postId)
</script>

<header>{$post.title} for {$profile.name}</header>

Solid

Use @nanostores/solid and useStore() composable function to get store’s value and re-render component on store’s changes.

import { useStore } from '@nanostores/solid'
import { profile } from '../stores/profile.js'
import { Post } from '../stores/post.js'

export function Header({ postId }) {
  const user = useStore(profile)
  const post = useStore(Post(postId))
  return <header>{post().title} for {user().name}</header>
}

Vanilla JS

Store#subscribe() calls callback immediately and subscribes to store changes. It passes store’s value to callback.

import { profile } from '../stores/profile.js'
import { Post } from '../stores/post.js'

const post = Post(postId)

function render () {
  console.log(`${post.title} for ${profile.name}`)
}

profile.listen(render)
post.listen(render)
render()

See also listenKeys(store, keys, cb) to listen for specific keys changes in the map.

Server-Side Rendering

Nano Stores support SSR. Use standard strategies.

if (isServer) {
  settings.set(initialSettings)
  router.open(renderingPageURL)
}

You can wait for async operations (for instance, data loading via isomorphic fetch()) before rendering the page:

import { allTasks } from 'nanostores'

post.listen(() => {}) // Move store to active mode to start data loading
await allTasks()

const html = ReactDOMServer.renderToString(<App />)

Tests

Adding an empty listener by keepMount(store) keeps the store in active mode during the test. cleanStores(store1, store2, …) cleans stores used in the test.

import { cleanStores, keepMount } from 'nanostores'
import { profile } from './profile.js'

afterEach(() => {
  cleanStores(profile)
})

it('is anonymous from the beginning', () => {
  keepMount(profile)
  expect(profile.get()).toEqual({ name: 'anonymous' })
})

You can use allTasks() to wait all async operations in stores.

import { allTasks } from 'nanostores'

it('saves user', async () => {
  saveUser()
  await allTasks()
  expect(analyticsEvents.get()).toEqual(['user:save'])
})

Best Practices

Move Logic from Components to Stores

Stores are not only to keep values. You can use them to track time, to load data from server.

import { atom, onMount } from 'nanostores'

export const currentTime = atom<number>(Date.now())

onMount(currentTime, () => {
  currentTime.set(Date.now())
  const updating = setInterval(() => {
    currentTime.set(Date.now())
  }, 1000)
  return () => {
    clearInterval(updating)
  }
})

Use derived stores to create chains of reactive computations.

import { computed } from 'nanostores'
import { currentTime } from './currentTime.js'

const appStarted = Date.now()

export const userInApp = computed(currentTime, now => {
  return now - appStarted
})

We recommend moving all logic, which is not highly related to UI, to the stores. Let your stores track URL routing, validation, sending data to a server.

With application logic in the stores, it is much easier to write and run tests. It is also easy to change your UI framework. For instance, add React Native version of the application.

Separate changes and reaction

Use a separated listener to react on new store’s value, not an action where you change this store.

  const increase = action(counter, 'increase', store => {
    store.set(store.get() + 1)
-   printCounter(store.get())
  }

+ counter.listen(value => {
+   printCounter(value)
+ })

An action is not the only way for store to a get new value. For instance, persistent store could get the new value from another browser tab.

With this separation your UI will be ready to any source of store’s changes.

Reduce get() usage outside of tests

get() returns current value and it is a good solution for tests.

But it is better to use useStore(), $store, or Store#subscribe() in UI to subscribe to store changes and always render the actual data.

- const { userId } = profile.get()
+ const { userId } = useStore(profile)

Known Issues

ESM

Nano Stores use ESM-only package. You need to use ES modules in your application to import Nano Stores.

In Next.js ≥11.1 you can alternatively use the esmExternals config option.

For old Next.js you need to use next-transpile-modules to fix lack of ESM support in Next.js.

Comments
  • Nano Stores 0.5

    Nano Stores 0.5

    We collected first feedback of Nano Stores, and we are ready for next big refactoring and fix user’s complaints.

    We will try to keep backward compatibility: we will wrap new methods to old methods and print warning.

    Refactoring goals:

    • [x] Unclear terms:
      • [x] Store vs. Map. How to call common category of store and map? “Store” is not self-describable.
      • [x] “Derived” term is hard to remember
      • [x] Store definition (defaineMap) is not self-describable
    • [x] Make functions shorter by removing create* prefix
    • [x] Fix Diamond problem
    • [x] Add pure JS API to listen for some specific keys
    • [x] Do not clean store by default
    • [x] Add store events for store cleaning, method patching and plugins chains
    • [x] Add “action” term to docs and action() builder with effect auto-wrapper
    • [x] Clean npm package from framework integrations
    • [x] Immutable object in map store for better Vue integration

    dotto.x by @Eddort solve most of these problems and can be a huge inspiration source (should we merge projects?).

    Changes after release:

    • [ ] Better DevTools:
      • [ ] Logger
      • [ ] See all stores with values, listener number and listener’s filename:line
      • [ ] See store changes with old, new value, optional changed key, changer action
      • [ ] Build a map of stores and components
    opened by ai 28
  • Nanostores Missunderstanding In react native

    Nanostores Missunderstanding In react native

    Iam implementing a simple exemple with useStore counter in main composant and another component is modifying it with a increaseCounter() but I got an error :

    "Warning: Cannot update a component from inside the function body of a different component."

    If I manage increaseCounter from the same main component it works, so how implement a store change from others components ? is not the purpose ? Iam sure I have missed something.

    thanks

    opened by electroheadfx 10
  • Object as value in map?

    Object as value in map?

    My API returns complete related object along with item data and I wanted to store it in a map store but when reading store I'm always getting undefined for that key - is this some kind of limitation?

    opened by zgoda 8
  • With map store calling setKey() before subscription loses values

    With map store calling setKey() before subscription loses values

    If you use createMap to create a map store and update it via .setKey() before the store is active it'll dump all those values once it activates.

    https://codesandbox.io/s/nanostores-mapsetkey-losing-values-9u0w4?file=/src/index.js

    import { createMap, getValue } from "nanostores";
    
    const map = createMap();
    
    map.setKey("foo", 1);
    map.setKey("bar", 1);
    
    console.log(getValue(map)); // {}
    
    // Expected it to log out { foo: 1, bar: 1 }
    

    It's caused by this condition in .listen(),

    https://github.com/nanostores/nanostores/blob/d372a5432c9cf875fb59fc3bbe757f40eab5cc30/create-map/index.js#L50-L53

    A simple store created via createStore doesn't have this extra bit of logic (which makes sense, what would it reset to?)

    https://github.com/nanostores/nanostores/blob/d372a5432c9cf875fb59fc3bbe757f40eab5cc30/create-store/index.js#L23-L25

    Is this behavior expected/documented anywhere? I didn't expect it to dump all the stored values as soon as something tried to read the values and can't find any info in the readme about it. It feels like a bug to me, but I can work around it for now by using createStore with a Map object and updating it using update(...) instead of .setKey().

    opened by tivac 8
  • publish docs

    publish docs

    As for now logux.io were updated not to have @logux/state references this project doesn't have web site with own documentation. Do we expect to have such or readme is enough as for now?

    opened by Guria 8
  • Computed stores fire in strange order making state incorrect

    Computed stores fire in strange order making state incorrect

    Here's a repro.

    Given

    Atom 1 -> Computed 1 -> Computed 2

    Expected

    When making changes to Atom 1, you always get the following order of execution: Computed 1 -> Computed 2. Also, Computed 2 always has correct derived state.

    Actual

    If you make changes in some sequence (I don't really get the logic), the order of execution of computed stores reverts, so first we get a Computed 2 execution with an OLD state (which results in incorrect derived state for this store), then Computed 1 gets executed, but it doesn't trigger Computed 2 execution.

    Here's a video for this repro. Hint: if you see a string getting logged twice in a row, it's the bug. I did two doubleclicks, that should have resulted in number 3 and number 5 appearing.

    https://user-images.githubusercontent.com/2846746/155877236-e5151f30-0375-43d2-9dcf-9183760b5002.mov

    opened by dkzlv 7
  • Fix some type-related errors

    Fix some type-related errors

    This is not a full fix. There are three more errors of type "Required type parameters may not follow optional type parameters." in lifecycle/index.d.ts. I don't know how to fix them yet.

    opened by davidmz 7
  • Advanced lifecycle

    Advanced lifecycle

    In Nano Stores 0.5 we can copy lifecycle from dotto.x. The plan:

    Events:

    • onStart for first listener
    • onStop for last listener
    • onChange/onSet on data changes

    API: onStart(store, event => {}). event will contain store, abort (for onChange and onSet), stopPropagation, data.

    Questions:

    1. What is the difference between onChange and onSet in dotto.x?
    2. Do we really need onGet?

    Plan:

    1. Add lifecycle code
    2. Remove store auto-cleaning and use lifecycle
    3. Add mount helper to set onStart and onStop with delay after onStop
    4. Use mount in deprecated/index.js store creators

    /cc @Eddort

    opened by ai 7
  • Glitches (Diamond problem)

    Glitches (Diamond problem)

    Hi! I was wondering, how nanostores handles cases with tricky dependencies, so i wrote a few tests to see, if nanostores is glitch-prone

    Effector & Reatom tests were used as a reference:

    1. Nanostores tests for glitches and diamond problem - https://codesandbox.io/s/nanostores-glitches-2w42k?file=/src/nanostores.test.js
    2. Comparison with Rx, MobX & Effector: https://codesandbox.io/s/effector-comparison-forked-0bri6?file=/src/index.test.js (based on this sandbox)

    And i found a few issues:

    1. Only Rx & Nanostores are recalculating whole chain over and over again - for each update there is 3 triggers of "displayName" store subscribers - which is, obviously, bad for library performance, especially if stores combinations are getting more complicated

    2. nanostores triggers subscribers at the end of the chain with intermediate, inconsistent values. It happens, because nanostores doesn't wait for calculations in other branch to finish and proceeds to update store

    As a result - store value becomes not convergent until second branch calculations are done - and store subscribers are triggered with invalid value too, so this inconvergence will be spreading further

    It, probably, will not be a big problem for React - since nanostores/react uses React's batchedUpdates function internally, so if inconvergent value is followed by convergent one - React will take only last and the correct one But it will be the problem for all other subscribers without such batching mechanism - including other nanostores

    Expected behavior: I think that detecting if Store value is convergent (and that calculations cycle is over) should not be the problem of store consumers - and library must handle these cases internally (like MobX, Effector and Reatom do)

    opened by AlexandrHoroshih 7
  • feat(lifecycle): add `onError`

    feat(lifecycle): add `onError`

    Useful for Devtools: https://devtools.vuejs.org/plugin/api-reference.html#addtimelineevent With onError we can show red dots as errors in Vue Devtools timeline.

    opened by euaaaio 6
  • Type error in create-map/index.d.ts file

    Type error in create-map/index.d.ts file

    The create-map/index.d.ts causes the following error in my repo:

    node_modules/nanostores/create-map/index.d.ts:2:38 - error TS2322: Type 'K' is not assignable to type 'string | number | symbol'.
      Type 'K' is not assignable to type 'symbol'.
    
    2 type Get<T, K> = Extract<T, { [K1 in K]: any }>[K];
    

    The same error I see in TypeScript playground (here).

    opened by davidmz 6
  • Wrong numbers for computed

    Wrong numbers for computed

    Hi! Im trying to add nanostores to my bench and get this error

    Expected "18", received "26"

    image

    Looks like there is glitch somewhere, or I have a mistake in atoms definitions?

    opened by artalar 0
  • feature request: createActions utility

    feature request: createActions utility

    TL:DR

    I made createActions utility like redux-toolkit's createSlice. TypeScript ready with Type Inference.

    Problem

    It takes a long time to create multiple actions.

    There are many boilerplates now. Like next TodoList example.

    export const todoListStore = atom<TodoT[]>([])
    
    export const actions = {
      addTodo: action(todoListStore, 'addTodo', (store, content: string) => {
        const newTodo = {
          id: generateId(),
          content,
          isCompleted: false,
        }
        store.set([...store.get(), newTodo])
      }),
      completeTodo: action(todoListStore, 'completeTodo', (store, id: number, isCompleted: boolean) => {
        const todoList = store.get();
        store.set(todoList.map((todo) =>
          todo.id === id ? { ...todo, isCompleted } : todo
        ));
      }),
      changeTodo: action(todoListStore, 'changeTodo', (store,id: number, content: string) => {
        const todoList = store.get();
        store.set(todoList.map((todo) => (todo.id === id ? { ...todo, content } : todo)));
      }),
      deleteTodo: action(todoListStore, 'deleteTodo', (store, id: number) => {
        store.set(store.get().filter((todo) => todo.id !== id)) 
      })
    }
    

    Did you find the duplicated pattern here?

    {
      [name]: action(store, name, (store, ...args) => {
        const oldState = store.get();
        ...
        store.set(newState)
      })
    }
    

    Solution

    So I made createActions utility like redux-toolkit's createSlice but simple.

    Here it is:

    export const todoListStore = atom<TodoT[]>([]);
    
    // use createActions
    export const actions = createActions(todoListStore,  {
      // just pure old javascript function
      addTodo(old, content: string) {
        if (content.length === 0) return old;
        
        const newTodo = {
          id: generateId(),
          content,
          isCompleted: false,
        };
        return [...old, newTodo]; // return next state
      },
      completeTodo(old, id: TodoT["id"], isCompleted: boolean) {
        return old.map((todo) =>
          todo.id === id ? { ...todo, isCompleted } : todo
        );
      },
      changeTodo(old, id: TodoT["id"], content: string) {
        return old.map((todo) => (todo.id === id ? { ...todo, content } : todo));
      },
      deleteTodo(old, id: TodoT["id"]) {
        return old.filter((todo) => todo.id !== id);
      },
    });
    

    It is Typescript ready, and can inference types well. Thanks to @XiNiha

    image

    Implementation

    It is simple, but just a proof of concept.

    // XiNiHa's work
    type AsAction<T, I extends Record<string, (old: T, ...args: any[]) => T>> = {
      [K in keyof I]: I[K] extends (old: T, ...args: infer A) => T ? (...args: A) => void : never
    }
    
    function createActions<
      T,
      I extends Record<string, (old: T, ...args: any) => T>
    >(store: WritableStore<T>, rawActions: I): AsAction<T, I> {
      return Object.fromEntries(
        Object.entries(rawActions).map(([name, rawAction]) => {
          return [
            name,
            action(store, name, (store, ...args) => {
              const old = store.get();
              store.set(rawAction(old, ...args));
            }),
          ];
        })
      ) as AsAction<T, I>;
    }
    

    It can be imperative, but encapsulate effect in the function...

    function createActions<
      T,
      I extends Record<string, (old: T, ...args: any) => T>
    >(store: WritableStore<T>, rawActions: I): AsAction<T, I> {
      const result: any = {};
      for (const name in rawActions){
        result[name] = action(store, name, (store, ...args) => {
          const old = store.get();
          store.set(rawActions[name](old, ...args));
        });
      }
      return result as AsAction<T, I>;
    }
    

    Limitation

    • We can't use store's methods like setKey and notify.
    • I don't know it can support Map and MapTempltate
    • It can't handle async actions. (now)
    • It is just tested on todoMVC. More tests are needed.
    opened by twinstae 2
  • setKeys method on store

    setKeys method on store

    First off, Thank you guys for this library. I wanted to ask if you can possibly include a setKeys (on the map store) method that internally simply does

    const setKeys = (partialState : Partial<state>)=>{
         store.set({...store.get(),...obj});
    }
    

    or in the map store define this function

      store.setKeys=function(object){
        Object.keys(object).forEach(key=>{
          store.setKey(key,object[key]);
        })
      }
    

    or a function that also handles deeply nested objects (like lodash-es merge method). I can't say how performant or not the second option is, but it leverages the checks done in setKey method. Or if there is a way to create an extension on the store that adds this, kindly point that to me. This will help reduce overall the lines of code one has to write as more often that not, we are partially updating the store , and setKey only does a key at a time.

    opened by shiftlabs1 1
  • Reference error when using without a bundler

    Reference error when using without a bundler

    I'm trying to use nanostores without a bundler (using https://modern-web.dev/docs/dev-server/overview/)

    I'm getting Uncaught ReferenceError: process is not defined (Line https://github.com/nanostores/nanostores/blob/main/atom/index.js#L50)

    This occurs because browsers do not define process in global scope

    opened by blikblum 8
  • Nanostores in SSR

    Nanostores in SSR

    Hi there!

    I saw news on new release, saw allTasks API and decided to research, how nanostores is compatible with SSR

    Loading of Node.js template in codesandbox takes forever for some reason, so i decided to create similiar reproduce with basic one: https://codesandbox.io/s/loving-wilson-4vp4r?file=/src/index.js (see index.js and console)

    Idea is the same:

    1. Request happens
    2. Request-handler task is added to event-loop and starts some logic and web or db requests
    3. Once those web or db requests are over, state is recalculated with new data
    4. State injected in the app, which then rendered to string
    5. Resulting string sent as response

    During all of these operations is important to isolate state that "belongs" to different requests from each other - otherwise data from one request will leak to the other

    Nanostores have two issues there:

    1. States of nanostores are shared between requests, which then leads to data prepared for one request to show in another - in result rendered html-string is not correct.

    2. State of internal allTasks counter is also shared between all requests, which leads to weird bug: responses are not sent until all requests are handled, even if their data is ready.

    Possible solutions:

    1. Many state-managers require creation of new instance of store/stores for every request, like this
    const ssr = async (request) => {
     const stores = initStores()
     
     await stores.startLogic(request)
     
     return renderAppToString(stores)
    }
    

    This will effectively isolate requests from each other, because all of them will have their own instance of stores and all pending operations also will be bounded to this instance Mobx example: https://github.com/vercel/next.js/tree/master/examples/with-mobx-state-tree

    1. Some other state-managers, like effector, allow to create separate instance of app state in special way, allowing to reuse already initialized stores and connections, like this:
    const ssr = async (request) => {
     // all stores and connections already defined somewhere
     const scope = fork()
     
     await allSettled(startLogic, { scope, params: request })
     
     return renderAppToString(scope)
    }
    

    Example: https://github.com/GTOsss/ssr-effector-next-example/blob/effector-react-form-ssr/src/pages/ssr.tsx

    opened by AlexandrHoroshih 13
Owner
Nano Stores
A tiny state manager with many atomic tree-shakable stores
Nano Stores
Secure boilerplate for Electron app based on Vite. TypeScript + Vue/React/Angular/Svelte/Vanilla

Vite Electron Builder Boilerplate Vite+Electron = ?? This is a template for secure electron applications. Written following the latest safety requirem

Alex Kozack 1.5k Dec 1, 2022
Universal rendering for Preact: render JSX and Preact components to HTML.

preact-render-to-string Render JSX and Preact components to an HTML string. Works in Node & the browser, making it useful for universal/isomorphic ren

Preact 506 Nov 26, 2022
⚛️Preact-PWA - a boilerplate to build fast progressive web applications with Preact & Vitejs

⚛️ pheralb/preact-pwa is a boilerplate to build fast progressive web applications with Preact & Vitejs. ?? Demo - Cloudflare Pages. ?? Gett

Pablo Hdez 6 Oct 29, 2022
Remix + Preact for SSR, Preact + Islands + On Demand Compilation for client side interactions.

Welcome to Remix! Remix Docs Development From your terminal: npm run dev This starts your app in development mode, rebuilding assets on file changes.

Jacob Ebey 11 Oct 27, 2022
Starter template for Vite with React (TypeScript). Supports Tailwind with CSS-Modules. Jest and @react/testing-library configured and ready to go. Also ESLint, Prettier, Husky, Commit-lint and Atomic Design for components.

Mocking up web app with Vital(speed) Live Demo Features ⚡️ React 17 ?? TypeScript, of course ?? Jest - unitary testing made easy ?? Tailwind with JIT

Josep Vidal 137 Nov 29, 2022
An "atomic css" style toolkit for React Native

consistencss An "atomic css" style toolkit for React Native, inspired by tailwindcss Full Documentation: https://consistencss.now.sh/ Installation npm

Mateo Silguero 47 Aug 15, 2022
Gatsby-typescript-testing - A project with React GatsbyJS and TypeScript with Testing using Atomic Design

?? Inicio Rápido Clona el repositorio. Una vez creado el repositorio procede a i

Developers Funny 2 May 3, 2022
The instant on-demand Atomic CSS engine

UnoCSS The instant on-demand Atomic CSS engine. ?? I highly recommend reading th

Anthony Fu 7.7k Nov 29, 2022
Small ES6 Native Speed jQuery for Svelte, Vue3, React, Angular, and WEB

sQuery This is a Very Small ES6 Native Speed jQuery for Svelte, Vue3, React, Angular, and WEB. Are you fed up with the modern js frameworks? But you'r

Exis 2 Sep 24, 2022
Micro frontend template for starter using qiankun + Vite + TypeScript + Vue3 + React.js + Svelte 🚀

Micro frontend template for starter using qiankun + Vite + TypeScript + Vue3 + React.js + Svelte ??

Yuga Sun 27 Nov 24, 2022
React Dashboard made with Material UI’s components. Our pro template contains features like TypeScript version, authentication system with Firebase and Auth0 plus many other

Material Kit - React Free React Admin Dashboard made with Material UI's components, React and of course create-react-app to boost your app development

Devias 4.2k Nov 30, 2022
A toolkit for React, Preact, Inferno & vanilla JS apps, React libraries and other npm modules for the web, with no configuration (until you need it)

nwb nwb is a toolkit for: Quick Development with React, Inferno, Preact or vanilla JavaScript Developing: React Apps Preact Apps Inferno Apps Vanilla

Jonny Buchanan 5.5k Nov 21, 2022
⚡️ WebExtension Vite Starter Template with Preact

Preact Webext A Vite powered WebExtension (Chrome, FireFox, etc.) starter template with Preact. Credits This boilerplate is a shameless fork of antfu/

Piyush Suthar 17 Oct 3, 2022
Zero configuration Preact widgets renderer in any host DOM

Preact Habitat A 900 Bytes module for that will make plugging in Preact components and widgets in any CMS or website as fun as lego! Demos Login Widge

Zouhir ⚡️ 496 Nov 21, 2022
Completely static, built with Next.js, Preact, and TailwindCSS

Oxygen Updater website Completely static, built with Next.js, Preact, and TailwindCSS. Detailed setup instructions may follow in a later commit. Getti

Oxygen Updater 2 Mar 6, 2022
Bootstraps a Preact Island project with no configuration

?? Preact Island Starter Bootstraps a Preact Island project with no configuration. Features ?? Multi entry point builds by default. Make all the islan

Marcus Wood 20 Nov 21, 2022
Salvia-kit provides beautiful dashboard templates built with Tailwind CSS for React, Next.js, Vue and Nuxt.js

Salvia-kit provides beautiful dashboard templates built with Tailwind CSS for React, Next.js, Vue and Nuxt.js

salvia-kit 378 Nov 28, 2022
⚡️ Super-fast electron + vite boilerplate. Support React/Vue template.

electron-vite-template Run Setup # clone the project git clone [email protected]:caoxiemeihao/electron-vite-template.git # enter the project directory c

草鞋没号 592 Nov 27, 2022
Functional props composition for UI components (React.js & Vue.js)

Functional props composition for components A 1.5kB library integrating with your favourite UI framework Website | Documentation | Packages | Contribu

Fahad Heylaal 942 Dec 1, 2022