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

react-stores

v6.0.0

Published

React stores

Downloads

428

Readme

react-stores

react-stores

build status npm bundlephobia minzip npm version with types devtool extension

Shared states for React.

How to install

npm i react-stores --save

Demo

Online demo

For local demo clone this repo and run the script below inside the dir, then go to http://localhost:9000 in your browser

npm i && npm run demo

Tests

npm run test

How to use

Here you are a few examples

Create a Store

// myStore.ts
import { Store } from 'react-stores';

export interface IMyStoreState {
  counter: number;
}

export const myStore = new Store<IMyStoreState>({
  counter: 0, // initial state values
});

React useHooks

import React, { FC } from 'react';
import { useStore } from 'react-stores';
import { myStore } from './myStore';

export const Component: FC = () => {
  const myStoreState = useStore(myStore);

  return (
    <div>
      {myStoreState.counter}
    <div/>
  );
}

// Invoke this code somewhere outside Component and it will be re-rendered
myStore.setState({
  counter: 2,
});

Look here 👉more about use hooks

Event-driven component

// EventDrivenComponent.tsx
import React from 'react';
import { StoreEvent, StoreEventType } from 'react-stores';
import { myStore, IMyStoreState } from './myStore';

interface State {
  myStoreState: IMyStoreState;
}

export class EventDrivenComponent extends React.Component<any, State> {
  private storeEvent: StoreEvent<IMyStoreState> = null;

  state: State = {
    myStoreState: myStore.state,
  };

  comonentDidMount() {
    // Add store state event binder
    this.storeEvent = myStore.on(
      StoreEventType.All,
      (storeState: IMyStoreState, prevState: IMyStoreState, type: StoreEventType) => {
        this.setState({
          myStoreState: storeState,
        });
      },
    );
  }

  componentDidUnmount() {
    // Remove store state event binder
    this.storeEvent.remove();
  }

  render() {
    return <p>Counter: {this.state.myStoreState.counter.toString()}</p>;
  }
}

Component with followStore decorator

// FollowStoreComponent.tsx
import React from 'react';
import { followStore } from 'react-stores';
import { myStore } from './myStore';

// You can use multiple follows
// @followStore(myStore)
// @followStore(myOtherStore)
@followStore(myStore)
export class CounterDecorator extends React.Component {
  public render() {
    return <p>Counter: {myStore.state.counter.toString()}</p>;
  }
}

Advanced Component Hooks

import React from 'react';
import { useStore } from 'react-stores';
import { myStore, IMyStoreState } from './myStore';

interface IMappedState {
  counter: string;
}

interface IProps {
  index: number;
}

function recursiveFibonacci(num: number) {
  if (num <= 1) {
    return 1;
  }
  return recursiveFibonacci(num - 1) + recursiveFibonacci(num - 2);
}

export const MyHookComponent: React.FC<IProps> = (props: IProps) => {
  // Memoize your mapState function
  const mapState = React.useCallback(
    (state: IMyStoreState): IMappedState => ({
      counter: recursiveFibonacci(state.counter), // Very long operation
    }),
    [props.index],
  );

  // Get your state form store
  const { counter } = useStore<IMyStoreState, IMappedState>(myStore, mapState);

  return <p>Counter: {counter}</p>;
};

Mutating store state

import { myStore } from './myStore';

myStore.setState({
  counter: 9999,
});

Read store state value

import { myStore } from './myStore';

console.log(myStore.state.counter); // 9999

useIsolatedStore

import React, { FC } from 'react';
import { useIsolatedStore } from 'react-stores';

interface IMyStoreState {
  counter: number;
}

const initialState = {
  counter: 0,
};

export const Component: FC<{ name: sting }> = ({ name }) => {
  const myStore = useIsolatedStore<IMyStoreState>(initialState, {
    persistence: true,
    immutable: true,
    name,
  });

  const handleIncrement = React.useCallback(() => {
    myStore.setState({
      counter: myStore.state.counter + 1,
    });
  }, [myStore.state.counter]);

  return <button onClick={handleIncrement}>{myStore.state.counter}</button>;
};

API

Store

Store constructor

| Argument | Type | Optional | Description | | ------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | initialState | StoreState | No | Initial state values object | | options | StoreOptions | Yes | Setup store as you need with immutable, persistence and etс. | | persistenceDriver | StorePersistentDriver<StoreState> | Yes | StorePersistentDriver<StoreState> class instance |

Example

StoreState

This can be any interface describes your store's state.

initialState

Any object corresponding to StoreState interface.

StoreOptions

| Property | Type | Default | Optional | Description | | ----------------- | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | immutable | boolean | false | Yes | Object.freeze(...) for store state instances, when disabled you have fully mutable states, but increased performance, for more see | | persistence | boolean | false | Yes | Enables persistent mode using LocalStorage persistence of custom StorePersistentDriver. If you want use two persistence stores with identical interface you must set name props. In other case both of stores will be use one key in storage. | | name | string | null | Yes | Uses for name in storage if persistence flag is true. If name not defined name for persistent will be created from initialState interface. | | setStateTimeout | number | 0 | Yes | Store state updates with timeout |

Store methods

Check demo here.

| Method name | Arguments | Returns | Description | | ------------------ | ------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------ | | setState | newState: Partial<StoreState> | void | Set store's state to provided new one can be partial | | resetState | No | void | Reset srote to it's initialState | | update | No | void | Force update all bound components and emit events | | on | eventType, callback* +2overload | StoreEvent<StoreState> | Subscribe to store state event listener | | resetPersistence | No | void | Reset persistent state | | resetDumpHistory | No | void | Reset all persistent history states | | saveDump | No | number | Save state dump and get its ID which is represented in number of current time milliseconds | | removeDump | timestamp: number | void | Remove state dump by ID | | restoreDump | timestamp: number | void | Replace current state to one from history by ID | | getDumpHistory | No | number[] | Get dump history IDs |

* Store methods: on() arguments
on() overloads
// Use this to get the whole store state
eventType: StoreEventType | StoreEventType[],
callback: (storeState: StoreState, prevState: StoreState, type: StoreEventType) => void

Use this overload if you need some optimization.

// Use this to get only specific keys
eventType: StoreEventType | StoreEventType[],
includeKeys: Array<keysof StoreState>
callback: (storeState: StoreState, prevState: StoreState, includeKeys: Array<keysof StoreState>, type: StoreEventType) => void

StoreEvent

StoreEvent methods

| Method name | Arguments | Returns | Description | | ----------- | --------- | ------- | ------------------------------------------- | | remove | No | void | Unsubscribe from store state event listener |

StoreEventType Enum

| Value | The event will be emitted | | ------------- | --------------------------------------------------- | | All | After every other event type emits | | Init | Once, as soon as the event has been bound | | Update | Each time after store was updated | | DumpUpdated | Each time after persistent store's dump was updated |

Example

followStore

We do not recomend use this way to connected with your stores, because it have no any performance techniques

| Argument | Type | Optional | Description | | -------- | ----------------------------- | -------- | -------------- | | store | Store<StoreState> | No | followed store |

Example

useStore

Use useStore only with store argument makes many performance issues. Instead, we recommend using includeKeys or mapState for optimizations and custom compare for perfect optimization.

| Argument | Type | Optional | Description | | ----------- | ---------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | store | Store<StoreState> | No | followed store* | | eventType | StoreEventType \| StoreEventType[] | Yes | re-render only on specific events | | mapState | callback | Yes | The selector function should be pure since it is potentially executed multiple times and at arbitrary points in time. | | compare | callback | Yes | The optional comparison function also enables using something like Lodash's _.isEqual() or Immutable.js's comparison capabilities. More about compare |

* To have more control over re-renders use eventType, mapState and compare their arguments. See more in optimization.
import React from 'react';
import { useStore } from 'react-stores';
import { myStore } from './myStore';

export const CounterComponent = ({ value }) => {
  const store = useStore(myStore);

  // Do not abuse this in a real app with complicated stores.
  // The component will not automatically update if the myStore state changes.
  return <div>{store.anyOfProps}</div>;
};

With includeKeys. example.

| Argument | Type | Optional | Description | | ------------- | ---------------------------------------------------------------------------------------- | -------- | --------------------------------- | | store | Store<StoreState> | No | followed store | | includeKeys | Array<keyof StoreState> | No | followed keys from store | | eventType | StoreEventType \| StoreEventType[] | Yes | re-render only on specific events |

Or with mapState and compare only. Example.

| Argument | Type | Optional | Description | | ---------- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | store | Store<StoreState> | No | followed store | | mapState | callback | No | The selector function should be pure since it is potentially executed multiple times and at arbitrary points in time. | | compare | callback | Yes | The optional comparison function also enables using something like Lodash' _.isEqual() or Immutable.js's comparison capabilities. More about compare |

Or use with second argument called options (legacy).

| Argument | Type | Optional | Description | | --------- | ----------------------------- | -------- | -------------------------------------------- | | store | Store<StoreState> | No | followed store | | options | Object | No | legacy, use an another overload |

useStore options

This is legacy, please use another useState overload.

| Argument | Type | Optional | Description | | ----------- | ---------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | eventType | StoreEventType \| StoreEventType[] | Yes | re-render only on specific events | | mapState | callback | Yes | The selector function should be pure since it is potentially executed multiple times and at arbitrary points in time. | | compare | callback | Yes | The optional comparison function also enables using something like Lodash's _.isEqual() or Immutable.js's comparison capabilities. More about compare |

mapState

This should take the first argument called state, optionally the second argument called prevState, and also takes optionally type as the third argument and returns a plain object containing data that connected component(s) listens to. Selector function should be pure since it is potentially executed multiple times and at arbitrary points in time.

type TMapState<StoreState, MappedState> = (
  storeState: StoreState,
  prevState?: StoreState,
  type?: StoreEventType,
) => MappedState;

compare

When store changes, useState() with mapState calling a compare of the previous mapState result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render. It should return true if compared states are equal. By default, it uses strict === reference equality checks for next and previous mapped state. It works only if you map primitives. Check this compare explanation.

type TCompare<MappedState> = (storeMappedState: MappedState, prevStoreMappedState: MappedState) => boolean;

useIsolatedStore

If you want to use isolated persistent stores dynamically with identical interface you must set name property inside passed options. In other case both of stores will be use one key in storage. Check demo here.

| Argument | Type | Optional | Description | | ------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | initialState | StoreState | No | Initial state values object | | options | StoreOptions | Yes | Setup store as you need with immutable, persistence and etс. | | persistenceDriver | StorePersistentDriver<StoreState> | Yes | StorePersistentDriver<StoreState> class instance |

Persistence

A store instance can be persistent from session to session in case you've provided StorePersistentDriver to it. React-stores includes built-in StorePersistentLocalStorageDriver, it saves store state into the LocalStore* using name or generated hash-name based on initialState interface. Check demo here.

* For React Native you have to provide your own StorePersistentDriver see below.

const myStore = new Store<IMyStoreState>(initialState, new StorePersistentLocalStorageDriver('myStore'));

You can implement your own persistence driver by implementing StorePersistentDriver abstract class.

Optimization

If you need to solve performance problems in the Components connected to stores, react-stores offers you tools to help you fix performance issues. Check demo here.

Use includeKeys in StoreEvent

You can prevent unnecessary update calls with includeKeys. In this case, events calls areSimilar function to compare previous state and next state using only the keys you provided to the includeKeys array.

comonentDidMount() {
  // You call setState each time when storeState changes even if storeState.mapOfObjects does not exist
  // It's not a big deal while you get only primitives from a store state, like strings or numbers
  this.storeEvent = myStore.on(
    StoreEventType.All,
    (storeState) => {
      this.setState({
        momentousMap: storeState.mapOfObjects,
      });
    },
  );
}
comonentDidMount() {
  //You can prevent update call for unnecessary keys in
  // and watch only for an important key for this component
  this.storeEvent = myStore.on(
    StoreEventType.All,
    // Watch only for mapOfObjects key from the store
    ['mapOfObjects'],
    (storeState) => {
      // The callback is fired only when mapOfObjects key was changed
      this.setState({
        momentousMap: storeState.mapOfObjects,
      });
    },
  );
}

Use includeKeys in useStore

You can prevent unnecessary update calls with includeKeys passed to the useStore. In this case, events calls the areSimilar function to compare previous state and next state uses only keys witch you pass into includeKeys.

// You call setState each time when storeState changes even if storeState.mapOfObjects does not exist
// It's not a big deal when you grab a primitives from store, like a strings or a numbers
const { mapOfObjects } = useStore(myStore, StoreEventType.Update /* Optional */);
// You can prevent update call for unnecessary keys in the store
// and watch only for important keys for this component
const { mapOfObjects } = useStore(myStore, ['mapOfObjects'], StoreEventType.Update /* Optional */);

Use mapState in useStore

Let's look at this example. In this case, CounterComponent will re-render every time after any of the keys in myStore were changed. This happens because after changing we get a new copy of the store state and forcing the update.

import React from 'react';
import { useStore } from 'react-stores';
import { myStore } from './myStore';

export const CounterComponent = ({ value }) => {
  // Component re-renders when myStore state changes
  const counter = useStore(myStore);

  /*
   * This code executes when myStore state changes
   */
  return <div>{counter}</div>;
};

You can use mapState function to pick primitives from state and pass them into your component.

import React from 'react';
import { useStore } from 'react-stores';
import { myStore } from './myStore';

export const CounterComponent = ({ value }) => {
  // Okay, now component will re-render only when the counter property actually changed
  // mapState function returns primitive
  const counter = useStore(myStore, state => state.counter);

  return <div>{counter}</div>;
};

It works because useHook uses strict === reference equality that checks next mapped state and previous mapped state. For primitives, it works perfectly, but let's look at this:

import React from 'react';
import { useStore } from 'react-stores';
import { myStore } from './myStore';

export const CounterComponent = ({ value }) => {
  // Now component re-renders every time we change store state
  // It is also relevant when you map two or more keys
  const { counter } = useStore(myStore, state => ({ counter: state.counter }));

  /*
   * All this code will be executed when myStore were changed
   */

  return <div>{counter}</div>;
};

It happens because mapState(nextState) does not equal the previous mapState, they are different objects. You can fix it with compare function.

Use compare in useStore

When you use mapState with only few keys, your component will re-render even if keys did not changed. Take a look:

import React from 'react';
import { useStore } from 'react-stores';
import { myStore } from './myStore';

export const CounterComponent = ({ value }) => {
  // Now component re-renders every time
  // It is also relevant when you map two or more keys
  const { counter } = useStore(myStore, state => ({ counter: state.counter }));

  /*
   * All this code will be executed when myStore were changed
   */

  return <div>{counter}</div>;
};

It happens because mapState(nextState) not equal previous mapState, they are different objects. Let's use compare function to fix it.

import React from 'react';
import { useStore } from 'react-stores';
// You can use `areSimilar` function exported from react-stores
import { areSimilar } from 'react-stores';
import { myStore } from './myStore';

export const CounterComponent = ({ value }) => {
  // Re-render component only when counter or anotherValue was changed
  const { counter, anotherValue } = useStore(
    myStore,
    state => ({ counter: state.counter, anotherValue: state.anotherValue }),
    // Use your custom compare function to prevent re-renders
    // if the store was changed but mapped values are the same.
    (a, b) => a.counter === b.counter && someCompareFunction(a.anotherValue, b.anotherValue),
  );

  return (
    <div>
      {counter}, {anotherValue.id}
    </div>
  );
};

Debugging

Install extension for chrome devtools for better debugging.