stedux
TypeScript icon, indicating that this package has built-in type declarations

0.2.1 • Public • Published

Stedux

Stedux is a state management library for Stencil components, inspired by React Redux. Components can listen to state, dispatch actions or async actions easily.

Instalation

npm i stedux or yarn add stedux

Examples

Source code for examples can be found in the example folder. The deployed live version can be found at https://randy-r.github.io/stedux.

Counter

// File: example/src/components/stedux-couter/stedux-counter.tsx
import { Component, h } from '@stencil/core';
import { Dispatch } from 'stedux';
import {
  createDispatch,
  createSelector,
  MyAction,
} from '../../store/counter-store';

@Component({
  tag: 'stedux-counter',
  shadow: true,
})
export class SteduxCounter {
  dispatch: Dispatch<MyAction>;
  counter: () => number;

  constructor() {
    this.dispatch = createDispatch(this);
    this.counter = createSelector((s) => s.counter, this);
  }

  render() {
    return (
      <div>
        <button onClick={() => this.dispatch({ type: 'decrement' })}>-</button>
        <span>{this.counter()}</span>
        <button onClick={() => this.dispatch({ type: 'increment' })}>+</button>
      </div>
    );
  }
}
// File: /src/store/counter-store.ts
import { forceUpdate, getElement, getRenderingRef } from '@stencil/core';
import { createStore } from 'stedux';

export type MyState = {
  counter: number;
};

export type Increment = {
  type: 'increment';
};
export type Decrement = {
  type: 'decrement';
};

export type MyAction = Increment | Decrement;

const counterReducer = (slice: MyState['counter'], action: MyAction) => {
  switch (action.type) {
    case 'decrement':
      return slice - 1;
    case 'increment':
      return slice + 1;
    default:
      return slice;
  }
};

export const myRootReducer = (state: MyState, action: MyAction): MyState => {
  const counter = counterReducer(state.counter, action);
  return {
    ...state,
    counter,
  };
};

export const myInitialState: MyState = {
  counter: 0,
};

const { createDispatch, createSelector } = createStore<MyState, MyAction>(
  myInitialState,
  myRootReducer,
  {
    forceUpdate,
    getElement,
    getRenderingRef,
  }
);

export { createDispatch, createSelector };

Data fetching / async thunks

// File: example/src/components/stedux-async/stedux-async.tsx
import { Component, Host, h } from '@stencil/core';
import { Thunk, ThunkDispatch } from 'stedux';
import {
  createDispatch,
  createSelector,
  MyAction,
  MyState,
} from '../../store/person-store';

const fetchPerson =
  (id: number): Thunk<MyState, MyAction> =>
  async (dispatch, _getState) => {
    dispatch({ type: 'person-loading' });
    const response = await fetch(`https://swapi.dev/api/people/${id}`);
    if (!response.ok) {
      dispatch({
        type: 'person-error',
        payload: { error: { message: `${response.status}` } },
      });
      return;
    }
    const person = await response.json();
    dispatch({ type: 'person-done', payload: person });
  };

@Component({
  tag: 'stedux-async',
  shadow: true,
})
export class SteduxAsync {
  dispatch: ThunkDispatch<MyAction, MyState>;
  personData: () => MyState['person'];

  constructor() {
    this.personData = createSelector((s) => s.person, this);
    this.dispatch = createDispatch(this);
  }

  render() {
    const { data, loading, error } = this.personData();

    return (
      <Host>
        {loading && <div>Loading...</div>}
        {data && (
          <Fragment>
            <div>name: {data.name}</div>
            <div>height: {data.height}</div>
          </Fragment>
        )}
        {error && <div>Oops: {error.message}</div>}
        <button onClick={() => this.dispatch(fetchPerson(1))}>
          Fetch data
        </button>
      </Host>
    );
  }
}
// File: /src/store/async-store.ts
import { forceUpdate, getElement, getRenderingRef } from '@stencil/core';
import { createStore } from 'stedux';

export type MyState = {
  person: {
    loading: boolean | null;
    data: { name: string; height: string } | null;
    error: { message?: string; name?: string } | null;
  };
};

export type PersonFetchLoading = {
  type: 'person-loading';
};
export type PersonFetchDone = {
  type: 'person-done';
  payload: { name: string; height: string };
};
export type PersonFetchError = {
  type: 'person-error';
  payload: { error: { message?: string; name?: string } };
};

export type PersonAction =
  | PersonFetchLoading
  | PersonFetchDone
  | PersonFetchError;

export type MyAction = PersonAction;

export const myInitialState: MyState = {
  person: {
    loading: null,
    data: null,
    error: null,
  },
};

const personReducer = (
  slice: MyState['person'],
  action: MyAction
): MyState['person'] => {
  switch (action.type) {
    case 'person-loading':
      return { data: null, error: null, loading: true };
    case 'person-error': {
      return { data: null, error: action.payload.error, loading: false };
    }
    case 'person-done':
      return { data: action.payload, error: null, loading: false };
    default:
      return slice;
  }
};

export const myRootReducer = (state: MyState, action: MyAction): MyState => {
  return {
    ...state,
    person: personReducer(state.person, action),
  };
};

const { createDispatch, createSelector, createProvider } = createStore<
  MyState,
  MyAction
>(myInitialState, myRootReducer, {
  forceUpdate,
  getElement,
  getRenderingRef,
});

export { createDispatch, createSelector, createProvider };

Package Sidebar

Install

npm i stedux

Weekly Downloads

1

Version

0.2.1

License

MIT

Unpacked Size

151 kB

Total Files

27

Last publish

Collaborators

  • randy-r