UNPKG

haystack-react

Version:

Project Haystack utilities for building React applications

178 lines (149 loc) 3.96 kB
/* * Copyright (c) 2020, J2 Innovations. All Rights Reserved */ /* eslint prefer-const: "off" */ import { useEffect, useRef, useState } from 'react' import { Watch, DEFAULT_POLL_RATE_SECS, Ids } from 'haystack-nclient' import { HGrid } from 'haystack-core' import { GridRefreshResult, useClient, useOnlyOps } from './client' interface Callback { (): void } /** * A grid refresh watch result. */ export interface GridRefreshWatchResult extends GridRefreshResult { /** * The number of times the underlying watch has updated the grid result. */ updates: number } interface WatchData { grid: HGrid isLoading: boolean loads: number error?: Error } /** * A hook to watch some live haystack data. * * @param options.ids The ids of the records to watch. Alternatively a `filter` can be specified. * @param options.filter The haystack filter to query on the server. Alternatively record `ids` * can be specified. * @param options.display An optional display name for the filter. * @param options.pollRate An optional poll rate for the watch in seconds. * @returns A result that wraps a grid. */ export function useWatch({ filter, ids, display, pollRate = DEFAULT_POLL_RATE_SECS, }: { filter?: string ids?: Ids display?: string pollRate?: number }): GridRefreshWatchResult { const client = useClient() const ops = useOnlyOps() const data = useRef<WatchData>() const watchData = data.current ?? (data.current = { grid: new HGrid(), isLoading: true, loads: 0, }) const [refreshes, setRefreshes] = useState(0) let [updates, setUpdates] = useState(0) const forceUpdate = (): void => setUpdates(++updates) useEffect( (): Callback => { let cancel = false async function open(): Promise<Watch | undefined> { try { const wasLoading = watchData.isLoading watchData.isLoading = true // If watch wasn't loading before it is now so force an // update in the UI. if (!wasLoading) { forceUpdate() } if (!ids) { ids = filter ? await (ops ? client.ops.read(filter) : client.record.readByFilter(filter)) : new HGrid() // If the effect was closed after the network call then bail. if (cancel) { return } watchData.grid = ids watchData.isLoading = false // Force an update here as we already have some valid data to render before // the watch is opened. forceUpdate() } const watch = await (ops ? client.ops.watch : client.watch ).make( display || filter || `useWatch@${new Date().toISOString()}`, ids ) // If the effect was closed after opening the watch then close it and bail. if (cancel) { await watch.close() return watch } watch.pollRate = pollRate > 0 ? pollRate : DEFAULT_POLL_RATE_SECS watch.changed({ callback: forceUpdate, }) watchData.grid = watch.grid return watch } catch (err) { if (!cancel) { watchData.error = err as Error } } finally { if (!cancel) { watchData.isLoading = false ++watchData.loads } forceUpdate() } return undefined } const promise = open() // Return a function that will be invoked when the effect is closed. return (): void => { cancel = true // On clean up, reset to the initial state. watchData.isLoading = false watchData.error = undefined async function close(): Promise<void> { if (promise) { const watch = await promise watch?.close() } } close() } }, [filter, JSON.stringify(ids), client, display, pollRate, ops, refreshes] // Effect re-runs if any of these parameters change. ) return { grid: watchData.grid, isLoading: watchData.isLoading, loads: watchData.loads, error: watchData.error, refresh: (): void => setRefreshes(refreshes + 1), updates, } }