Socket
Book a DemoInstallSign in
Socket

@fleur/lys

Package Overview
Dependencies
Maintainers
4
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fleur/lys

lys is an micro statement manager for '21s react

latest
Source
npmnpm
Version
3.0.1
Version published
Maintainers
4
Created
Source

CI latest BundleSize License npm

Lys

Lys (risu) is an minimal statement manger for '21s React.

It's focus to Per page state management, not application global state management.
Lys is usable to instead of useReducer, Mobx, or Recoil if you have async procedure.

See CodeSandbox Example

yarn add @fleur/lys

Features

  • Per page level micro state management
  • Initial state via external data
    • Can be use with likes Next.js, useSWR
  • Testing friendly
  • Type safe
  • Minimal re-rendering

Usage

Summary in CodeSandbox Example.

First, define your slice.

import { createSlice } from '@fleur/lys';

const formSlice = createSlice({
  actions: {
    // Define actions
    async patchItem({ commit }, index: number, patch: Partial<State['form']['items'][0]>) {
      commit((draft) => {
        Object.assign(draft.form.items[index], patch);
      });
    },
    async submit({ state, commit }) {
      if (state.hasError) return;

      commit({ submitting: true });

      commit({
        submiting: false,
        form: await (
          await fetch('/api/users', { body: JSON.stringify(state.form) })
        ).json(),
      });
    },
    async validate({ state }) {
      commit({ hasError: false });

      // Use your favorite validator
      commit({ hasError: await validateForm(state.form) });
    },
  },
  computed: {
    // You can define computable values in `computed`
    // `computed` is cached between to next state changed
    itemOf: (state) => (index: number) => state.form.items[index],
    canSubmit: (state) => !state.submitting,
  },
  }, (): State => ({
    // Define initial state
    submitting: false,
    hasError: false,
    form: {
      id: null,
      username: "",
      items: [{ name: "" }],
    },
  })
);

Next, initialize slice on your page component

import { useLysSliceRoot, useLysSlice } from '@fleur/lys';

export const NewUserPage = () => {
  const { data: initialData, error } = useSWR('/users/1', fetcher);

  // Initialize slice by `useLysSliceRoot`
  // `initialState` in second argument, it shallow override to Slice's initial state.
  // `initialData` is re-evaluated when it changes from null or undefined to something else.
  //
  // Or can you define `fetchUser` in slice and call it in `useEffect()`
  const [state, actions] = useLysSliceRoot(
    formSlice,
    initialData ? { form: initialData } : null
  );

  const handleChangeName = useCallback(({ currentTarget }) => {
    // `set` is builtin action
    actions.set((draft) => {
      draft.form.username = currentTarget.value;
    });
  }, []);

  const handleSubmit = useCallback(async () => {
    await actions.validate();
    await actions.submit();
  }, []);

  return (
    <div>
      <label>
        Display name:
        <input type="text" value={state.form.name} onChange={handleChangeName} />
      </label>

      <h1>Your items</h1>
      {state.form.items.map((index) => (
        <Item index={index} />
      ))}

      <button disabled={!state.canSubmit} onClick={handleSubmit}>
        Register
      </button>
    </div>
  );
};

Use initialize slice into child component

// In child component
const Item = ({ index }) => {
  // Use slice from root component by `useLysSlice`
  const [state, actions] = useLysSlice(formSlice);
  const item = state.itemOf(index);

  const handleChangeName = useCallback(({ currentTarget }) => {
    // Can call action from child component and share state with root.
    // Re-rendering from root (no duplicate re-rendering)
    actions.patchItem(index, { name: currentTarget.value });
  }, []);

  return (
    <div>
      Item of #{index + 1}
      <label>
        Name: <input type="text" value={item.name} />
      </label>
    </div>
  );
};

Testing

Lys's Slice is very testable. Let look testing example!

import { instantiateSlice, createSlice } from "@fleur/lys";

// Define (Normally, import from other file)
const slice = createSlice(
  {
    actions: {
      increment({ commit }) {
        commit((draft) => draft.count++);
      },
    },
    computed: {
      isZero: (state) => state.count === 0,
    },
  },
  () => ({ count: 0, submitting: false })
);

describe("Testing slice", () => {
  it("Should increment one", async () => {
    // instantiate
    const { state, actions } = instantiateSlice(slice);

    // Expection
    expect(state.current.count).toBe(0);
    expect(state.current.isZero).toBe(true);

    await actions.increment();
    expect(state.current.count).toBe(1);
    expect(state.current.isZero).toBe(false);
  });

  it("mock slice actions (for component testing)", () => {
    const actionSpy = jest.fn(({ state }) => (state.count = 10));

    const { state, actions } = mockSlice(
      slice,
      {
        /* part of initial state here */
      },
      {
        /* Mock action implementations here */
        increment: actionSpy,
      }
    );

    actions.increment();
    expect(actionSpy).toBeCalled();
  });
});

FAQs

Package last updated on 30 Sep 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts