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

@bonzar/rtk-query-tags-util

v2.0.0

Published

A companion package for Redux Toolkit Query, this package offers helpful utilities for providing and invalidating tags.

Downloads

7

Readme

@bonzar/rtk-query-tags-util

Utility package that provides convenient functions for working with tags in Redux Toolkit Query. It offers various helper functions to simplify the management of tags.

The utils are implemented as higher-order functions.

Installation

You can install package with:

npm install @bonzar/rtk-query-tags-util

or

yarn add @bonzar/rtk-query-tags-util

Configuration

To ensure correct types, you need to pass the TagTypes from apiSlice. It's recommended to create a separate file, such as apiCacheUtils.ts, for this purpose.

Create an instance of the CacheUtils class with the TagTypes passed as a generic.

From here, you can export the necessary utils for convenient importing in other files.

// apiCacheUtils.ts

import type { TagTypes } from "../apiSlice";
import { CacheUtils } from "@bonzar/rtk-query-tags-util";

const cacheUtils = new CacheUtils<TagTypes>();

export const {
  withList,
  withArgAsId,
  withNestedList,
  withNestedArgId,
  withNestedResultId,
  withDeepNestedList,
  invalidateList,
  invalidateOnSuccess,
  withTags,
} = cacheUtils;
// apiSlice.ts

import { createApi } from "@reduxjs/toolkit/query/react";

const tagTypes = ["Product", "Basket"] as const; // don't forget const

export type TagTypes = (typeof tagTypes)[number];

export const apiSlice = createApi({
  // ... other api options
  tagTypes,
});

Create Your Own Tag Utility

This is also a good place to create additional utils. You can do this easily with the createTagsProvider() and getTags() functions.

Creating Tags Provider

To provide additional tags based on the result, error, and argument:

const cacheUtils = new CacheUtils<TagTypes>();

export function withResultAsId<R extends string | number, A>(type: TagTypes) {
  return cacheUtils.createTagsProvider<R, A>((result, error, arg) => {
    if (!result) {
      return [];
    }

    return [{ type, id: result }]; // tags that will be added to tags list
  });
}

Creating Tags Wrapper

This can be useful to conditionally include providing tags. The invalidateOnSuccess() utility is already implemented for this purpose:

import type { TagsWrapper } from "@bonzar/rtk-query-tags-util";

const cacheUtils = new CacheUtils<TagTypes>();

export const invalidateOnError: TagsWrapper<TagTypes> =
  (errorTags) => (result, error, arg) => {
    if (!error) return [];

    return cacheUtils.getTags(result, error, arg)(errorTags);
  };

Custom BaseQueryError

If you use custom baseQuery, you can specify a type of your BaseQueryError when creating an instance of CacheUtil class.

const cacheUtils = new CacheUtils<TagTypes, YourBaseQueryErrorType>();

Also, when you create a tags wrapper with TagsWrapper type, you should put YourBaseQueryErrorType as second generic

const invalidateOnError: TagsWrapper<TagTypes, YourBaseQueryErrorType> =
  (tags) => (result, error, arg) => {
    // ...
  };

Usage

To provide tags, put the utility function in the providesTags/invalidatesTags field of the createApi object.

All utilities can be used as providesTags or invalidatesTags.

import { createApi } from "@reduxjs/toolkit/query/react";
import { withArgAsId } from "./apiCacheUtils";

const apiSlice = createApi({
  // ... other api creation options
  endpoints: (build) => ({
    getProducts: build.query<GetProductsResult, GetProductsArg>({
      query: (arg) => `product/${arg}`,
      providesTags: withArgAsId("Product")(), // it will provide tag { type: "Product", id: arg }
    }),
  }),
});

Specifying Additional Tags

You can add additional tags in the arguments of the tagsProvider, or specify a callback that will return these tags.

// it will provide tags [ { type: "Product", id: arg }, "Basket" ]
providesTags: withArgAsId("Product")(["Basket"]),

// or
providesTags: withArgAsId("Product")((result, error, arg) => ["Basket"]),

Using multiply utils

You can nest one utility inside another. In order for the types to work correctly, you need to pass the Result and Arg types to each tag utility.

For example:

const apiSlice = createApi({
  // omit other api creation options
  endpoints: (build) => ({
    getProducts: build.query<GetProductsResult, GetProductsArg>({
      query: (arg) => `product/${arg}`,
      providesTags: withArgAsId<GetProductsResult, GetProductsArg>("Product")(
        withNestedList<GetProductsResult, GetProductsArg>(
          "Basket",
          (result) => result.basket
        )(
          withNestedResultId<GetProductsResult, GetProductsArg>(
            "Coupon",
            (result) => result.coupon.id
          )()
        )
      ),
    }),
  }),
});

This is pretty unreadable :)

You can use the withTags utility to compose them and provide types to each one:

import { withTags } from "./apiCacheUtils";

const apiSlice = createApi({
  // omit other api creation options
  endpoints: (build) => ({
    getProducts: build.query<GetProductsResult, GetProductsArg>({
      query: (arg) => `product/${arg}`,
      providesTags: withTags<GetProductsResult, GetProductsArg>([
        withArgAsId("Product"),
        withNestedList("Basket", (result) => result.basket), // result already typed
        withNestedResultId("Coupon", (result) => result.coupon.id),
      ])(),
    }),
  }),
});

Note that when only one utility is used, types can be omitted.

Utility Functions

Each function returns a tags provider function.

The provider may receive tags and return a callback that can be passed to the providesTags/invalidatesTags field in the createApi object of RTK Query.

You can combine them by nesting one inside another.

P.S: Types are approximate

withList

withList<R extends Record<"id", string | number>[], A>(type: TagTypes): TagsProvider

Adds tags with the specified type and ids: "LIST", id property of items from the result array.

If the result is rejected, only a tag with the id "LIST" will be provided.

Example

// Result
// [
//   { id: 1, message: "foo" },
//   { id: 2, message: "bar" },
// ];

withList("Product")();
// [
//   { type: "Product", id: "LIST"},
//   { type: "Product", id: 1 },
//   { type: "Product", id: 2 },
// ]

withArgAsId

withArgAsId<R, A extends string | number>(type: TagTypes): TagsProvider

Adds a tag with the specified type and the argument as the id.

Example

// Argument - 5

withArgAsId("Product")();
// [{ type: "Product", id: 5 }]

withNestedList

withNestedList<R, A>(
  type: TagTypes,
  extractList: (result: R) => Record<"id", string | number>[]
): TagsProvider

Adds tags with the specified type and ids: "LIST", id property of items from the extracted list.

The list is extracted from the result using the extractList function.

If the result is rejected, only a tag with the id "LIST" will be provided.

Example

// Result
// {
//   nested: {
//     list: [
//       { id: 1, message: "foo" },
//       { id: 2, message: "bar" },
//     ],
//   },
// };

withNestedList("Product", (result) => result.nested.list)();
// [
//   { type: "Product", id: "LIST"},
//   { type: "Product", id: 1 },
//   { type: "Product", id: 2 },
// ]

withNestedArgId

withNestedArgId<R, A>(
  type: TagTypes,
  extractId: (arg: A) => string | number
): TagsProvider

Adds a tag with the specified type and the id, as an extracted field from the argument

Example

// Argument - { id: 5 }

withNestedArgId("Product", (arg) => arg.id)();
// [{ type: "Product", id: 5 }]

withNestedResultId

withNestedResultId<R, A>(
  type: TagTypes,
  extractId: (result: R) => string | number
): TagsProvider

Adds a tag with the specified type and the id, as an extracted field from the result

Example

// Result - { id: 5 }

withNestedArgId("Product", (res) => res.id)();
// [{ type: 'Product', id: 5 }]

withDeepNestedList

withDeepNestedList<R, A, IdItem extends Record<string, unknown>>(
  type: TagTypes,
  extractList: (result: R) => IdItem[],
  extractId: (item: IdItem) => string | number
): TagsProvider

Adds tags with the specified type and ids: "LIST", ids as properties extracted from items in the extracted list.

The list is extracted from the result using the extractList function.

The id is extracted from each item in the list using the extractId function.

If the result is rejected, only a tag with the id "LIST" will be provided.

Example

// Result
// {
//   nestedResult: [
//     { productId: 1 },
//     { productId: 2 },
//   ];
// }

withDeepNestedList(
  "Product",
  (result) => result.nestedResult,
  (item) => item.productId
)();
// [
//   { type: "Product", id: "LIST"},
//   { type: "Product", id: 1 },
//   { type: "Product", id: 2 },
// ]

invalidateList

invalidateList<R, A>(type: TagTypes): TagsProvider

Adds a tag with the specified type and id "LIST".

Example

invalidateList("Product")();
// [{ type: "Product", id: "LIST"}]

invalidateOnSuccess

invalidateOnSuccess(successTags?: Tags | () => Tags): TagsProvider

Adds the tags passed to the successTags argument, if the request is successful.

Otherwise, nothing will be provided.

Example

invalidateOnSuccess(["Product"]); // success request
// ['Product']

invalidateOnSuccess(() => ["Product"]); // rejected request
// []

withTags

withTags<R, A>(tagsProviders: TagsProvider[]): TagsProvider

This function is used to compose multiple tags provider functions together and pass typings from the generic to each of them.

It provides a better way to type multiple tags providers.

Example

withTags<ResultType, ArgType>([
  // no needed to pass typings for each of tags provider
  withList("BasketProduct"),
  withArgAsId("Basket"),
])(),

Contributing

Contributions to @bonzar/rtk-query-tags-util are welcome! If you find any issues or would like to suggest new features/utils, please create a GitHub issue or submit a pull request.

License

@bonzar/rtk-query-tags-util is open-source software licensed under the MIT license.