Skip to content

MrWangJustToDo/reactivity-store

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Mar 12, 2025
96258f5 · Mar 12, 2025
Feb 10, 2025
Dec 19, 2024
Mar 12, 2025
Feb 8, 2025
Apr 25, 2023
Mar 18, 2023
Oct 31, 2024
Dec 18, 2024
Mar 17, 2023
Mar 16, 2023
Mar 16, 2023
Feb 8, 2025
Feb 8, 2025
Jun 21, 2023
Dec 18, 2024
Mar 16, 2023

Repository files navigation

RStore

Deploy npm Release

A React state-management, inspired by the Vue and zustand

a React state-management power by Reactive api, which mean you can use Vue Reactive api in React app, any change of the state will make then UI auto update!

Install

# use pnpm
pnpm add reactivity-store

# or use npm/yarn

Example

import { createStore, ref } from "reactivity-store";

// simple reactive store
const useCount = createStore(() => {
  const refValue = ref(0);

  // this is the only valid way to define the change state function (in the `createStore` function)
  const changeRef = (v) => (refValue.value = v);

  return { refValue, changeRef };
});

const App = () => {
  const { ref, change } = useCount((state) => ({
    // the state is a readonly value, so we can't change state here
    // the `ref` value which return from `createStore` will be auto unwrap
    ref: state.refValue,
    change: state.changeRef,
  }));

  return (
    <div>
      <p>{ref}</p>
      <button onClick={() => change(ref + 1)}>add</button>
    </div>
  );
};

createState support middleware

import { createState, withPersist, withActions } from "reactivity-store";

// simple reactive state, but we can't change the state
const useCount = createState(() => {
  return { count: 0 };
});

// simple reactive state with middleware
// the `withPersist` middleware support auto cache the `state` to the `Storage` when the `state` change
const useCount = createState(withPersist(() => ({ count: 0 }), { key: "count" }));

// the `withActions` middleware support define the action for the state
const useCount = createState(withActions(() => ({ count: 0 }), { generateActions: (state) => ({ add: () => state.count++ }) }));
// then you can get the action from the `selector`
const { count, add } = useCount((state) => ({ count: state.count, add: state.add }));

// you can also compose this two middleware

// or you can use the `option` api with full type support, it is very simple to use
const useCount = createState(() => ({ count: 0 }), { withActions: (state) => ({ add: () => state.count++ }), withPersist: "count" });

// the createState have the same usage with createStore
const App = () => {
  const { count, add } = useCount();

  return (
    <div>
      <p>{count}</p>
      <button onClick={add}>add</button>
    </div>
  );
};

// all build in middleware
withPersist; // support auto cache the state to the `Storage`
withActions; // support define the action for the state
withNameSpace; // support define the namespace for the state, and support reduxDevTools in develop mode
withSelectorOptions; // withDeepSelector; // support deep selector / stable selector for the state, can be used for performance optimization

// TODO (maybe won't)
withComputed; // support define the computed value for the state

createStoreWithComponent support lifeCycle

import { createStoreWithComponent, onMounted, onUpdated, ref } from "reactivity-store";

// reactive store with component lifeCycle
const Count = createStoreWithComponent({
  setup: () => {
    const refValue = ref(0);

    const changeRef = (v) => (refValue.value = v);

    onUpdated(() => {
      console.log("component updated");
    });

    onMounted(() => {
      console.log("component mounted");
    });

    return { refValue, changeRef };
  },
});

const App = () => {
  return (
    <div>
      <Count>
        {({ refValue, changeRef }) => (
          <>
            <p>{refValue}</p>
            <button onClick={() => changeRef(refValue + 1)}>add</button>
          </>
        )}
      </Count>
    </div>
  );
};

v0.1.9 update

Pure hook api for reactive state;

import { useReactiveEffect, useReactiveState } from "reactivity-store";

const usePosition = () => {
  // the `state` object will be a reactive object;
  // so every change for this object will cause the component auto update
  // also support a function as the params
  const [state, setState] = useReactiveState({ x: 0, y: 0 });

  // the second value is a `setState` function, this function expect receive a callback function which has the reactiveState as params
  // so we can update the state in the callback function
  const [xPosition, setXPosition] = useReactiveState({ x: 0 });

  useReactiveEffect(() => {
    const listener = (e: MouseEvent) => {
      setState((state) => {
        state.x = e.clientX;
        state.y = e.clientY;
      });
    };

    window.addEventListener("mousemove", listener);

    // same behavior as `useEffect`
    return () => window.removeEventListener("mousemove", listener);
  });

  // when the component mount or the `state.x` has changed, the effect callback will be invoked
  // because of the `xPosition` is a `state` which create by `useReactiveState`, so the change will cause component auto update
  // no need deps for reactive hook
  useReactiveEffect(() => {
    // update the state
    // callback / object
    setXPosition({
      x = state.x;
    });
  });

  return { y: state.y, x: xPosition.x };
};

Subscribe a state change

import { createState } from "reactivity-store";

const useCount = createState(() => ({ count: 0 }), { withActions: (s) => ({ add: () => s.count++ }) });

// you can use subscribe anywhere
const unSubscribe = useCount.subscribe((s) => s.count, callback);

v0.2.4 update

createState support withDeepSelector option

import { createState, withActions, withPersist } from "reactivity-store";

const useCount = createState(
  withActions(
    () => {
      const data = { re: { count: 0 } };

      return data;
    },
    { generateActions: (state) => ({ add: () => state.re.count++, del: () => state.re.count-- }) }
  ),
  {
    // make the selector support deep selector
    /**
     * state is `{a: {b: '1'}}`
     * select is `const re = (state) => state.a;`
     * if `withDeepSelector` is true, when the `re.b` state change, the selector will also be trigger
     * if `withDeepSelector` is false, when the `re.b` state change, the selector will not be trigger
     *
     * the default value for the `withDeepSelector` is true
     */
    withDeepSelector: true;
  }
);

const App = () => {
  // the `withDeepSelector` option is true, the selector will be trigger when the `re.count` state change, so the component will update normally
  const { re, add } = useCount((state) => ({ re: state.re, add: state.add }));

  return (
    <div>
      <p>React Reactive Count</p>
      <p>{re.count}</p>
      <button onClick={add}>Add</button>
    </div>
  );
};

const useCount_2 = createState(
  withActions(
    () => {
      const data = { re: { count: 0 } };

      return data;
    },
    { generateActions: (state) => ({ add: () => state.re.count++, del: () => state.re.count-- }) }
  ),
  {
    withDeepSelector: false;
  }
);

const App = () => {
  //the `withDeepSelector` option is false, the selector will not be trigger when the `re.count` state change, so the component will not update
  const { re, add } = useCount_2((state) => ({ re: state.re, add: state.add }));

  return (
    <div>
      <p>React Reactive Count</p>
      <p>{re.count}</p>
      <button onClick={add}>Add</button>
    </div>
  );
};

v0.2.6 update

createState support withNameSpace option for reduxDevTools in develop mode

import { createState, withActions, withNameSpace } from "reactivity-store";

const useCount = createState(
  withActions(
    withNameSpace(
      () => {
        const data = { re: { count: 0 } };

        return data;
      },
      {
        namespace: "useCount",
        reduxDevTool: true,
      }
    ),
    { generateActions: (state) => ({ add: () => state.re.count++, del: () => state.re.count-- }) }
  )
);

or

import { createState } from "reactivity-store";

const useCount = createState(
  () => {
    const data = { re: { count: 0 } };

    return data;
  },
  {
    withNamespace: "useCount",
    withActions: (state) => ({ add: () => state.re.count++, del: () => state.re.count-- }),
  }
);

v0.3.5 update

withDeepSelector change to withSelectorOptions, also add stableSelector config

License

MIT