Skip to content

TanStack Query Integration

key-hierarchy is designed to work seamlessly with TanStack Query, providing type-safe, collision-free query keys that make managing cache invalidation and data fetching easier and more reliable.

React

typescript
import { defineKeyHierarchy } from 'key-hierarchy'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

// Define your key hierarchy
const keys = defineKeyHierarchy((dynamic) => ({
  users: {
    getAll: true,
    create: true,
    byId: dynamic<number>().extend({
      get: true,
      update: true,
      delete: true,
      posts: {
        getAll: true,
      },
    }),
  },
}))

// Use in queries
function useUserQueries(userId: number) {
  const userQuery = useQuery({
    queryKey: keys.users.byId(userId).get,
    queryFn: () => fetchUser(userId),
  })

  const userPostsQuery = useQuery({
    queryKey: keys.users.byId(userId).posts.getAll,
    queryFn: () => fetchUserPosts(userId),
  })

  // ...
}

Vue

typescript
import { defineKeyHierarchy } from 'key-hierarchy'
import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'
import { MaybeRefOrGetter, toValue } from 'vue'

// Define your key hierarchy
const keys = defineKeyHierarchy((dynamic) => ({
  users: {
    getAll: true,
    create: true,
    byId: dynamic<MaybeRefOrGetter<number>>().extend({
      get: true,
      update: true,
      delete: true,
      posts: {
        getAll: true,
      },
    }),
  },
}))

function useUserQueries(id: MaybeRefOrGetter<number>) {
  const userQuery = useQuery({
    queryKey: keys.users.byId(userId).get,
    queryFn: () => fetchUser(toValue(userId)),
  })

  const userPostsQuery = useQuery({
    queryKey: keys.users.byId(userId).posts.getAll,
    queryFn: () => fetchUserPosts(toValue(userId)),
  })

  // ...
}

Cache Invalidation

One of the biggest benefits is precise cache invalidation. You can invalidate specific queries or use predicate functions for more complex invalidation patterns:

typescript
function useUpdateUser() {
  const queryClient = useQueryClient()
  
  return useMutation({
    mutationFn: updateUser,
    onSuccess: (updatedUser) => {
      // Invalidate specific user queries
      queryClient.invalidateQueries({
        queryKey: keys.users.byId(updatedUser.id).get
      })
      
      queryClient.invalidateQueries({
        queryKey: keys.users.byId(updatedUser.id).posts.getAll
      })
      
      // Also invalidate the users list
      queryClient.invalidateQueries({
        queryKey: keys.users.getAll
      })
    }
  })
}

Predicate-based Invalidation

For more flexible invalidation patterns, you can use the predicate option:

typescript
import { deepEqual } from 'fast-equals'

function useDeleteUser() {
  const queryClient = useQueryClient()
  
  return useMutation({
    mutationFn: deleteUser,
    onSuccess: (deletedUserId) => {
      // Remove specific user queries
      queryClient.removeQueries({
        queryKey: keys.users.byId(deletedUserId).get
      })
      
      queryClient.removeQueries({
        queryKey: keys.users.byId(deletedUserId).posts.getAll
      })
      
      // Invalidate users list
      queryClient.invalidateQueries({
        queryKey: keys.users.getAll
      })
      
      // Use predicate for more complex patterns
      queryClient.invalidateQueries({
        predicate: (query) => {
          const userQueryKey = keys.users.byId(deletedUserId)
          // Invalidate any sub-query for the deleted user
          return deepEqual(userQueryKey, query.queryKey.slice(0, userQueryKey.length))
        }
      })
    }
  })
}

Released under the MIT License.