npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

hookstore

v0.2.5

Published

Hook based and lightweight centralized state management for React.

Downloads

13

Readme

English | 简体中文

hookstore

NPM version Build Status Coverage Status NPM downloads size React

Hook based and lightweight centralized state management for React.

Try it on codesandbox

Edit

Features

  • Minimal(only 5 APIs) and simplified API: easy to learn(5 minutes about), no reducer, dispatch or effects in Redux/dva, only action which is just normal function with async/await support.
  • Centralized state management and multiple models: model is just normal javascript object, state inside action(and middleware)is mutatable for understanding while immutatable inside components(follow the design of React's unidirectional data flow).
  • Performance optimized: useStore api is inspired by react-redux-hooks useSelector, the preformance issue caused by useContext is controlled, only component(s) which used useStore hook will be re-rendered when state changed.
  • Built-in action status listening: Listening pending and error status of (async)action and update to DOM in real time when action's status change.
  • koa style middleware system

Install

$ npm install hookstore -S
# or
$ yarn add hookstore

Usage

Please check out all examples in the examples folder.

1. model(s) definition

// src/models/count.js
export default {
  name: 'count',
  state: {
    count: 0,
  },
  actions: {
    add(n) {
      const { state } = this.ctx;

      state.count += n;
    },
    async asyncAdd(n) {
      const { state } = this.ctx;

      await new Promise(resolve => {
        setTimeout(resolve, 1000);
      });

      state.count += n;
    },
    addx(n) {
      const { state, actions } = this.ctx;

      state.count += n;

      actions.asyncAdd(n);
      // await actions.asyncAdd(n); // use async/await can access asyncAdd() response
    },
  },
}

2. model(s) initialization

import { Provider } from 'hookstore';

import countModel from './models/count';
import listModel from './models/list';

import Counter from './src/components/Counter';

ReactDOM.render(
  <Provider models={[ countModel ]}>
    <Counter />
    <Counter />
  </Provider>
  , document.getElementById('root')
);

3. Access state and actions in child components

// src/components/Counter.js
import { useStore } from 'hookstore';

export default () => {
  const [ count, actions ] = useStore('count', s => s.count);
  return (
    <div>
      {Math.random()}
      <div>
        <div>Count: {count}</div>
        <button onClick={() => actions.add(1)}>add 1</button>
        <button onClick={() => actions.addx(1)}>add 1 and async add 1</button>
      </div>
    </div>
  );
}

API

<Provider models>

The Provider has to be put in the parent component, and maybe Provider as root component is recommended.

const Root = () => (
  <Provider models={[ model1, model2 ]}>
    ...
  </Provider>
);

ReactDOM.render(<Root />, document.getElementById('root'));

useStore(name, selector?: Function, equalityFn?: Function) => [ selectedState, actions ]

useStore integrated the useSelector and useDispatch() Apis in react-redux v7.x, it returns the latest state in store and the collect of actions methods(which can safely modify store's state) by tuples.

  • name: The name of model
  • selector: Pure function with which can extract data from store, the default value is (state) => state which will returns the state object in store.It's highly recommended to pass in selector to select state in need, components will re-render only if the selected keys changes.
  • equalityFn: the default value is the same as connect of react-redux, optional comparison function also enables using something like Lodash's _.isEqual().
const Component = () => {
  const [ name, actions ] = useStore('foo', s => s.name);
  const [ nested, actions ] = useStore('bar', s => s.nested, _.isEqual);
  // ...
};

useStatus(name/action) => { pending: boolean, error: Error }

useStatus hook listens the execution status of (asynchronous)actions in real time, and all components used useStatus hook will receive the actions status update and then render to the DOM.

// src/components/CounterWithLoading.js
import { useStore, useStatus } from 'hookstore';

const CounterWithLoading = () => {
  const [ { count }, actions ] = useStore('count', s => c.count);
  const { pending, error } = useStatus('count/asyncAdd');
  const asyncAdd = () => {
    if (pending) return console.log('pls wait...');
    actions.asyncAdd(5);
  };

  return (
    <div>
      {Math.random()}
      <div>
        { pending && <div>loading...<div> }
        { error && <div>{error.message}<div> }
        <div>count: {count}</div>
        <button onClick={asyncAdd}>async add 5</button>
      </div>
    </div>
  );
};

getStore(name, selector?: Function) => [ selectedState, actions ]

The params and returns of getStore is the same as useStore, the difference is that getStore is not a React Hook, it;'s just a normal function, so the usage of getStore is not restricted by Hook Rules(maybe outside of React components), but you should known that useStore can not listen the changes of state, you should call useStore again to get the lastest state.

// models/foo.js
import { getStore } from 'hookstore';

export default {
  name: 'foo',
  actions: {
    const [ , barActions ] = getStore('bar'); // access actions from `bar` model
    // ...
  }
}

applyMiddlewares([middleware1, middleware2, ...])

Appling koa-style middleware to all of the actions.

import { Provider, applyMiddlewares } from 'hookstore';
import errorMiddleware from 'hookstore-error';
import loggerMiddleware from 'hookstore-logger';

import countModel from './models/count';
import listModel from './models/list';

import Counter from './src/components/Counter';
import List from './src/components/List';

function Root() {
  useEffect(() => {
    // if (/localhost|\btest\b/.test(location.hostname)) {
    applyMiddlewares([ errorMiddleware(), loggerMiddleware({ showDiff: true }) ]);
    // }
  }, []);

  return (
    <Provider models={[ countModel, listModel ]}>
      <h2>Counter</h2>
      <Counter />
      <Counter />
      <h2>List</h2>
      <List />
    </Provider>
  );
}

ReactDOM.render(<Root />, document.getElementById('root'));

custom middleware:

// middlewares/errHandler.js
export default async (ctx, next) => {
  try {
    await next();
  } catch(e) {
    console.error(`${ctx.name}/${ctx.action}`, e);
  }
}

// use middleware
import errHandler from 'errHandler';

function Root() {
  applyMiddlewares([errHandler]);

  return (
    <Privider model={model}>
      // ...
    </Privider>
  );
}

model(s) definition

model is just normal javascript object:

interface Model {
  readonly name: string, // name of model
  state?: {}, // model state
  actions: {
    [action: string]: ({this: {ctx: Context}}) => any | Promise<any>
  },
}

example:

// src/models/foo.js
export default {
  name: 'foo', // model name
  actions: {
    setName(newName) {
      this.ctx.state.name = newName;
    },
    async asyncSetName(newName) {
      await new Promise(resolve => setTimeout(resolve, 1000));
      this.ctx.state.name = newName;
    }
  },
}

The ctx

The ctx store some intermediate states and methods which can only access inside of actions and middlewares.

Type definition:

interface Actions {
  [ action: string ]: (...args: any[]) => Promise<any>;
}

interface Context<S = {}> {
  // access current store's name
  readonly name: string,
  // access current action's name
  readonly action: string,
  // access the lastest state in current store
  state: S,
  // access the bound action collection of current store
  actions: Actions,
  // access the lastest state and actions of some other store
  getStore: (name?: string, selector?: StateSelector<S>) => [ any, Actions ],
}

Run examples locally

The examples folder contains working examples. You can run one of them with

$ cd examples/[folder] && npm start

then open http://localhost:3000 in your web browser.

License

MIT