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

zustand-fetching

v2.5.0

Published

Zustand state manager controllers

Downloads

77

Readme

Zustand controllers

Helps to avoid using other state managers to execute requests and allows you to work efficiently with zustand.

The Zustand Fetching Helpers library provides a set of functions and controllers that facilitate working with Zustand, a state management library. The functions described below are well-typed and allow working with nested objects. While Zustand suggests writing custom slices to divide the store into several parts, this library aims to simplify common data-related tasks without the need for additional state management solutions.

To get a better understanding of what this library offers and how it works, you can refer to the live example on CodeSandbox . In many cases, the provided controllers will help reduce the complexity of your store, eliminating the need to split it into multiple parts.

Installation

⚠️library changed name. You can install the library using npm:

npm install leiten-zustand

Since "Zustand" translates to "state" in German, we decided to adhere to the same naming strategy and used the word " leiten" (meaning "lead" and "manage") to denote our controllers.

Common view

const useStore = create<IState>(() => ({ ... })); //clean store without actions
const useController = leiten[Controller](useStore, "dot.nested.path", [options]);

Small Example

Let's create some fake example: load some data and then change it.

Pure zustand

const useStore = create<IStore>((set, get) => ({
  loadData: async (id: string) => {
    try {
      set({ loadingData: true })
      const response = await getData(id);
      set({ data: response })
    } catch {
      // Todo show error
    } finally {
      set({ loadingData: false })
    }
  },
  loadingData: false,
  data: { user: null, cards: [] },
  updateUser: (user: Partial<IUser>) => {
    set({ data: { ...get().data, user: { ...get().data?.user, ...user } } })
  },
  removeCard: (cardId: string) => {
    const cards = get().data.cards.filter(card => card.id !== cardId);
    set({ data: { ...get().data, cards } })
  }
}))

With leiten controllers

const useStore = create<IStore>(() => ({
  data: { user: null, cards: [] },
}));

// loadData & loadingData
const useRequest = leitenRequest(useStore, "data", (id: string) => getData(id));
// updateUser
const userController = leitenRecord(useStore, "data.user");
// removeCard
const cardsController = leitenList(useStore, "data.cards", { compare: (a, b) => a.id == b.id });

Using "leiten" controllers empowers you to simplify your state management by removing redundant actions, eliminating unnecessary states, and reducing the complexity of working with nested objects. By adopting "leiten" controllers you can create a cleaner and more streamlined store structure, making it easier to manage and manipulate your application's data.

All actions and states for your zustand store. Examples

  • leitenRequest Helps handle requests (any async function) and catch errors. Returns a hook with request parameters and provides methods such as action, clear, abort, and set.
  • leitenGroupRequest Handles multiple similar requests dynamically. Returns a hook with two overloads and provides methods such as action and clear. Can work with arrays as well as with the normalized list.
  • leitenRecord Works with objects and provides methods such as set, patch and clear.
  • leitenPrimitive Works with data as if it were a primitive value, but it can be an object, function, or primitives. Provides methods such as set and clear.
  • leitenList Works with arrays and provides methods such as set, clear, add, update, remove, toggle, and filter. If the array item is an object, a compare function needs to be set in the controller's options (third parameter).
  • leitenNormalizedList Same as leitenList but works with normalized state.
  • leitenModal Helps work with modals and provides a built-in modal manager for cascading modals. Returns hooks with [openState, hiddenState] and provides methods such as open, close and action.
  • leitenFilterRequest Same as leitenRequest but provide createFilter and listen methods, which allows you to create an unlimited number of filters for the request. The request will automatically start action when the filter's patch method is called. Or in case listen, the request will be executed if the observed value changes.
  • leitenGroupFilterRequest Same as leitenGroupRequest but provide createFilter method, which allows you to create an unlimited number of filters for the request. Works like leitenFilterRequest.

All leitenControllers automatically infer the required types based on the specified path and will throw a TypeScript error if the provided path does not match the controller's requirements or established types. Examples:

  • Argument of type '"info.keywords.1"' is not assignable to parameter of type '"info.keywords"'.
  • Argument of type 'string' is not assignable to parameter of type 'never'.

⚠️ If you encounter an error when specifying the path to your field in the store, it is likely because you are attempting to attach a controller to a field with an incompatible type. Please ensure that the field you are attaching the controller to has a permitted type to resolve this issue.

Library well tree shaking and have dependencies from immer, lodash-es and nanoid

Advanced

Options

leitenRecord, leitenPrimitive, leitenList and leitenNormalizedList have options with callbacks: sideEffect and patchEffect. You can use them to extend basic functionality

const useExampleStore = create<IState>(() => ({ user: null }));
const recordController = leitenRecord(useExampleStore, "user", {
  sideEffect: (value: { prev: IUser; next: IUser }) => {
    // you can execude here some side actions
  },
  patchEffect: (value: VALUE) => {
    // you can update your entire store here in one tick with value update
  },
});

leitenRequest and leitenGroupRequest have a useful reactions: fulfilled, rejected, abort, resolved and action

const useExampleStore = create<IState>(() => ({ user: null }));
const recordController = leitenRequest(useExampleStore, "user", async (id: string) => getUser(id), {
  fulfilled: ({ previousResult, result, payload }) => {
    // do something after successful request
  },
  rejected: ({ previousResult, error, payload }) => {
    // do something after error request
  },
  abort: ({ previousResult, payload }) => {
    // do something after request was aborted
  },
  resolved: ({ previousResult, payload }) => {
    // do something after request was resolved
  },
  action: ({ previousResult, payload }) => {
    // do something before request was called
  },
  optimisticUpdate: (payload) => {
    //if you use this callback, then leitenRequest will automatically switch to optimistic update mode  
  },
  initialStatus: ILoadingStatus // initial status if request, init by default
});
  • leitenList - if you are using object then you also should specify compare function like in example
  • leitenNormalizedList - in addition to the compare function, you also need to define the getKey function

Request

All requests working with useLeitenRequests. Usually you will never need it, but if you need it, then the record is stored there with all the query parameters. The request key is returned by each leitenRequest

interface IState {
  user: IUser | null;
}

const useExampleStore = create<IState>(() => ({
  user: null,
}));

const useController = leitenRequest(useExampleStore, "user", getUser);

const User = () => {
  //  const status = useController(state => state.status) - the same
  const status = useLeitenRequests(state => state[useController.key].status)
  return <>{status}</>
}

leitenMap also can be helpful, example

Group Request

leitenGroupRequest works equally well with both a normalized list and a regular array. If you are using an array, make sure to specify the getKey function, as shown in the example below. Codesandbox link with arrays

interface IStore {
  record: Record<string, ICard>,
  array: ICard[],
}

const useStore = create<IStore>(() => ({
  record: {},
  array: []
}));
const useRecordController = leitenGroupRequest(useStore, "record", (id: string) => getCard(id))
const useArrayController = leitenGroupRequest(useStore, "array", (id: string) => getCard(id), {
  getKey: (value) => value.id
})

leitenGroupRequest return overloaded hook

interface IState {
  cards: Record<string, ICard>;
}

const useExampleStore = create<IState>(() => ({
  cards: {},
}));
export const useGroupController = leitenGroupRequest(
  useExampleStore,
  "cards",
  async (props: ILeitenGroupRequestParams<string>) => {
    return getCard(props.params);
  },
);

const status = useGroupController(id, (state) => state.status); //First param is key, better option
or
const requests = useGroupController((state) => state); // Record with all requests

Store

The library provides wrappers for ContextStore and ResettableStore. These wrappers can be used to enhance your Zustand store with additional features.