Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

nanostores

Package Overview
Dependencies
Maintainers
1
Versions
50
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nanostores - npm Package Compare versions

Comparing version 0.3.3 to 0.4.0

effect/index.d.ts

2

clean-stores/index.d.ts

@@ -21,3 +21,3 @@ import { MapBuilder, AnySyncBuilder } from '../define-map/index.js'

export function cleanStores(
...stores: (ReadableStore | MapBuilder | AnySyncBuilder)[]
...stores: (ReadableStore | MapBuilder | AnySyncBuilder | undefined)[]
): void

@@ -10,5 +10,7 @@ export const clean = Symbol('clean')

for (let store of stores) {
if (store.mocked) delete store.mocked
store[clean]()
if (store) {
if (store.mocked) delete store.mocked
store[clean]()
}
}
}
import { clean } from '../clean-stores/index.js'
export function createMap(init) {
let listeners = []
let currentListeners
let nextListeners = []
let destroy

@@ -37,3 +38,4 @@

notify(changedKey) {
for (let listener of listeners) {
currentListeners = nextListeners
for (let listener of currentListeners) {
listener(store.value, changedKey)

@@ -55,9 +57,15 @@ }

}
listeners.push(listener)
if (nextListeners === currentListeners) {
nextListeners = nextListeners.slice()
}
nextListeners.push(listener)
return () => {
let index = listeners.indexOf(listener)
listeners.splice(index, 1)
if (!listeners.length) {
if (nextListeners === currentListeners) {
nextListeners = nextListeners.slice()
}
let index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
if (!nextListeners.length) {
setTimeout(() => {
if (store.active && !listeners.length) {
if (store.active && !nextListeners.length) {
if (destroy) destroy()

@@ -78,3 +86,3 @@ store.active = false

store.value = undefined
listeners = []
nextListeners = []
destroy = undefined

@@ -81,0 +89,0 @@ }

import { clean } from '../clean-stores/index.js'
export function createStore(init) {
let listeners = []
let currentListeners
let nextListeners = []
let destroy

@@ -10,3 +11,4 @@

store.value = newValue
for (let listener of listeners) {
currentListeners = nextListeners
for (let listener of currentListeners) {
listener(store.value)

@@ -27,9 +29,15 @@ }

}
listeners.push(listener)
if (nextListeners === currentListeners) {
nextListeners = nextListeners.slice()
}
nextListeners.push(listener)
return () => {
let index = listeners.indexOf(listener)
listeners.splice(index, 1)
if (!listeners.length) {
if (nextListeners === currentListeners) {
nextListeners = nextListeners.slice()
}
let index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
if (!nextListeners.length) {
setTimeout(() => {
if (store.active && !listeners.length) {
if (store.active && !nextListeners.length) {
if (destroy) destroy()

@@ -50,3 +58,3 @@ destroy = undefined

store.value = undefined
listeners = []
nextListeners = []
destroy = undefined

@@ -53,0 +61,0 @@ }

export {
createRouter,
RouteParams,
getPagePath,
openPage,
redirectPage,
Router,
Page
} from './create-router/index.js'
export {
BuilderValue,

@@ -22,7 +13,8 @@ BuilderStore,

} from './create-store/index.js'
export { startEffect, effect, allEffects } from './effect/index.js'
export { createMap, MapStore } from './create-map/index.js'
export { clean, cleanStores } from './clean-stores/index.js'
export { createPersistent } from './create-persistent/index.js'
export { update, updateKey } from './update/index.js'
export { createDerived } from './create-derived/index.js'
export { keepActive } from './keep-active/index.js'
export { getValue } from './get-value/index.js'

@@ -1,9 +0,4 @@

export {
createRouter,
openPage,
redirectPage,
getPagePath
} from './create-router/index.js'
export { startEffect, effect, allEffects } from './effect/index.js'
export { clean, cleanStores } from './clean-stores/index.js'
export { createPersistent } from './create-persistent/index.js'
export { update, updateKey } from './update/index.js'
export { createDerived } from './create-derived/index.js'

@@ -10,0 +5,0 @@ export { createStore } from './create-store/index.js'

{
"name": "nanostores",
"version": "0.3.3",
"description": "A tiny (152 bytes) state manager for React/Preact/Vue/Svelte with many atomic tree-shakable stores",
"version": "0.4.0",
"description": "A tiny (172 bytes) state manager for React/Preact/Vue/Svelte with many atomic tree-shakable stores",
"keywords": [
"logux",
"nano",
"state",
"state manager",
"react",
"react native",
"preact",

@@ -16,4 +18,3 @@ "vue",

"license": "MIT",
"homepage": "https://logux.io/",
"repository": "ai/nanostores",
"repository": "nanostores/nanostores",
"sideEffects": false,

@@ -29,2 +30,5 @@ "type": "module",

},
"react-native": {
"./react/batch/index.js": "./react/batch/index.native.js"
},
"engines": {

@@ -31,0 +35,0 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"

@@ -1,6 +0,8 @@

import { unstable_batchedUpdates } from 'react-dom'
import React from 'react'
import { getValue } from '../get-value/index.js'
import { batch } from './batch/index.js'
export { batch }
export function useStore(store) {

@@ -20,3 +22,3 @@ let [, forceRender] = React.useState({})

let unbind = store.listen(() => {
unstable_batchedUpdates(() => {
batch(() => {
forceRender({})

@@ -23,0 +25,0 @@ })

# Nano Stores
<img align="right" width="95" height="148" title="Logux logotype"
src="https://logux.io/branding/logotype.svg">
<img align="right" width="92" height="92" title="Nano Stores logo"
src="https://nanostores.github.io/nanostores/logo.svg">
A tiny state manager for **React**, **Preact**, **Vue** and **Svelte**.
It uses **many atomic stores** and direct manipulation.
A tiny state manager for **React**, **React Native**, **Preact**, **Vue**,
**Svelte**, and vanilla JS. It uses **many atomic stores**
and direct manipulation.
* **Small.** 152 bytes (minified and gzipped). Zero dependencies.
It uses [Size Limit] to control size.
* **Small.** between 172 and 527 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.
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. Already has **router**
and **persistent** stores.
* Was designed to move logic from components to stores.
* It has good **TypeScript** support.

@@ -21,3 +21,3 @@

// store/users.ts
import { createStore, getValue } from 'nanostores'
import { createStore, update } from 'nanostores'

@@ -29,3 +29,3 @@ export const users = createStore<User[]>(() => {

export function addUser(user: User) {
users.set([...getValue(users), user])
update(users, current => [...current, user])
}

@@ -61,6 +61,3 @@ ```

It is part of [Logux] project but can be used without any other Logux parts.
<a href="https://evilmartians.com/?utm_source=logux-client">
<a href="https://evilmartians.com/?utm_source=nanostores">
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg"

@@ -70,6 +67,19 @@ alt="Sponsored by Evil Martians" width="236" height="54">

[Size Limit]: https://github.com/ai/size-limit
[Logux]: https://logux.io/
[Size Limit]: https://github.com/ai/size-limit
## Table of Contents
* [Tools](#tools)
* [Guide](#guide)
* Integration
* [React & Preact](#react--preact)
* [Next.js](#nextjs)
* [Vue](#vue)
* [Svelte](#svelte)
* [Vanilla JS](#vanilla-js)
* [Tests](#tests)
* [Best Practices](#best-practices)
* [Known Issues](#known-issues)
## Install

@@ -83,4 +93,5 @@

* [Persistent](#persistent) store to save data to `localStorage`.
* [Router](#router) store.
* [Persistent](https://github.com/nanostores/persistent) store to save data
to `localStorage` and synchronize changes between browser tabs.
* [Router](https://github.com/nanostores/router) store.
* [Logux Client](https://github.com/logux/client): stores with WebSocket

@@ -90,8 +101,8 @@ sync and CRDT conflict resolution.

## Stores
## Guide
In Nano Stores, stores are **smart**. They subscribe to events,
validate input, send AJAX requests, etc. For instance,
build-in [Router](#Router) store subscribes to click on `<a>`
and `window.onpopstate`. It simplifies testing and switching
[Router](https://github.com/nanostores/router) store subscribes to click
on `<a>` and `window.onpopstate`. It simplifies testing and switching
between UI frameworks (like from React to React Native).

@@ -131,3 +142,3 @@

By we have shortcut to subscribe, return value and unsubscribe:
We have shortcut to subscribe, return value and unsubscribe:

@@ -140,3 +151,11 @@ ```ts

And there is shortcut to get current value, change it and set new value.
```ts
import { update } from 'nanostores'
update(store, value => newValue)
```
### Simple Store

@@ -147,3 +166,3 @@

```ts
import { createStore, getValue } from 'nanostores'
import { createStore, update } from 'nanostores'

@@ -155,3 +174,3 @@ export const counter = createStore<number>(() => {

export function increaseCounter() {
counter.set(getValue(counter) + 1)
update(counter, value => value + 1)
}

@@ -162,6 +181,19 @@ ```

All async operations in store you need to wrap to `effect` (or use `startEffect`).
It will help to wait async operations end in tests.
```ts
import { effect } from 'nanostore'
export function saveUser() {
effect(async () => {
await api.saveUser(getValue(userStore))
})
}
```
### Map Store
This store with key-value pairs.
This store is with key-value pairs.

@@ -182,3 +214,4 @@ ```ts

In additional to `store.set(newObject)` it has `store.setKey(key, value)`
to change specific key.
to change specific key. There is a special shortcut
`updateKey(store, key, updater)` in additional to `update(store, updater)`.

@@ -227,3 +260,3 @@ Changes listener receives changed key as a second argument.

A template to create a similar store. Each store made by the template
is map store with at least the `id` key.
is a map store with at least the `id` key.

@@ -270,80 +303,2 @@ ```ts

## 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.
```ts
import { createStore } from 'nanostores'
export const currentTime = createStore<number>(() => {
currentTime.set(Date.now())
const updating = setInterval(() => {
currentTime.set(Date.now())
}, 1000)
return () => {
clearInterval(updating)
}
})
```
Use derived stores to create chains of reactive computations.
```ts
import { createDerived } from 'nanostores'
import { currentTime } from './currentTime.js'
const appStarted = Date.now()
export const userInApp = createDerived(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’s much easy to write and run tests.
It is also easy to change your UI framework. For instance, add React Native
version of the application.
### Think about Tree Shaking
We recommend doing all store changes in separated functions. It will allow
to tree shake unused functions from JS bundle.
```ts
export function changeStore (newValue: string) {
if (validate(newValue)) {
throw new Error('New value is not valid')
} else {
store.set(newValue)
}
}
```
For builder, you can add properties to the store, but try to avoid it.
```ts
interface UserExt {
avatarCache?: string
}
export function User = defineMap<UserValue, [], UserExt>((store, id) => {
})
function getAvatar (user: BuilderStore<typeof User>) {
if (!user.avatarCache) {
user.avatarCache = generateAvatar(getValue(user).email)
}
return user.avatarCache
}
```
## Integration

@@ -363,8 +318,9 @@

export const Header = () => {
const profile = useStore(profile)
const currentUser = useStore(User(profile.userId))
return <header>${currentUser.name}<header>
const { userId } = useStore(profile)
const currentUser = useStore(User(userId))
return <header>{currentUser.name}<header>
}
```
### Vue

@@ -388,4 +344,4 @@

setup () {
const profile = useStore(profile)
const currentUser = useStore(User(profile.value.userId))
const { userId } = useStore(profile).value
const currentUser = useStore(User(userId))
return { currentUser }

@@ -397,2 +353,3 @@ }

### Svelte

@@ -410,4 +367,4 @@

const profile = useStore(profile)
const currentUser = useStore(User(profile.userId))
const { userId } = useStore(profile)
const currentUser = useStore(User(userId))
</script>

@@ -419,2 +376,26 @@

### Vanilla JS
`Store#subscribe()` calls callback immediately and subscribes to store changes.
It passes store’s value to callback.
```js
let prevUserUnbind
profile.subscribe(({ userId }) => {
// Re-subscribe on current user ID changes
if (prevUserUnbind) {
// Remove old user listener
prevUserUnbind()
}
// Add new user listener
prevUserUnbind = User(userId).subscribe(currentUser => {
console.log(currentUser.name)
})
})
```
Use `Store#listen()` if you need to add listener without calling
callback immediately.
### Tests

@@ -441,72 +422,175 @@

You can use `allEffects()` to wait all async options in stores.
## Build-in Stores
```ts
import { getValue, allEffects } from 'nanostores'
### Persistent
it('saves user', async () => {
saveUser()
await allEffects()
expect(getValue(analyticsEvents)).toEqual(['user:save'])
})
```
You can create a store to keep value with some prefix in `localStorage`.
## 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.
```ts
import { createPersistent } from 'nanostores'
import { createStore } from 'nanostores'
export interface CartValue {
list: string[]
}
export const currentTime = createStore<number>(() => {
currentTime.set(Date.now())
const updating = setInterval(() => {
currentTime.set(Date.now())
}, 1000)
return () => {
clearInterval(updating)
}
})
```
export const shoppingCart = createPersistent<CartValue>({ list: [] }, 'cart')
Use derived stores to create chains of reactive computations.
```ts
import { createDerived } from 'nanostores'
import { currentTime } from './currentTime.js'
const appStarted = Date.now()
export const userInApp = createDerived(currentTime, now => {
return now - appStarted
})
```
This store also listen for keys changes in `localStorage` and can be used
to synchronize changes between browser tabs.
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.
### Router
Since we promote moving logic to store, the router is a good part
of the application to be moved from UI framework like React.
### Think about Tree Shaking
We recommend doing all store changes in separated functions. It will allow
to tree shake unused functions from JS bundle.
```ts
import { createRouter } from 'nanostores'
export function changeStore (newValue: string) {
if (validate(newValue)) {
throw new Error('New value is not valid')
} else {
store.set(newValue)
}
}
```
// Types for :params in route templates
interface Routes {
home: void
category: 'categoryId'
post: 'categoryId' | 'id'
For builder, you can add properties to the store, but try to avoid it.
```ts
interface UserExt {
avatarCache?: string
}
export const router = createRouter<Routes>({
home: '/',
category: '/posts/:categoryId',
post: '/posts/:categoryId/:id'
export function User = defineMap<UserValue, [], UserExt>((store, id) => {
})
function getAvatar (user: BuilderStore<typeof User>) {
if (!user.avatarCache) {
user.avatarCache = generateAvatar(getValue(user).email)
}
return user.avatarCache
}
```
Store in active mode listen for `<a>` clicks on `document.body` and Back button
in browser.
### Separate changes and reaction
You can use `getPagePath()` to avoid hard coding URL to a template. It is better
to use the router as a single place of truth.
Use a separated listener to react on new store’s value, not a function where you
change this store.
```tsx
import { getPagePath } from 'nanostores'
```diff
function increase () {
update(counter, value => value + 1)
- printCounter(getValue(counter))
}
<a href={getPagePath(router, 'post', { categoryId: 'guides', id: '10' })}>
+ counter.subscribe(value => {
+ printCounter(value)
+ })
```
If you need to change URL programmatically you can use `openPage`
or `replacePage`:
A "change" function is not only a way for store to a get new value.
For instance, persistent store could get the new value from another browser tab.
```ts
import { openPage, replacePage } from 'nanostores'
With this separation your UI will be ready to any source of store’s changes.
function requireLogin () {
openPage(router, 'login')
}
function onLoginSuccess() {
// Replace login route, so we don’t face it on back navigation
replacePage(router, 'home')
}
### Reduce `getValue()` usage outside of tests
`getValue()` 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.
```diff
- const { userId } = getValue(profile)
+ const { userId } = useStore(profile)
```
In store’s functions you can use `update` and `updateKey` shortcuts:
```diff
function increase () {
- counter.set(getValue(counter) + 1)
+ update(counter, value => value + 1)
}
```
## Known Issues
### Diamond Problem
To make stores simple and small, Nano Stores doesn’t solve “Diamond problem”.
```
A
F←B→C
↓ ↓
↓ D
↓ ↓
G→H←E
```
On `A` store changes, `H` store will be called twice in different time
by change signals coming from different branches.
You need to care about these changes on your own.
### ESM
Nano Stores use ES modules and doesn’t provide CommonJS exports.
You need to use ES modules in your application to import Nano Stores.
For instance, for Next.js you need to use [`next-transpile-modules`] to fix
lack of ESM support in Next.js.
```js
// next.config.js
const withTM = require('next-transpile-modules')(['nanostores'])
module.exports = withTM({
/* previous configuration goes here */
})
```
[`next-transpile-modules`]: https://www.npmjs.com/package/next-transpile-modules

@@ -5,4 +5,2 @@ import { DeepReadonly, Ref } from 'vue'

type ReadonlyRef<Type> = DeepReadonly<Ref<Type>>
/**

@@ -22,5 +20,7 @@ * Subscribe to store changes and get store’s value.

*
* export default () => {
* let page = useStore(router)
* return { page }
* export default {
* setup () {
* let page = useStore(router)
* return { page }
* }
* }

@@ -35,2 +35,2 @@ * </script>

store: ReadableStore<Value>
): ReadonlyRef<Value>
): DeepReadonly<Ref<Type>>

@@ -10,4 +10,3 @@ import {

export function useStore(store) {
let state = ref(null)
let readonlyState = readonly(state)
let state = ref()
let unsubscribe

@@ -31,3 +30,6 @@

return readonlyState
if (process.env.NODE_ENV !== 'production') {
return readonly(state)
}
return state
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc