Skip to content

Some small changes #35

@JakeCoxon

Description

@JakeCoxon

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);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions