react-shared-state-hook

Simple React shared state hook based on useState


Keywords
state, shared, global, react, hook, minimal, small, simple
License
MIT
Install
npm install react-shared-state-hook@0.1.0

Documentation

react-shared-state-hook Build status NPM version Bundle size

Simple React shared state hook based on useState

Installation

npm i react-shared-state-hook

Basic usage

import React from 'react';
import { createSharedStateHook } from 'react-shared-state-hook';

const [useSharedState, setSharedState] = createSharedStateHook({ a: 1, b: 2 });
// setSharedState({ a: 1, b: 3 }); // update state and trigger re-render

const ComponentA = () => {
  const state = useSharedState(); // re-render on any change
  return (
    <div>
      {state.a} + {state.b}
    </div>
  );
};

const ComponentB = () => {
  const b = useSharedState(o => o.b); // only re-render when b changes
  return <div>{b}</div>;
};

Example application setup

// index.ts
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';

ReactDOM.render(React.createElement(App), document.getElementById('root'));
// App.tsx
import React from 'react';
import { useActions, useAppState } from './state';

export const App = () => (
  <>
    <UsernameControl />
    <CountControl />
  </>
);

const UsernameControl = () => {
  const username = useAppState(o => o.username);
  const actions = useActions();
  return (
    <div>
      <p>Hello {username || '...'}</p>
      <input value={username ?? ''} onChange={e => actions.setUsername(e.target.value)} />
    </div>
  );
};

const CountControl = () => {
  const count = useAppState(o => o.count);
  const actions = useActions();
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => actions.increaseCount()}>+1</button>
    </div>
  );
};
// state.ts
import { createSharedStateHook } from 'react-shared-state-hook';
import * as actions from './actions';

export interface AppState {
  username: string | null;
  count: number;
}

let appState: AppState = {
  username: null,
  count: 1,
};

const [useAppStateHook, setAppState] = createSharedStateHook(appState);
const [useActionsHook] = createSharedStateHook(actions);

export const useAppState = useAppStateHook;
export const useActions = useActionsHook;

export const getState = () => appState;
export const setState = (update: Partial<AppState>) => {
  const newState: AppState = { ...appState, ...update };
  appState = newState;
  setAppState(appState);
};
// actions.ts
import { setState, getState } from './state';

export function setUsername(username: string) {
  setState({ username });
}

export function increaseCount() {
  setState({ count: getState().count + 1 });
}

Transforming data structures

When the selector function returns a new object, the component will re-render on every state update. To prevent this, either split up into multiple hook calls, pass an isEqual function or memoize state selections.

Renders on each state update:

const Component = () => {
  const myState = useAppState(o => ({ a: o.count, b: o.username, t: '...' })); 
  return <div>..</div>
};

Using multiple hook calls (and create objects outside of selectors):

const Component = () => {
  const a = useAppState(o => o.count);
  const b = useAppState(o => o.username);
  const myState = { a, b, t: '...' };
  return <div>..</div>
};

Passing an isEqual function:

import { shallowEqual } from 'other-npm-module';

const Component = () => {
  const myState = useAppState(o => ({ a: o.count, b: o.username, t: '...' }), shallowEqual); 
  return <div>..</div>
};

Or memoize selections using something like reselect:

const myStateSelector = createSelector<AppState>(
  o => o.count,
  o => o.username,
  (a, b) => ({ a, b, t: '...' })
);

const Component = () => {
  const myState = useAppState(myStateSelector);
  return <div>..</div>
};

Mixing local state with shared state

const CountControl = () => {
  const [ownCount, setOwnCount] = useState(0);
  const countText = useAppState(o => `Count: ${o.count} (current ownCount: ${ownCount})`);
  const ownCountText = `Own count: ${ownCount}`;
  const actions = useActions();
  return (
    <div>
      <p>{countText}</p>
      <p>{ownCountText}</p>
      <button onClick={() => actions.increaseCount()}>+1 shared</button>
      <button onClick={() => setOwnCount(ownCount + 1)}>+1 own</button>
    </div>
  );
};