/hooks-store

☔️Lightweight React state management library based on React Hooks.

Primary LanguageTypeScriptMIT LicenseMIT

English | 简体中文

hooks-store

Lightweight React state management library based on React Hooks.

NPM version Package Quality build status NPM downloads Known Vulnerabilities David deps

🕹 CodeSandbox demos 🕹
Counter Todos

Introduction

hooks-store is a lightweight React state management library based on hooks. It has the following core features:

  • Minimal & Familiar API: No additional learning costs, easy to get started with the knowledge of React Hooks.
  • Centralization: Easy to initialize data and support hooks interaction.
  • Readonly API: Supports read-only state without subscribing to updates.
  • Great Compatibility: Class Component Support && Perfect TypeScript Support.

Basic example

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { createStore } from '@ice/hooks-store';

// 1️⃣ Create a custom hook as usual
function useCounter() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);

  return {
    count,
    increment,
  };
}

const hooks = {
  useCounter,
};

// 2️⃣ Create the store
const store = createStore(hooks);

// 3️⃣ Consume hooks
const { useHooks } = store;
function Button() {
  const { increment } = useHooks('useCounter');
  return (
    <button type="button" onClick={increment}> + </button>
  );
}
function Count() {
  const { count } = useHooks('useCounter');
  return (<span>{count}</span>);
}

// 4️⃣ Wrap your components with Provider
const { Provider } = store;
function App() {
  return (
    <Provider>
      <Count />
      <Button />
    </Provider>
  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

Installation

@ice/hooks-store requires React 16.8.0 or later.

npm install @ice/hooks-store --save

Advanced Usages

Readonly

In some scenarios, you may only want to call the method returned by the hooks to update the state instead of subscribing to the update of the hooks state. For example, the button component in the "Basic example", you do not consume the state of the hooks in the component, so you may not expect the change of the state of the hooks to trigger the re-render of the component.

At this time, you can use the getHooks API, check following example and compare them with the above example:

const { getHooks } = store;
function Button() {
  function handleIncrement() {
    getHooks('useCounter').increment();
  }
  return (
    <button type="button" onClick={handleIncrement}> + </button>
  );
}

Hooks Interaction

In some scenarios, you might expect a state change of Hooks A to trigger a state update of Hooks B. We call this behavior as "Hooks Interaction".

For example:

  • We have a useTodos Hooks that records all tasks.
  • We have a useUser Hooks, in which there is a todos field, which records the number of tasks owned by the current user.
  • Whenever the todos Hooks's tasks changes, the number of tasks held by users needs to be kept in sync.

State subscription

import { useEffect, useState } from 'react';
import produce from 'immer';
import '@/store';

function useUser() {
  const [state, setState] = useState({ todos: 0 });
  const [todos] = store.useHooks('useTodos');

  useEffect(() => {
    setState(produce((draft) => {
      draft.todos = todos.length;
    }));
  }, [ todos ]);

  return [state, setState];
}

Method Call

import { useState } from 'react';
import produce from 'immer';
import '@/store';

function useTodos() {
  const [state, setState] = useState([
    {
      name: 'angular',
    },
  ]);

  function setTodos(todos) {
    setState(todos);

    const [, setUser] = store.getHooks('useUser');
    setUser(produce((draft) => {
      draft.todos = todos.length;
    }));
  }
 
  return [state, { setTodos }];
}

Class Component Support

import { Component } from 'react';
import store from '@/store';
import useTodos from '@/hooks/useTodos';

const { withHooks } = store;

interface MapHooksToProp {
  useTodos: ReturnType<typeof useTodos>; // This field is automatically added by withHooks
}

interface CustomProp {
  title: string; // User defined props
}

type Props = CustomProp & MapHooksToProp;

class Todos extends Component<Props> {
  render() {
    const { title, useTodos } = this.props;
    const [ state, actions ] = useTodos;
    return (
      <div>
        {
          state.map(({ name }, index) => {
            return (<div key={index}>
              {name}
              <button onClick={() => actions.remove(index)}>
                Remove
              </button>
            </div>);
          })
        }
      </div>
    );
  }
}

export default withHooks('useTodos')<MapHooksToProp, Props>(Todos);

Browser Compatibility

Chrome Firefox Edge IE Safari Opera UC
9+ ✔

Contributors

Feel free to report any questions as an issue, we'd love to have your helping hand on @ice/hooks-store.

If you're interested in @ice/hooks-store, see CONTRIBUTING.md for more information to learn how to get started.

Community

DingTalk community GitHub issues Gitter
issues gitter

License

MIT