Comparing version 0.4.0 to 0.5.1
@@ -8,2 +8,8 @@ # Changelog | ||
## [Unreleased] | ||
### [0.5.0] - - 2020-12-18 | ||
- **Minor Breaking Change:** The `set` function will now filter incoming updates and discard any that don't actually change the value that is currently in the store. Listeners will only receive the _actual_ changes to a store, and components will only re-render when a watched store property has actually changed to a new value. This change was made to allow for easier [integration with libraries like Immer](https://codesandbox.io/s/statery-immer-vr9b2?file=/src/App.tsx:592-783) that produce a complete new version of the store. | ||
## [0.4.0] - 2020-12-17 | ||
@@ -10,0 +16,0 @@ |
@@ -10,3 +10,3 @@ /** | ||
*/ | ||
export declare type Store<T extends State> = { | ||
export declare type Store<T extends State = State> = { | ||
/** | ||
@@ -13,0 +13,0 @@ * Return the current state. |
@@ -1,2 +0,2 @@ | ||
import{useState as e,useRef as t,useEffect as r}from"react";const s=e=>{let t=e,r=new Array;return{get state(){return t},set:e=>{e=e instanceof Function?e(t):e;for(const s of r)s(e,t);return t=Object.assign(Object.assign({},t),e),t},subscribe:e=>{r.push(e)},unsubscribe:e=>{r=r.filter((t=>t!==e))}}},n=s=>{const[,n]=e(0),c=t(new Set).current;return r((()=>{const e=e=>{Object.keys(e).find((e=>c.has(e)))&&n((e=>e+1))};return s.subscribe(e),()=>{s.unsubscribe(e)}}),[s]),new Proxy({},{get:(e,t)=>(c.add(t),s.state[t])})};export{s as makeStore,n as useStore}; | ||
import{useState as e,useRef as t,useEffect as s}from"react";const n=e=>{let t=e;const s=new Set;return{get state(){return t},set:e=>{const n=(e=>Object.keys(e).reduce(((s,n)=>(e[n]!==t[n]&&(s[n]=e[n]),s)),{}))(e instanceof Function?e(t):e);if(Object.keys(n).length>0){for(const e of s)e(n,t);t=Object.assign(Object.assign({},t),n)}return t},subscribe:e=>{s.add(e)},unsubscribe:e=>{s.delete(e)}}},r=n=>{const[,r]=e(0),c=t(new Set).current;return s((()=>{const e=e=>{Object.keys(e).find((e=>c.has(e)))&&r((e=>e+1))};return n.subscribe(e),()=>{n.unsubscribe(e)}}),[n]),new Proxy({},{get:(e,t)=>(c.add(t),n.state[t])})};export{n as makeStore,r as useStore}; | ||
//# sourceMappingURL=index.esm.js.map |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");exports.makeStore=e=>{let t=e,r=new Array;return{get state(){return t},set:e=>{e=e instanceof Function?e(t):e;for(const s of r)s(e,t);return t=Object.assign(Object.assign({},t),e),t},subscribe:e=>{r.push(e)},unsubscribe:e=>{r=r.filter((t=>t!==e))}}},exports.useStore=t=>{const[,r]=e.useState(0),s=e.useRef(new Set).current;return e.useEffect((()=>{const e=e=>{Object.keys(e).find((e=>s.has(e)))&&r((e=>e+1))};return t.subscribe(e),()=>{t.unsubscribe(e)}}),[t]),new Proxy({},{get:(e,r)=>(s.add(r),t.state[r])})}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");exports.makeStore=e=>{let t=e;const s=new Set;return{get state(){return t},set:e=>{const r=(e=>Object.keys(e).reduce(((s,r)=>(e[r]!==t[r]&&(s[r]=e[r]),s)),{}))(e instanceof Function?e(t):e);if(Object.keys(r).length>0){for(const e of s)e(r,t);t=Object.assign(Object.assign({},t),r)}return t},subscribe:e=>{s.add(e)},unsubscribe:e=>{s.delete(e)}}},exports.useStore=t=>{const[,s]=e.useState(0),r=e.useRef(new Set).current;return e.useEffect((()=>{const e=e=>{Object.keys(e).find((e=>r.has(e)))&&s((e=>e+1))};return t.subscribe(e),()=>{t.unsubscribe(e)}}),[t]),new Proxy({},{get:(e,s)=>(r.add(s),t.state[s])})}; | ||
//# sourceMappingURL=index.js.map |
@@ -16,3 +16,3 @@ { | ||
], | ||
"version": "0.4.0", | ||
"version": "0.5.1", | ||
"main": "dist/index.js", | ||
@@ -19,0 +19,0 @@ "module": "dist/index.esm.js", |
@@ -1,2 +0,1 @@ | ||
![Status](https://img.shields.io/badge/status-experimental-orange) | ||
[![Version](https://img.shields.io/npm/v/statery)](https://www.npmjs.com/package/statery) | ||
@@ -36,2 +35,31 @@ [![CI](https://github.com/hmans/statery/workflows/CI/badge.svg)](https://github.com/hmans/statery/actions?query=workflow%3ACI) | ||
## SUMMARY | ||
```ts | ||
import * as React from "react" | ||
import { makeStore, useStore } from "statery" | ||
/* Make a store */ | ||
const store = makeStore({ counter: 0 }) | ||
/* Write some code that updates it */ | ||
const increment = () => | ||
store.set((state) => ({ | ||
counter: state.counter + 1 | ||
})) | ||
const Counter = () => { | ||
/* Fetch data from the store (and automatically re-render | ||
when it changes!) */ | ||
const { counter } = useStore(store) | ||
return ( | ||
<div> | ||
<p>Counter: {counter}</p> | ||
<button onClick={increment}>Increment</button> | ||
</div> | ||
) | ||
} | ||
``` | ||
## BASIC USAGE | ||
@@ -53,3 +81,3 @@ | ||
Statery stores wrap around plain old JavaScript objects: | ||
Statery stores wrap around plain old JavaScript objects that are free to contain any kind of data: | ||
@@ -60,3 +88,9 @@ ```ts | ||
const store = makeStore({ | ||
wood: 8, | ||
player: { | ||
id: 1, | ||
name: "John Doe", | ||
email: "john@doe.com" | ||
}, | ||
gold: 100, | ||
wood: 0, | ||
houses: 0 | ||
@@ -92,3 +126,3 @@ }) | ||
Updates will be shallow-merged with the current state, meaning that properties you don't update will not be touched. | ||
Updates will be shallow-merged with the current state, meaning that top-level properties will be replaced, and properties you don't update will not be touched. | ||
@@ -109,3 +143,3 @@ ### Reading from a Store (with React) | ||
Naturally, your components will **re-render automatically** when the data they've accessed changes. | ||
When any of the data your components access changes in the store, they will automatically re-render. | ||
@@ -112,0 +146,0 @@ ### Reading from a Store (without React) |
@@ -26,3 +26,3 @@ import { useEffect, useRef, useState } from "react" | ||
*/ | ||
export type Store<T extends State> = { | ||
export type Store<T extends State = State> = { | ||
/** | ||
@@ -92,4 +92,10 @@ * Return the current state. | ||
let state = initialState | ||
let listeners = new Array<Listener<T>>() | ||
const listeners = new Set<Listener<T>>() | ||
const getActualChanges = (updates: Partial<T>) => | ||
Object.keys(updates).reduce<Partial<T>>((changes, prop: keyof Partial<T>) => { | ||
if (updates[prop] !== state[prop]) changes[prop] = updates[prop] | ||
return changes | ||
}, {}) | ||
return { | ||
@@ -100,11 +106,13 @@ get state() { | ||
set: (updates) => { | ||
/* Get new properties */ | ||
updates = updates instanceof Function ? updates(state) : updates | ||
set: (incoming) => { | ||
/* If the argument is a function, run it */ | ||
const updates = getActualChanges(incoming instanceof Function ? incoming(state) : incoming) | ||
/* Execute listeners */ | ||
for (const listener of listeners) listener(updates, state) | ||
if (Object.keys(updates).length > 0) { | ||
/* Execute listeners */ | ||
for (const listener of listeners) listener(updates, state) | ||
/* Apply updates */ | ||
state = { ...state, ...updates } | ||
/* Apply updates */ | ||
state = { ...state, ...updates } | ||
} | ||
@@ -115,7 +123,7 @@ return state | ||
subscribe: (listener) => { | ||
listeners.push(listener) | ||
listeners.add(listener) | ||
}, | ||
unsubscribe: (listener) => { | ||
listeners = listeners.filter((l) => l !== listener) | ||
listeners.delete(listener) | ||
} | ||
@@ -122,0 +130,0 @@ } |
@@ -49,2 +49,14 @@ import { makeStore } from "../src" | ||
}) | ||
it("if no new values are applied, the state stays untouched", () => { | ||
const oldState = store.state | ||
store.set({ foo: 0 }) | ||
expect(store.state).toBe(oldState) | ||
}) | ||
it("if something actually changes, the state become a new object", () => { | ||
const oldState = store.state | ||
store.set({ foo: 1 }) | ||
expect(store.state).not.toBe(oldState) | ||
}) | ||
}) | ||
@@ -51,0 +63,0 @@ |
Sorry, the diff of this file is not supported yet
199737
507
257
19