Skip to content

1111mp/simple-store

Repository files navigation

simple-store

Manage your state in the easiest way for React.

Installation

npm run install @the1111mp/simple-store
# Or
yarn add @the1111mp/simple-store

Example

You can see the code: simple-store-example

Or

Edit

Quick Start

Basic usage

Create a userStore through the createStore method:

// user.store.ts
import { createStore } from "@the1111mp/simple-store";

type UseStore = { name: string; age: number };

// directly assign the initialized value
export const [userStore, resetStore] = createStore<UseStore>({
  name: "Tom",
  age: 18,
});

// Or

// Use a function to get the initialized value (sync)
export const [userStore, resetStore] = createStore<UseStore>(() => {
  // you can do something

  return {
    name: "Tom",
    age: 18,
  };
});

// Or

// Use a function to get the initialized value (async)
// Remember this will triggers an update
export const [userStore, resetStore] = createStore<UseStore>(
  { name: "", age: 0 },
  async () => {
    // you can do something
    const { name, age } = await fetch("/user");

    return {
      name,
      age,
    };
  }
);

// App.tsx
import { userStore } from "user.store";

export const App: React.FC = () => {
  const [{ name, age }, updateUserStore] = userStore();

  return (
    <div>
      App:
      <br />
      name: <span>{name}</span>
      <br />
      age: <span>{age}</span>
      <br />
      <button
        onClick={() => {
          updateUserStore((store) => {
            return { name: "Jim", age: 16 };
          });
        }}
      >
        update
      </button>
    </div>
  );
};

Custom hooks

// user.store.ts
import { createStore } from "@the1111mp/simple-store";

type UseStore = { name: string; age: number };

const [userStore] = createStore<UseStore>({
  name: "Tom",
  age: 18,
});

export function useUserStore() {
  const [{ name, age }, updateUserStore] = userStore();

  const updateName = (name: string) => {
    updateUserStore((store) => ({ ...store, name }));
  };

  return {
    name,
    age,
    updateName,
  };
}

// App.tsx
import { useUserStore } from "user.store";

export const App: React.FC = () => {
  const { name, age, updateName } = useUserStore();

  return (
    <div>
      App:
      <br />
      name: <span>{name}</span>
      <br />
      age: <span>{age}</span>
      <br />
      <button
        onClick={() => {
          updateName("Jim");
        }}
      >
        update
      </button>
    </div>
  );
};

It is recommended to use custom hooks to split and manage your Store.

Performance optimization

With the depsFn function, you can control the state you want to subscribe to to avoid unwanted updates. This is similar to the deps parameter of React's useMemo or useEffect.

import { createStore } from "@the1111mp/simple-store";

type UseStore = { name: string; age: number };

const [userStore] = createStore<UseStore>({
  name: "Tom",
  age: 18,
});

export function useUserNameStore() {
  // depsFn
  const [{ name }, updateUserStore] = userStore((store) => [store.name]);

  const updateName = (name: string) => {
    updateUserStore((store) => ({ ...store, name }));
  };

  return {
    name,
    updateName,
  };
}

How to use it in class components

Cannot pass userStore directly to withStore.

// user.store.ts
import { createStore } from "@the1111mp/simple-store";

type UseStore = { name: string; age: number };

const [userStore] = createStore<UseStore>({
  name: "Tom",
  age: 18,
});

export function useUserStore() {
  const [{ name, age }, updateUserStore] = userStore();

  const updateName = (name: string) => {
    updateUserStore((store) => ({ ...store, name }));
  };

  return {
    name,
    age,
    updateName,
  };
}

// App.tsx
import { Component } from "react";
import { withStore } from "@the1111mp/simple-store";
import { useUserStore } from "user.store";

type Props = ReturnType<typeof useUserStore> & {};

class App extends Component<Props> {
  render(): React.ReactNode {
    const { name, age, updateName } = this.props;

    return (
      <div>
        App
        <br />
        name: {name}
        <br />
        age: {age}
        <br />
        <button
          onClick={() => {
            updateName("Jim");
          }}
        >
          update
        </button>
      </div>
    );
  }
}

export default withStore(useUserStore, (userStore) => ({
  ...userStore,
}))(App);

Dependencies between stores

Single store:

// weather.store.ts
import { createStore } from "@the1111mp/simple-store";

export type WeatherStore = {
  weather: string;
  temperature: number;
};

export const [weatherStore] = createStore<WeatherStore>({
  weather: "sunny",
  temperature: 28,
});

export function useWeatherNameStore() {
  const [{ weather }, updateStore] = weatherStore((store) => [store.weather]);

  const updateWeather = (weather: string) => {
    updateStore((store) => {
      return {
        ...store,
        weather,
      };
    });
  };

  return {
    weather,
    updateWeather,
  };
}

export function useTempStore() {
  const [{ temperature }, updateStore] = weatherStore((store) => [
    store.temperature,
  ]);

  const updateTemperature = (temperature: number) => {
    updateStore((store) => {
      return {
        ...store,
        temperature,
      };
    });
  };

  return {
    temperature,
    updateTemperature,
  };
}

export function useWeatherStore() {
  const { weather, updateWeather } = useWeatherNameStore();
  const { temperature, updateTemperature } = useTempStore();

  return {
    weather,
    temperature,
    updateWeather,
    updateTemperature,
  };
}

// Test.tsx
import { Component } from "react";
import { withStore } from "@the1111mp/simple-store";
import { useWeatherStore } from "./weather.store";

type Props = ReturnType<typeof useWeatherStore> & {};

class Test extends Component<Props> {
  render(): React.ReactNode {
    console.log("rerender from Test");
    const { weather, temperature, updateWeather, updateTemperature } =
      this.props;
    return (
      <div>
        Test
        <br />
        weather: {weather}
        <br />
        temperature: {temperature} 摄氏度
        <br />
        <button
          onClick={() => {
            updateWeather("hot");
          }}
        >
          update weather
        </button>
        <br />
        <button
          onClick={() => {
            updateTemperature(30);
          }}
        >
          update temperature
        </button>
      </div>
    );
  }
}

export default withStore(useWeatherStore, (weatherStore) => ({
  ...weatherStore,
}))(Test);

Multiple Stores:

// user.store.ts
import { createStore } from "@the1111mp/simple-store";

type UseStore = { name: string; age: number };

export const [userStore] = createStore<UseStore>({
  name: "Tom",
  age: 18,
});

export function useUsertore() {
  // depsFn
  const [{ name, age }, updateStore] = userStore();

  const updateUserStore = (user: Partial<UseStore>) => {
    updateStore((store) => ({ ...store, ...user }));
  };

  return {
    name,
    age,
    updateUserStore,
  };
}

// weather.store.ts
import { createStore } from "@the1111mp/simple-store";
import { useUserStore } from "./user.store";

export type WeatherStore = {
  weather: string;
  temperature: number;
};

export const [weatherStore] = createStore<WeatherStore>({
  weather: "sunny",
  temperature: 28,
});

export function useInfoStore() {
  const { name, age, updateUserStore } = useUserStore();
  const [{ weather, temperature }, updateWeatherStore] = weatherStore();

  const updateWeatherStore = (weather: Partial<WeatherStore>) => {
    updateStore((store) => {
      return {
        ...store,
        ...weather,
      };
    });
  };

  return {
    name,
    age,
    weather,
    temperature,
    updateUserStore
    updateWeatherStore,
  };
}

Read only

In some scenarios, we only want to read the current value of a model, without subscribing to its updates.

useStore.store:

import { createStore } from "@the1111mp/simple-store";

type UseStore = { name: string; age: number };

export const [userStore] = createStore<UseStore>({
  name: "Tom",
  age: 18,
});

// will not subscribe to updates
export function useInfoStore() {
  const { name, age } = userStore.store;

  return {
    name,
    age,
  };
}

API

createStore

type Store = Record<string, unknown>;
type ResetStore = (notify?: boolean) => void;

export declare function createStore<T extends Store>(
  initial: T,
  initialFn?: (store: T) => Promise<T>
): [UseStore<T>, ResetStore];
export declare function createStore<T extends Store>(
  initial: () => T,
  initialFn?: (store: T) => Promise<T>
): [UseStore<T>, ResetStore];

Create a Store.

UseStore

type UpdateStoreNormal<T> = (store: T) => T;
type UpdateStoreWithPromise<T> = (store: T) => Promise<T>;
type UpdateStore<T> = (
  val: T | UpdateStoreNormal<T> | UpdateStoreWithPromise<T>
) => void;
type DepFn<T> = (store: T) => unknown[];

type UseStore<T> = {
  (depsFn?: DepFn<T>): [T, UpdateStore<T>];
  store?: T;
};

Call the return value of the createStore function.

resetStore

Reset your store data.

type ResetStore = (notify?: boolean) => void;

The param of notify default is true, reset store data then notify to trigger once update.

withStore

export declare function withStore<TStoreProps, TOwnProps, T>(
  useStore: UseStore<T>,
  mapModelToProps: MapModelToProps<TStoreProps, TOwnProps, T>
): InferableComponentEnhancerWithProps<TStoreProps, TOwnProps>;
export declare function withStore<TStoreProps, TOwnProps, Model>(
  useStores: UseStore<any>[],
  mapModelToProps: MapModelToProps<TStoreProps, TOwnProps, any[]>
): InferableComponentEnhancerWithProps<TStoreProps, TOwnProps>;

Used to link store and class components, similar to connect of react-redux.