@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
5
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-utilor
yarn add @bonzar/rtk-query-tags-utilConfiguration
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): TagsProviderAdds 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): TagsProviderAdds 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>[]
): TagsProviderAdds 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
): TagsProviderAdds 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
): TagsProviderAdds 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
): TagsProviderAdds 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): TagsProviderAdds a tag with the specified type and id "LIST".
Example
invalidateList("Product")();
// [{ type: "Product", id: "LIST"}]invalidateOnSuccess
invalidateOnSuccess(successTags?: Tags | () => Tags): TagsProviderAdds 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[]): TagsProviderThis 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.
