pure-store
pure-store copied to clipboard
Some small changes
Hi I've been using a fork of pure-store in production. Pure-store is a great little utility, however a few changes will make it much better. Let me know if you are interested in them.
Firstly you want to be able to hook into primitive values and only cause a render when that specific field changes
const store = createStore({ foo: 23, bar: 51 });
const Thing = () => {
const [foo,] = store.usePureStore(x => x.foo);
return <>{foo}</>
}
usePureStore works in this way but it's not properly correct. The second return value is an updater function which doesn't work for primitive values. If you accidently try to use it you will get an error.
My proposal is to restrict the return value of getter
usePureStore<X extends object>( // X extends object means you must return an object
getter?: (s: T) => X
): readonly [X, (updater: Partial<X> | ((e: X) => void)) => void];
And add an additional hook called useValue, that does let you return a primitive value and only returns this value without the updater function
useValue(): T;
useValue<X>(getter?: (s: T) => X): X;
useValue(getter?: any) {
return (this.usePureStore(getter) as any)[0];
}
This is a must-have because it lets you do derived values as well. In this example the component only renders when foo > 50
changes. Not everytime foo changes
const foo = store.useValue(x => x.foo > 50);
The second change is that subscribe should not run the callback if the value did not change
const store = createStore({ foo: 23, bar: 51 });
store
.storeFor(x => x.foo) // only foo
.subscribe(() => console.log("change"))
store.update({ bar: 23 }); // change is logged even though foo didn't change
The fix is just check the identity between the old value and new value. I also added the value to the parameter of the callback which is super useful.
subscribe = (callback: (s: T) => void) => {
let oldValue: T = this.getState();
const getterCallback = (s: S) => {
const newValue = this.getter(s);
if (newValue === oldValue) return;
oldValue = newValue;
callback(newValue);
};
this.root.callbacks.push(getterCallback);
return () => {
const index = this.root.callbacks.indexOf(getterCallback);
this.root.callbacks.splice(index, 1);
};
};
The third change is not as important but somewhat useful. I just add a watch function (and equivalent react hook) that is the same as subscribe but it also calls your callback immediately. It's useful in situations where you're trying to sync two things together. (Could be called sync or some other variation)
watch = (callback: (s: T) => void) => {
callback(this.getState());
return this.subscribe(callback);
};
// eg
store.storeFor(x => x.foo).watch(foo => {
div.current.style.background = foo;
})
The final change is that storeFor
must return the same type that was instantiated with. Currently storeFor always returns a PureStore object which does not have React hooks on it. Its actually quite useful to be able to pass sub-stores around and hook into them. Personally I only use the pure store with React but I understand why you want to split them
const Thing = ({ store }) => {
store.useValue(x => x.foo) // store does not have the react methods on it
}
<Thing store={store.storeFor(x => x.someSubStore)) />
The fix is to override storeFor in PureStoreReact
storeFor = <X>(getter: (s: T) => X): PureStoreReact<T, X> => new PureStoreReact(this, getter);
Hey @JakeCoxon , I believe Arthur is not maintaining this package anymore.
I will review and take your issue into consideration on the updated fork I will maintain: https://github.com/zacharytyhacz/pure-store-updated#readme