posva / pinia-colada

🍹 The smart data fetching layer for Pinia
https://pinia-colada.esm.dev
MIT License
1.09k stars 26 forks source link

Add `autoRefetch` Option to `useQuery` for automatic stale entry refetching #49

Open quiteeasy opened 5 months ago

quiteeasy commented 5 months ago

Add a boolean option, autoRefetch, to automatically refetch an entry when it becomes stale.

posva commented 5 months ago

Interesting. This could be useful as a plugin once more stuff is exposed. I see it as a dangerous default as it would spam the server very often

quiteeasy commented 5 months ago

Adding this as an option would be useful, but I would not set it to true by default

posva commented 5 months ago

If it's added, it should be fully tree shakable too

quiteeasy commented 5 months ago

Should we add an option to QueryPlugin or possibly another plugin (perhaps as a separate package) to enable autoRefetch across the app, while still allowing control through each individual useQuery options? This approach would ensure it is tree-shakable and maintains flexibility

posva commented 5 months ago

We will see later on but probably a different kind of plugin. I think a lot of other more critical features are still missing before this 😄

ymansurozer commented 2 weeks ago

Would love to see this because it enables a major use case (use useQuery instead of adding manual polling).

posva commented 2 weeks ago

This should be doable as a plugin if anybody wants to contribute to the repo. The delay plugin should help to get started

ymansurozer commented 1 week ago

Thanks @posva! I'm writing a plugin for this but got stuck at accessing entry.stale in the plugin. I keep getting a Cannot read properties of null (reading 'staleTime') error. Would you have any ides or can you nudge me in the right direction? 🙏🏽

Minimal Reproduction: https://stackblitz.com/edit/github-ruyqjq?file=colada%2FautoRefetch.ts

Plugin file:

// plugins/auto-refresh/src/index.ts
import type { PiniaColadaPlugin, UseQueryEntry } from '@pinia/colada'

export interface PiniaColadaAutoRefetchOptions {
  /**
   * Whether to enable auto refresh by default.
   * Can be overridden per query.
   * @default true
   */
  enabled?: boolean
}

/**
 * Plugin that automatically refreshes queries when they become stale
 */
export function PiniaColadaAutoRefetch(
  options: PiniaColadaAutoRefetchOptions = {},
): PiniaColadaPlugin {
  // Default to enabled
  const { enabled = true } = options

  return ({ queryCache, scope }) => {
    // Keep track of active entries and their timeouts
    const refreshTimeouts = new Map<UseQueryEntry, NodeJS.Timeout>()

    queryCache.$onAction(({ name, after, args }) => {
      if (name === 'create') {
        after((entry) => {
          // Skip if auto-refresh is disabled globally or for this query
          const queryEnabled = entry.options?.autoRefetch ?? enabled
          if (!queryEnabled) return

          scope.run(() => {
            console.log('>> Running', entry.key)

            watch(
              () => entry.stale,
              (stale) => {
                console.log('>> Stale', stale)
                if (stale) {
                  // Clear any existing timeout
                  const existingTimeout = refreshTimeouts.get(entry)
                  if (existingTimeout) {
                    clearTimeout(existingTimeout)
                  }

                  // Schedule a refresh
                  const timeout = setTimeout(() => {
                    if (entry.active) {
                      console.log('>> Refreshing stale query:', entry.key)
                      queryCache.fetch(entry)
                    }
                    refreshTimeouts.delete(entry)
                  }, 0)

                  refreshTimeouts.set(entry, timeout)
                }
              },
              { immediate: true },
            )
          })
        })
      }

      // Clean up timeouts when entry is removed
      if (name === 'remove') {
        console.log('>> Remove', args)
        const [entry] = args
        const timeout = refreshTimeouts.get(entry)
        if (timeout) {
          clearTimeout(timeout)
          refreshTimeouts.delete(entry)
        }
      }
    })
  }
}

// Add types for the new option
declare module '@pinia/colada' {
  interface UseQueryOptions {
    /**
     * Whether to automatically refresh this query when it becomes stale.
     * Requires the PiniaColadaAutoRefetch plugin.
     */
    autoRefetch?: boolean
  }
}
posva commented 1 week ago

You get that error because the entry doesn't have options yet, so it cannot fetch. options are set by useQuery() calls. It's not a reactive property, so you can't watch it anyway. I would go for the fetch action (among others), to add a refetch timeout