@storecraft/sdk-react-hooks
Version:
Official Storecraft Javascript SDK React Hooks
297 lines (237 loc) • 6.48 kB
JavaScript
/**
* @import { ApiQuery, BaseType, Cursor } from '@storecraft/core/api'
* @import { inferDocumentCache, inferUseQueryCache } from './use-storecraft-cache.js'
* @import { InferQueryableType, queryable_resources } from './use-collection.types.js'
*/
import { useCallback, useEffect, useRef, useState } from "react"
import useTrigger from "./use-trigger.js"
import { useStorecraft } from "./use-storecraft.js";
import { useDocumentCache } from "./use-storecraft-cache.js";
import {
get_from_collection_resource as sdk_get, upsert_to_collection_resource as upsert_sdk, remove_from_collection_resource as sdk_remove
} from "@storecraft/sdk/src/utils.api.fetch.js";
/**
* @template T the `document` type
*
* @typedef {Omit<ReturnType<typeof useDocument<T>>, 'doc'> &
* {
* doc: T
* }
* } useDocumentHookReturnType This `type` will give you the return type of the hook
*
*/
/**
* @typedef {'load' | 'update' | 'delete'} Op
*/
/**
* @template {string | queryable_resources} [RESOURCE=(queryable_resources)]
* @template {InferQueryableType<RESOURCE>} [T=(InferQueryableType<RESOURCE>)]
*
* @param {RESOURCE} resource the table `identifier`
* @param {string} document the document `id` or `handle`
* @param {boolean} [autoLoad=true]
* @param {boolean} [try_cache_on_autoload=true]
*
*/
export function useDocument(
resource,
document,
autoLoad=true,
try_cache_on_autoload=true
) {
/** @type {inferDocumentCache<T>} */
const {
actions: {
get: cache_document_get,
put: cache_document_put,
remove: cache_document_remove
}
} = useDocumentCache();
const { sdk } = useStorecraft();
const [loading, setLoading] = useState(
(resource && document && autoLoad) ? true : false
);
/** @type {ReturnType<typeof useState<T>>} */
const [data, setData] = useState()
const [hasLoaded, setHasLoaded] = useState(false)
const [error, setError] = useState(undefined);
/** @type {ReturnType<typeof useState<Op>>} */
const [op, setOp] = useState(undefined);
const trigger = useTrigger();
useEffect(
() => sdk.auth.add_sub(trigger)
, [trigger]
);
const location = useRef([resource, document])
useEffect(
() => {
location.current = [resource, document];
}, [resource, document]
);
const reload_hard = useRef(
async (try_cache=true) => {
const resource = location.current[0];
const document = location.current[1];
if(!(resource && document)) {
throw 'no doc id';
}
setLoading(true);
setError(undefined);
setOp('load');
try {
/** @type {T} */
let item;
if(try_cache) {
item = await cache_document_get(document);
// found in cache
if(item) {
// background fetch from server
sdk_get(sdk, resource, document)
.then(
/** @param {T} item_server */
item_server => {
cache_document_put(item_server);
setData(item_server);
}
);
}
}
if(!item) {
item = await sdk_get(sdk, resource, document);
cache_document_put(item);
}
// console.log('item', item)
setData(item);
setHasLoaded(true);
return item;
} catch (e) {
console.log(e);
setError(e);
throw e;
} finally {
setLoading(false);
}
}
);
const reload = useCallback(
async (try_cache=true) => {
const resource = location.current[0];
const document = location.current[1];
if(!(resource && document)) {
console.log('resource', resource)
console.log('document', document)
throw 'no doc id';
}
setLoading(true);
setError(undefined);
setOp('load');
try {
/** @type {T} */
let item;
if(try_cache) {
item = await cache_document_get(document);
// found in cache
if(item) {
// background fetch from server
sdk_get(sdk, resource, document)
.then(
/** @param {T} item_server */
item_server => {
cache_document_put(item_server);
setData(item_server);
}
);
}
}
if(!item) {
item = await sdk_get(sdk, resource, document);
cache_document_put(item);
}
// console.log('item', item)
setData(item);
setHasLoaded(true);
return item;
} catch (e) {
console.log(e);
setError(e);
throw e;
} finally {
setLoading(false);
}
}, []
);
const upsert = useCallback(
/**
*
* @param {T} new_data
* @param {...any} extra
* @returns {Promise<T>}
*/
async (new_data, ...extra) => {
setLoading(true)
setError(undefined)
setOp('update')
try {
const id = await upsert_sdk(sdk, resource, new_data);
/** @type {T} */
const saved_data = await sdk_get(sdk, resource, id);
setData(saved_data);
setHasLoaded(true);
return saved_data;
} catch(e) {
setError(e);
console.log(JSON.stringify(e, null, 2));
throw e;
} finally {
setLoading(false)
}
}, [resource, document]
);
const remove = useCallback(
async () => {
setLoading(true);
setError(undefined);
setOp('delete');
try {
await sdk_remove(sdk, resource, document);
cache_document_remove(document);
setData(undefined);
setHasLoaded(true);
return document;
} catch (e) {
setError(e);
throw e;
} finally {
setLoading(false);
}
}, [resource, document]
);
useEffect(
() => {
if(resource==undefined)
throw new Error('useDocument: no Collection Id');
setData(undefined);
}, [resource, document]
);
useEffect(
() => {
if (autoLoad)
reload(try_cache_on_autoload);
}, [autoLoad, reload, resource, document]
);
return {
doc: data,
sdk,
loading,
hasLoaded,
error,
op,
resource,
document,
actions: {
reload, reload_hard, upsert,
remove,
setError,
}
}
}