react-query-key-manager

repository·master·Indexed 41 days ago

https://github.com/draftswithea/react-query-key-manager

A lightweight TypeScript library for managing and organizing query keys for @tanstack/react-query. It provides a type-safe, composable, and collision-free way to define query key factories using defineQueryKeys and key, preventing magic strings in large-scale applications. Version 0.0.2 introduces improved type safety and a revised API for key composition and namespacing.

Tokens
2.2K
Snippets
8
Records
10
Agent score
2%

What's inside react-query-key-manager

  1. How key composition works

    master

    You can compose complex query keys by combining existing keys. To use a key's underlying array for composition, call the key function with its arguments and then invoke it with an extra set of parentheses () to retrieve the array.

    const postKeys = defineQueryKeys("post", {
      detail: key((postId: string) => ["post", "detail", postId]),
    });
    
    const extendedPostKeys = defineQueryKeys("post.extended", {
      withAuthor: key((postId: string, authorId: string) => [
        ...postKeys.detail(postId)(), // Note the extra () to get the array
        "author",
        ...userKeys.profile(authorId)(),
      ]),
    });
    
    // Result: ["post", "detail", "123", "author", "user", "profile", "456"]
    const postKeys = defineQueryKeys("post", {
      detail: key((postId: string) => ["post", "detail", postId]),
    });
    
    const extendedPostKeys = defineQueryKeys("post.extended", {
      withAuthor: key((postId: string, authorId: string) => [
        ...postKeys.detail(postId)(),
        "author",
        ...userKeys.profile(authorId)(),
      ]),
    });
  2. How nested namespaces work

    master

    You can organize query keys hierarchically by nesting objects within the keyMap of defineQueryKeys. This is useful for large applications to maintain structure.

    export const adminKeys = defineQueryKeys("admin", {
      users: {
        list: key((page: number) => ["admin", "users", "list", page]),
        detail: key((id: string) => ["admin", "users", "detail", id]),
      },
      settings: {
        general: key(() => ["admin", "settings", "general"]),
      },
    });
    
    // Accessing nested keys:
    adminKeys.users.list(1); // returns ["admin", "users", "list", 1]
    export const adminKeys = defineQueryKeys("admin", {
      users: {
        list: key((page: number) => ["admin", "users", "list", page]),
        detail: key((id: string) => ["admin", "users", "detail", id]),
      },
      settings: {
        general: key(() => ["admin", "settings", "general"]),
      },
    });
    
    adminKeys.users.list(1);
  3. Quick Start: Define and use query keys

    master

    To manage query keys, use defineQueryKeys to create a namespaced collection and key to wrap individual key functions. This ensures type safety and prevents collisions.

    import { useQuery } from "@tanstack/react-query";
    import { defineQueryKeys, key } from "react-query-key-manager";
    
    // 1. Define your query keys
    export const userKeys = defineQueryKeys("user", {
      profile: key((userId: string) => ["user", "profile", userId]),
      settings: key((userId: string, section?: string) => [
        "user",
        "settings",
        userId,
        section,
      ]),
      list: key(() => ["user", "list"]),
    });
    
    // 2. Use them in a component
    function UserProfile({ userId }: { userId: string }) {
      const { data } = useQuery({
        queryKey: userKeys.profile(userId),
        queryFn: () => fetchUserProfile(userId),
      });
    
      return <div>{data?.name}</div>;
    }
    import { useQuery } from "@tanstack/react-query";
    import { defineQueryKeys, key } from "react-query-key-manager";
    
    export const userKeys = defineQueryKeys("user", {
      profile: key((userId: string) => ["user", "profile", userId]),
      settings: key((userId: string, section?: string) => [
        "user",
        "settings",
        userId,
        section,
      ]),
      list: key(() => ["user", "list"]),
    });
    
    function UserProfile({ userId }: { userId: string }) {
      const { data } = useQuery({
        queryKey: userKeys.profile(userId),
        queryFn: () => fetchUserProfile(userId),
      });
    
      return <div>{data?.name}</div>;
    }
  4. Migrate from v0.0.113 to v0.0.2+

    master

    Version 0.0.2 introduced breaking changes to improve type safety. Follow this checklist to migrate:

    1. Replace QueryKeyManager.create with defineQueryKeys.
    2. Replace defineKey with key.
    3. Crucial: Wrap all unwrapped functions with the key() function.
    4. Update your imports.

    Example Migration:

    Before (v0.0.113):

    import { QueryKeyManager, defineKey } from "react-query-key-manager";
    
    const userKeys = QueryKeyManager.create("user", {
      profile: defineKey((userId: string) => ["user", "profile", userId]),
    });

    After (v0.0.2+):

    import { defineQueryKeys, key } from "react-query-key-manager";
    
    const userKeys = defineQueryKeys("user", {
      profile: key((userId: string) => ["user", "profile", userId]),
    });
    import { defineQueryKeys, key } from "react-query-key-manager";
    
    const userKeys = defineQueryKeys("user", {
      profile: key((userId: string) => ["user", "profile", userId]),
      settings: key((userId: string) => ["user", "settings", userId]),
    });
  5. API: key(fn)

    master

    Wraps a query key function to enable type inference and composition.

    Parameters:

    • fn (function): A function that returns a query key array.

    Returns: An enhanced function that supports both direct calls (e.g., myKey(arg)) and composition (e.g., myKey(arg)()).

    Note: All functions within defineQueryKeys must be wrapped with key() to ensure type safety and prevent errors.

    profile: key((userId: string) => ["user", "profile", userId])
  6. Define a query key registry with defineQueryKeys()

    master

    The defineQueryKeys function is the primary way to organize and enforce best practices for your React Query keys. It takes a unique name and a keyMap object.

    Requirements for the keyMap:

    • Every leaf node in the object must be a function wrapped by key().
    • You can use nested objects to create namespaces.
    • If a function is provided without being wrapped in key(), TypeScript will emit a compile-time error.

    Runtime Behavior:

    • It registers the keys in a global registry for tracking.
    • In development, it throws an error if you attempt to register the same name twice (e.g., due to HMR or duplicate calls).
    • In production, duplicate registrations are ignored to prevent crashes.
    import { defineQueryKeys, key } from 'react-query-key-manager';
    
    export const userKeys = defineQueryKeys('users', {
      all: key(() => ['users']),
      detail: key((id: string) => ['users', id]),
      settings: {
        profile: key((userId: string) => ['users', userId, 'settings']),
      },
    });
    
    // Accessing keys:
    // userKeys.all() -> ['users']
    // userKeys.detail('123') -> ['users', '123']
    // userKeys.settings.profile('123') -> ['users', '123', 'settings']
  7. Create type-safe query key factories with key()

    master

    The key() function is a higher-order function used to wrap a function that returns a React Query key array. It provides two main benefits:

    1. Type Safety: It ensures the returned value is a valid QueryKeyPart array.
    2. Zero-argument invocation: It adds a property to the returned function that allows you to call it without arguments to get the base key (useful for keys without parameters).

    Every function passed to key() must return an array of QueryKeyPart (string, number, boolean, object, or undefined).

    import { key } from 'react-query-key-manager';
    
    // A key with parameters
    const userKey = key((userId: string) => ['users', userId]);
    
    // A key without parameters (base key)
    const allUsersKey = key(() => ['users']);
    
    // Usage:
    userKey('123');      // returns ['users', '123']
    allUsersKey();       // returns ['users']
    allUsersKey.call(); // returns ['users'] (via the added zero-arg wrapper)