Composables

useRemoteFetch

Composable per il caricamento dati da API remote con filtri statici, filtri dinamici da form, gestione errori e prevenzione race condition.

Panoramica

useRemoteFetch gestisce il fetch di dati per tutti i componenti "remote" di @pzeta/vue-form (es. SelectRemote, ListboxRemote, TreeRemote). Fornisce:

  • Filtri statici applicati a tutte le richieste
  • Filtri dinamici che si aggiornano automaticamente al cambiamento di altri campi del form
  • Prevenzione race condition tramite request ID e AbortController
  • Loading state e gestione errori reattivi
  • Varianti integrate: useRemoteFetchPostgREST per PostgREST, useRemoteSetup per componenti

API

import { useRemoteFetch } from '@pzeta/vue-form'

export function useRemoteFetch<T = Record<string, unknown>>(
  options: UseRemoteFetchOptions<T> | Ref<UseRemoteFetchOptions<T>>,
): UseRemoteFetchReturn<T>

Tipi

export interface UseRemoteFetchOptions<T = Record<string, unknown>> {
  fetchFn?: (filters?: FilterCriteria, signal?: AbortSignal) => Promise<T[]>
  filter?: string | FilterCriteria
  filterFields?: string[]
  fullData?: Record<string, unknown>
  immediate?: boolean           // default: true
  componentName?: string
  onError?: (error: Error, componentName: string) => void
}

export interface UseRemoteFetchReturn<T = Record<string, unknown>> {
  data: Ref<T[]>
  loading: Ref<boolean>
  error: Ref<Error | null>
  combinedFilters: ComputedRef<FilterCriteria | undefined>
  reload: () => Promise<void>
  setData: (newData: T[]) => void
}

Parametri / Opzioni

OpzioneTipoDefaultDescrizione
fetchFnFetchFn<T>Funzione async che riceve i filtri combinati e ritorna T[]
filterstring | FilterCriteriaFiltro statico applicato a tutte le richieste. Stringa JSON o oggetto
filterFieldsstring[]Campi del form da osservare per filtri dinamici
fullDataRecord<string, unknown>Oggetto con tutti i valori del form (sorgente dei filtri dinamici)
immediatebooleantrueSe false, il caricamento non parte al mount
componentNamestring'useRemoteFetch'Nome usato nei messaggi di errore/log
onError(error, name) => voidCallback per gestione errori personalizzata

Le opzioni possono essere passate come oggetto statico oppure come Ref<UseRemoteFetchOptions<T>>. Nel secondo caso, qualsiasi cambiamento alle opzioni provoca un nuovo fetch.

Valore di ritorno

ProprietàTipoDescrizione
dataRef<T[]>Array reattivo con i dati caricati. Vuoto finché il fetch non completa
loadingRef<boolean>true durante il fetch
errorRef<Error | null>Errore dell'ultimo fetch, null se nessun errore
combinedFiltersComputedRef<FilterCriteria | undefined>Filtri effettivamente applicati (statici + dinamici)
reload() => Promise<void>Esegue un nuovo fetch con i filtri correnti
setData(newData: T[]) => voidImposta i dati senza eseguire fetch

Esempi

Uso base con filtro statico

import { useRemoteFetch } from '@pzeta/vue-form'

const { data: utenti, loading, error } = useRemoteFetch({
  fetchFn: (filters) => api.getUtenti(filters),
  filter: { attivo: true },
  immediate: true,
  componentName: 'UtenteSelect',
})
<template>
  <div v-if="loading">Caricamento...</div>
  <div v-else-if="error" class="text-red-500">{{ error.message }}</div>
  <SelectRemote v-else :options="utenti" />
</template>

Filtri dinamici da altri campi del form

Quando formData.provinciaId cambia, comuni vengono ricaricati automaticamente con il nuovo filtro.

const formData = ref({ provinciaId: null as number | null, comuneId: null })

const { data: comuni, loading } = useRemoteFetch({
  fetchFn: (filters) => api.getComuni(filters),
  filterFields: ['provinciaId'],   // osserva questo campo
  fullData: formData.value,        // sorgente dei valori
  componentName: 'ComuneSelect',
})

I filtri dinamici vengono combinati con operatore eq. Il computed combinedFilters rifletterà:

{ "provinciaId": { "operator": "eq", "value": 3 } }

Caricamento lazy (immediate: false)

const { data, loading, reload } = useRemoteFetch({
  fetchFn: fetchCategorie,
  immediate: false,
  componentName: 'CategoriaSelect',
})

// Carica solo all'apertura del dropdown
const onDropdownOpen = async () => {
  if (data.value.length === 0) {
    await reload()
  }
}

Gestione errori con callback

const { data, error, reload } = useRemoteFetch({
  fetchFn: fetchDati,
  componentName: 'DatiRemoti',
  onError: (err, name) => {
    toast.error(`[${name}] ${err.message}`)
    logger.error(err)
  },
})

Opzioni reattive (Ref)

Quando si vuole cambiare il filtro statico dopo l'inizializzazione:

const options = ref({
  fetchFn: fetchArticoli,
  filter: { categoria: 'A' } as FilterCriteria,
})

const { data } = useRemoteFetch(options)

// Cambia categoria — trigger automatico del reload
const onCategoriaChange = (nuovaCategoria: string) => {
  options.value = { ...options.value, filter: { categoria: nuovaCategoria } }
}

Impostare i dati manualmente

const { data, setData } = useRemoteFetch({ fetchFn: fetchOpzioni })

// Pre-popola con dati di default
setData([{ id: 0, label: '-- Nessuno --' }])

// Aggiorna dopo una creazione locale senza ri-fetchare
const onNuovoRecord = (nuovoRecord: Opzione) => {
  setData([...data.value, nuovoRecord])
}

Varianti

useRemoteFetchPostgREST

Wrapper che integra useRemoteFetch con repository @pzeta/postgrest. Gestisce automaticamente cache, schema e deduplication.

import { useRemoteFetchPostgREST } from '@pzeta/vue-form'

const { data: cities, loading } = useRemoteFetchPostgREST<City>({
  path: '/cities',
  keyName: 'idcity',
  filterFields: ['idprovincia'],
  fullData: formData.value,
  repositoryOptions: {
    useCache: true,
    cacheTTL: 1800000,  // 30 minuti (default)
  },
})
Opzione aggiuntivaTipoDefaultDescrizione
pathstringPath endpoint PostgREST (es. '/cities')
keyNamekeyof TNome della chiave primaria
schemastringSchema PostgreSQL (override globale)
repositoryOptions.useCachebooleantrueAbilita cache risultati
repositoryOptions.cacheTTLnumber1800000TTL cache in ms (default 30 min)
repositoryOptions.isAuthRequiredbooleantrueRichiede autenticazione

useRemoteSetup

Composable di supporto interno usato dai componenti remote per centralizzare il boilerplate di setup. Accetta le props del componente e un nome, restituisce l'output di useRemoteFetch.

import { useRemoteSetup } from '@pzeta/vue-form'

// Dentro un componente remote custom
const props = defineProps<MyRemoteProps>()
const { data, loading, error, reload } = useRemoteSetup(props, 'MyRemote', {
  immediate: false,
})

Note

  • I filtri dinamici escludono automaticamente valori null, undefined e stringhe vuote.
  • Le race condition sono gestite internamente tramite request ID e AbortController: le risposte stale vengono silenziosamente ignorate.
  • Il watch sui filtri dinamici è ottimizzato: osserva solo i campi specificati in filterFields tramite computed, non esegue deep watch su tutto fullData.