Comparing version 0.6.0-next.0 to 0.6.0-next.1
@@ -10,4 +10,6 @@ { | ||
"lazy-buckets-accept", | ||
"orange-pillows-smash" | ||
"orange-pillows-smash", | ||
"smooth-points-remember", | ||
"spotty-planets-begin" | ||
] | ||
} |
# Changelog | ||
## 0.6.0-next.1 | ||
### Minor Changes | ||
- 75d0a40: Simplify types. | ||
- da27eba: `set` now takes a second argument `forceNotify`; when set to true, all updated properties will be notified, regardless of referential equality to the previous value. | ||
## 0.6.0-next.0 | ||
@@ -4,0 +11,0 @@ |
/** | ||
* The state objects managed by Statery stores are any JavaScript objects that | ||
* can be indexed using strings and/or numbers. | ||
* The state objects managed by Statery stores are any string-indexed JavaScript objects. | ||
*/ | ||
export declare type State = Record<string | number, any>; | ||
export declare type State = Record<string, any>; | ||
/** | ||
@@ -30,3 +29,3 @@ * Statery stores wrap around a State object and provide a few functions to update them | ||
*/ | ||
set: (updates: Partial<T> | StateUpdateFunction<T>) => T; | ||
set: (updates: Partial<T> | StateUpdateFunction<T>, forceNotify?: boolean) => T; | ||
/** | ||
@@ -53,3 +52,3 @@ * Subscribe to changes to the store's state. Every time the store is updated, the provided | ||
* | ||
* @param state The state object that will be wrapped by the store. | ||
* @param initialState The state object that will be wrapped by the store. | ||
*/ | ||
@@ -56,0 +55,0 @@ export declare const makeStore: <T extends State>(initialState: T) => Store<T>; |
@@ -1,2 +0,2 @@ | ||
import{useState as e,useLayoutEffect as t,useRef as n}from"react";const r=e=>{let t=e;const n=new Set;return{get state(){return t},set:e=>{const r=(e=>Object.keys(e).reduce(((n,r)=>(e[r]!==t[r]&&(n[r]=e[r]),n)),{}))(e instanceof Function?e(t):e);if(Object.keys(r).length>0){const e=t;t=Object.assign(Object.assign({},t),r);for(const t of n)t(r,e)}return t},subscribe:e=>{n.add(e)},unsubscribe:e=>{n.delete(e)}}},s=n=>{const[,r]=e(0),s=c((()=>new Set));return t((()=>{const e=e=>{Object.keys(e).find((e=>s.has(e)))&&r((e=>e+1))};return n.subscribe(e),()=>{n.unsubscribe(e)}}),[n]),new Proxy({},{get:(e,t)=>(s.add(t),n.state[t])})},c=e=>{const t=n(null);return t.current||(t.current=e()),t.current};export{r as makeStore,s as useStore}; | ||
import{useState as e,useLayoutEffect as t,useRef as n}from"react";const r=e=>{let t=e;const n=new Set;return{get state(){return t},set:(e,r=!1)=>{const s=e instanceof Function?e(t):e,c=r?s:(e=>Object.keys(e).reduce(((n,r)=>(e[r]!==t[r]&&(n[r]=e[r]),n)),{}))(s);if(Object.keys(c).length>0){const e=t;t=Object.assign(Object.assign({},t),c);for(const t of n)t(c,e)}return t},subscribe:e=>{n.add(e)},unsubscribe:e=>{n.delete(e)}}},s=n=>{const[,r]=e(0),s=c((()=>new Set));return t((()=>{const e=e=>{Object.keys(e).find((e=>s.has(e)))&&r((e=>e+1))};return n.subscribe(e),()=>{n.unsubscribe(e)}}),[n]),new Proxy({},{get:(e,t)=>(s.add(t),n.state[t])})},c=e=>{const t=n(null);return t.current||(t.current=e()),t.current};export{r as makeStore,s as useStore}; | ||
//# sourceMappingURL=index.esm.js.map |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");const t=t=>{const s=e.useRef(null);return s.current||(s.current=t()),s.current};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){const e=t;t=Object.assign(Object.assign({},t),r);for(const t of s)t(r,e)}return t},subscribe:e=>{s.add(e)},unsubscribe:e=>{s.delete(e)}}},exports.useStore=s=>{const[,r]=e.useState(0),n=t((()=>new Set));return e.useLayoutEffect((()=>{const e=e=>{Object.keys(e).find((e=>n.has(e)))&&r((e=>e+1))};return s.subscribe(e),()=>{s.unsubscribe(e)}}),[s]),new Proxy({},{get:(e,t)=>(n.add(t),s.state[t])})}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");const t=t=>{const s=e.useRef(null);return s.current||(s.current=t()),s.current};exports.makeStore=e=>{let t=e;const s=new Set;return{get state(){return t},set:(e,r=!1)=>{const n=e instanceof Function?e(t):e,c=r?n:(e=>Object.keys(e).reduce(((s,r)=>(e[r]!==t[r]&&(s[r]=e[r]),s)),{}))(n);if(Object.keys(c).length>0){const e=t;t=Object.assign(Object.assign({},t),c);for(const t of s)t(c,e)}return t},subscribe:e=>{s.add(e)},unsubscribe:e=>{s.delete(e)}}},exports.useStore=s=>{const[,r]=e.useState(0),n=t((()=>new Set));return e.useLayoutEffect((()=>{const e=e=>{Object.keys(e).find((e=>n.has(e)))&&r((e=>e+1))};return s.subscribe(e),()=>{s.unsubscribe(e)}}),[s]),new Proxy({},{get:(e,t)=>(n.add(t),s.state[t])})}; | ||
//# sourceMappingURL=index.js.map |
@@ -17,3 +17,3 @@ { | ||
"sideEffects": false, | ||
"version": "0.6.0-next.0", | ||
"version": "0.6.0-next.1", | ||
"main": "dist/index.js", | ||
@@ -20,0 +20,0 @@ "module": "dist/index.esm.js", |
@@ -37,4 +37,3 @@ [![Version](https://img.shields.io/npm/v/statery)](https://www.npmjs.com/package/statery) | ||
```ts | ||
import * as React from "react" | ||
```tsx | ||
import { makeStore, useStore } from "statery" | ||
@@ -200,2 +199,24 @@ | ||
### Forcing a store update | ||
When the store is updated, Statery will check which of the properties within the update object are actually different objects (or scalar values) from the previous state, and will only notify listeners to those properties. | ||
In some cases, you may want to force a store update even though the property has not changed to a new object. For these situations, the `set` function allows you to pass a second argument; if this is set to `true`, Statery will ignore the equality check and notify all listeners to the properties included in the update. | ||
Example: | ||
```tsx | ||
const store = makeStore({ | ||
rotation: new THREE.Vector3() | ||
}) | ||
export const randomizeRotation = () => | ||
store.set( | ||
(state) => ({ | ||
rotation: state.rotation.randomRotation() | ||
}), | ||
true | ||
) | ||
``` | ||
### Subscribing to updates (imperatively) | ||
@@ -246,4 +267,2 @@ | ||
- [ ] No support for middleware yet. Haven't decided on an API that is adequately simple. | ||
- [ ] I have yet to try how Statery behaves in React's upcoming Concurrent Mode. It does work fine within React's StrictMode, though, so chances are it'll be okay. | ||
- [ ] Probably other bits and pieces I haven't even encountered yet. | ||
@@ -250,0 +269,0 @@ ### Prior Art & Credits |
@@ -17,6 +17,5 @@ import { useLayoutEffect, useRef, useState } from "react" | ||
/** | ||
* The state objects managed by Statery stores are any JavaScript objects that | ||
* can be indexed using strings and/or numbers. | ||
* The state objects managed by Statery stores are any string-indexed JavaScript objects. | ||
*/ | ||
export type State = Record<string | number, any> | ||
export type State = Record<string, any> | ||
@@ -48,3 +47,3 @@ /** | ||
*/ | ||
set: (updates: Partial<T> | StateUpdateFunction<T>) => T | ||
set: (updates: Partial<T> | StateUpdateFunction<T>, forceNotify?: boolean) => T | ||
@@ -89,3 +88,3 @@ /** | ||
* | ||
* @param state The state object that will be wrapped by the store. | ||
* @param initialState The state object that will be wrapped by the store. | ||
*/ | ||
@@ -107,6 +106,15 @@ export const makeStore = <T extends State>(initialState: T): Store<T> => { | ||
set: (incoming) => { | ||
set: (incoming, forceNotify = false) => { | ||
/* If the argument is a function, run it */ | ||
const updates = getActualChanges(incoming instanceof Function ? incoming(state) : incoming) | ||
const incomingState = incoming instanceof Function ? incoming(state) : incoming | ||
/* | ||
Check which updates we're actually applying. If forceNotify is enabled, | ||
we'll use (and notify for) all of them; otherwise, we'll check them against | ||
the current state to only change (and notify for) the properties | ||
that have changed from the current state. | ||
*/ | ||
const updates = forceNotify ? incomingState : getActualChanges(incomingState) | ||
/* Has anything changed? */ | ||
if (Object.keys(updates).length > 0) { | ||
@@ -113,0 +121,0 @@ /* Keep a reference to the previous state, we're going to need it in a second */ |
import { makeStore } from "../src" | ||
describe("makeStore", () => { | ||
const store = makeStore({ | ||
const init = () => ({ | ||
foo: 0, | ||
@@ -10,4 +10,6 @@ bar: 0, | ||
const store = makeStore(init()) | ||
beforeEach(() => { | ||
store.set({ foo: 0, bar: 0 }) | ||
store.set(init) | ||
}) | ||
@@ -121,5 +123,18 @@ | ||
it("receives all changes made to the store if the `force` flag is set", () => { | ||
const listener = jest.fn() | ||
store.subscribe(listener) | ||
/* We're setting both foo and bar; only foo is actually a new value. */ | ||
store.set({ foo: 1, bar: 0 }, true) | ||
/* Since we've forced the update, the changes now include the un-changed `bar`, as well */ | ||
expect(listener.mock.calls[0][0]).toEqual({ foo: 1, bar: 0 }) | ||
store.unsubscribe(listener) | ||
}) | ||
it("already makes the updated state available to listeners", () => { | ||
let newValue: number | ||
let prevValue: number | ||
let newValue: number | undefined = undefined | ||
let prevValue: number | undefined = undefined | ||
@@ -126,0 +141,0 @@ const listener = (_, prevState) => { |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
230688
29
608
276
0