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

@fleur/lys

v3.0.1

Published

lys is an micro statement manager for '21s react

Downloads

46

Readme

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();
  });
});