Skip to content
Dead simple global store using hooks for react
TypeScript HTML Other
Branch: master
Clone or download
richarddd Upgrade deps (#10)
* Upgrade depedencies
Latest commit 3158315 Aug 10, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
example
lib
src Fix unmount error on localStore (#7) May 5, 2019
test
.babelrc
.gitignore
.npmignore
.travis.yml
README.md
package.json
tests-setup.js Add tests (#9) Aug 7, 2019
tsconfig.json
tslint.json
typescript.png
yarn.lock

README.md

global-hook-store

build

Dead simple global store using hooks for react.

Demo

TL;DR

Counter: https://codesandbox.io/s/j2v0p6kq7w

Todo list: https://codesandbox.io/s/54kwqpppnx

Installation

// yarn
yarn add global-hook-store

// npm
npm -i global-hook-store --save

Usage

Its super super simple to use. Only three steps required.

  1. Create your store with initial state & reducer style functions 👇
const counterStore = createStore(
  {
    count: 0
  },
  {
    increment: ({ count }) => ({ count: count + 1 }),
    decrement: ({ count }) => ({ count: count - 1 })
  }
);
  1. Use store 👌
const App = () => {
  const { actions, state } = useStore(counterStore);

  return (
    <>
      <h1>Count {state.count}</h1>
      <button onClick={() => actions.decrement()}>-</button>
      <button onClick={() => actions.increment()}>+</button>
    </>
  );
};
  1. Profit 🎉

The reducer style functions are converted to actions which could be called from react

Example with payload

Todo list:

const todoStore = createStore({} as Todo, {
  // payload is here:  👇
  toggleTodo: (todos, todo:string) => {
    todos[todo] = !todos[todo];
    return { ...todos };
  },
  // payload is here:👇
  addTodo: (todos, input:HTMLInputElement) => {
    const todo = input.value;
    input.value = "";
    return { ...todos, [todo]: false };
  }
});
const ToDoList = () => {
  const { state, actions } = useStore(todoStore);
  const ref = useRef(null);

  return (
    <div>
      <h3>Todo list example</h3>
      {Object.entries(state).map(([todo, done], i) => (
        // passed payload:                      👇
        <div onClick={() => actions.toggleTodo(todo)} key={i + todo}>
          {todo}
          {done ? "" : ""}
        </div>
      ))}
      <input ref={ref} />
      <button onClick={() => actions.addTodo(ref.current!)}>Add todo</button>
    </div>
  );
};

Nice to have

Typescript support

When using typescript actions, state and payload types are infered: Infered Types

If you dont need any payload, simply don't specify it in the reducer or set the payload type as null or undefined and the mapped actions could be executed like this:

const fooBarStore = createStore(
  {
    fooBar: 0
  },
  {
    setFooBar: (_state, payload:number) => ({ fooBar: payload }),
    incrementFooBar: ({fooBar}) => ({ fooBar: fooBar + 1 })
    decrementFooBar: ({fooBar},payload:null) => ({ fooBar: fooBar - 1 })
  }
)
fooBarStore.actions.setFooBar(10);
fooBarStore.actions.incrementFooBar();
fooBarStore.actions.decrementFooBar();

fooBarStore.actions.setFooBar(); // error, because we are missing required payload

Async actions work out of the box:

const counterStore = createStore(
  {
    count: 0
  },
  {
    increment: ({ count }) => ({ count: count + 1 }),
    decrement: ({ count }) => ({ count: count - 1 }),
    incrementByTen: async ({ count }) => {
      const promise = new Promise(resolve => setTimeout(resolve, 3000));
      await promise;
      return { count: count + 10 };
    }
  }
);

Also use this handy util for automatically setting loading, error and data state:

const githubStore = createStore(
  {
    // async initialicer:👇 (will create a typed object like this { loading: false, data: [], error: undefined})
    repos: asyncState<Repo[]>([]),
    userId: ""
  },
  {
    setRepoId: (state, userId: string) => ({ ...state, userId }),
      // util function:                              👇
    getUserRepos: async ({ userId }, _payload, { asyncAction }) =>
      asyncAction("repos", githubApi.getRepo(userId))
  }
);

Getters and setters are preserved:

const nameAndCounterStore = createStore(
  {
    count: 0,
    name: "Willy wonka",
    get length() {
      return this.name.length;
    }
  },
  {
    increment: ({ count, ...state }) => ({ ...state, count: count + 1 }),
    decrement: ({ count, ...state }) => ({ ...state, count: count - 1 }),
    updateName: (state, name) => ({ ...state, name })
  }
);
const {
  state: { length },
  actions
} = useStore(nameAndCounterStore);
<span>{length}</span>

Use the same style local store also

const App = () => {
  const { actions, state } = useLocalStore(counterStore);

  return (
    <>
      <h1>Count {state.count}</h1>
      <button onClick={() => actions.decrement()}>-</button>
      <button onClick={() => actions.increment()}>+</button>
    </>
  );
};

Reset util

Use this util to reset the entire state to initial state or just a part of the state

const githubStore = createStore(
  {
    repos: asyncState<Repo[]>([]),
    userId: ""
  },
  {
    resetAll: (_state, _payload: null, { reset }) => reset(),
    resetRepos: (_state, _payload: null, { reset }) => reset("repos") // also typed
  }
);

Also available as a reset hook when componet unmounts. This is very practical for when having a "current" item present in the store. If you have a userStore for instance you might want the currentUser to be cleared when the user-details component is unmounted. useStoreReset will reset the stores key to its initial state when component is unmounted

useStoreReset(userStore, "currentUser");
useStoreReset(store, "stateKey1","stateKey2", "stateKey3"...)
You can’t perform that action at this time.