state-use

Simple state manager for React.

Usage

Setup

First, You should define your state.

// src/state/user.ts
import { define } from 'state-use';

// async helpers.
type Async<R, E> = {
  state: 'default';
} | {
  state: 'loading';
} | {
  state: 'success';
  response: R;
} | {
  state: 'failure';
  error: E;
};
export const Async = {
  default: () => ({ state: 'default' } as const),
  loading: () => ({ state: 'loading' } as const),
  success: <R extends unknown>(r: R) => ({ state: 'success', response: r } as const),
  failure: <E extends unknown>(e: E) => ({ state: 'failure', error: e } as const),
};

export const UserState = define<{
  id: number;
  nickname: string;
  details: Async<{ birthday: string; }, unknown>;
}>();

Then you can initialize state.

// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { UserState, Async } from './state/user.ts';
import { App } from './state/component/App.tsx';

UserState.setup({
  id: 0,
  nickname: 'hrsh7th',
  details: Async.default(),
}); // Setup default state (It can be used to fill server-side props).

ReactDOM.render((
  <App />
), document.querySelector('#app'));

Basic read/write state

// src/component/User.tsx

import React from 'react';
import { UserState } from '../state/user';

export const User = () => {
  const nickname = UserState.use(s => s.nickname);

  const onNicknameClick = useCallback(() => {
    UserState.update(ctx => {
      ctx.state.nickname = 'new nickname';
    });
  });

  return (
    <div onClick={onNicknameClick}>{nickname}</div>
  );
};

Async read/write state

The state-use handles async operation via Generator Function.

// src/component/User.tsx

import React from 'react';
import { UserState, Async } from '../state/user';

export const User = () => {
  const user = UserState.use();

  const onFetchButtonClick = useCallback(() => {
    // Warning: You must use `ctx.state` directly. You can't save `ctx.state` as another variable.
    UserState.update(function *(ctx) => {
      ctx.state.details = Async.loading();
      try {
        ctx.state.details = Async.success(yield fetch(`https://example.com/users/${s.id}/details`).then(res => res.json()));
      } catch (e) {
        ctx.state.details = Async.failure(e);
      }
    });
  });

  return (
    <div onClick={onFetchButtonClick}>fetch details</div>
    {user.details.state === 'default' && (
      <div>
        {user.details.state === 'loading' ? (
          'Loading...'
        ) : user.details.state === 'success' ? (
          <UserDetails details={user.details.response} />
        ) : (
          'Error...'
        )}
      </div>
    )}
  );
};

GitHub

https://github.com/hrsh7th/state-use