usecache
usecache is front-end request cache react hooks.
Requests from front end load data from local cache(sessionStorage or localStorage) firstly.
If not hit, then load data from remote server and cache them for next time.
Get Started
Add dependency:
npm i @rwsbillyang/usecache
useCache hook
export function useCache<T>(url: string, shortKey?: string,
withouAuth: boolean = false,
showLoading: boolean = false,
storageType: number = UseCacheConfig.defaultStorageType,
transformDataBoxFromResponseJson?: (json: any) => DataBox<T>)
Eg:
const {loading, entity, errMsg } = useCache<BizXXX>("/api/xxx", "bizXXX")
loading: loading state
errMsg: error msg
entity: biz data
useCacheList hook
useCacheList hook loads list data from cache or remote with pagination
export function useCacheList<T, Q extends BasePageQuery>(
url: string,
shortKey?: string,
initalQuery?: Q,
needLoadMore: boolean = true,
storageType: number = UseCacheConfig.defaultStorageType,
transformDataBoxFromResponseJson?: (json: any) => DataBox<T[]>
)
useCacheList returns { isLoading, isError, errMsg, loadMoreState, list, refreshCount, setList, setQuery, setRefresh, setUseCache, setIsLoadMore }
- isLoading: loading state
- isError/errMsg: has error or not and error msg when load data
- loadMoreState/setIsLoadMore: loadMoreState is true means has more data, should enable LoadMore button, and load more data that will be merged into list. If call setIsLoadMore(false), it does not merge.
- list: the data loaded
- refreshCount/setRefresh: used for refreshing data from remote. When setRefresh(refreshCount+1) will result in loading data from remote.
- setQuery: if need modify query parameters and reload from remote, eg search.
- setUseCache: setUseCache(false) means load data only from remote.
Example:
const { isLoading, isError, errMsg, loadMoreState, setQuery, refreshCount, setRefresh, list, setList, setUseCache, setIsLoadMore }
= useCacheList<T, Q>(props.listApi, props.cacheKey, props.initialQuery, props.needLoadMore === false ? false : true)
We can:
- show loading state, erro info and LoadMore button by using
isLoading, isError, errMsg, loadMoreState
. - show list data by using
list
, and modifiy list by using setList
- search by using
setQuery
- refresh by using
setRefresh(refreshCount+1)
Non-hook edition
If not use react hook, we can use non-react-hook edition
cachedGet/cachedPost
Example using cachedGet:
function correctExpressionRecord() {
cachedGet<any[]>("/api/rule/composer/list/expression", (data) => {
}, { pagination: { pageSize: -1, sKey: "id", sort: 1 } })
cachedGet/cachedPost defined in usecache:
export function cachedGet<T>(url: string, onOK: (data: T) => void, data?: object, shortKey?: string, isShowLoading: boolean = true, attachAuthHeader: boolean = true)
export function cachedPost<T>(url: string, onOK: (data: T) => void, data?: object, shortKey?: string, isShowLoading: boolean = true, attachAuthHeader: boolean = true)
cachedFetch
cachedGet/cachedPost call cachedFetch:
export function cachedFetch<DATATYPE>(params: FetchParams<DATATYPE>)
export interface FetchParams<T> {
url: string,
data?: object,
method: "GET" | "POST" | "PUT" | "DELETE",
attachAuthHeader?: boolean,
shortKey?: string,
storageType?: number,
onOK: (data: T) => void,
onNoData?: () => void,
onKO?: (code: string, msg?: string) => void,
onErr?: (msg: string) => void,
onDone?: () => void,
isShowLoading?: boolean,
showLoading?: () => void,
hideLoading?: () => void,
transformDataBoxFromResponseJson?: (json: any) => DataBox<T>
}
Example of cachedFetch:
cachedFetch<any[]>({
method: "POST",
url: `${Host}/api/rule/composer/getByIds/constant`,
data: {data: constantQueryParams.ids},
shortKey: constantAsyncSelectProps.key,
onDone: () => { setConstantLoading(false) },
onOK: (data) => {
}
})
cachedFetchPromise
If need Promise somewhere, please use
export const cachedFetchPromise = async <T>(
url: string,
method: "GET" | "POST" | "PUT" | "DELETE",
data?: object,
shortKey?: string,
storageType: number = UseCacheConfig.defaultStorageType,
transformDataBoxFromResponseJson?: (json: any) => DataBox<T>,
transfomFromBizData?: (bizData: any) => T,
attachAuthHeader?: boolean,
isShowLoading: boolean = false,
showLoading?: () => void,
hideLoading?: () => void,
)
Example code:
function saveOne(data: any, url: string) {
cachedFetchPromise<any>(url, 'POST', data)
.then((data) => {
if (data) {
console.log("save done")
} else { console.log("no data return after save") }
return new Promise((resolve) => { resolve(true) });
}).catch((err) => {
console.warn("exception: " + err.message)
})
}
Detail Usage
Add query fields should be in XXXQueryParams which extends BasePageQuery
export interface XXXQueryParams extends BasePageQuery {
label?: string
typeId?: number
mapKey?: string
domainId?: number
categoryId?: number
}
usecache will automatically convert pagination in BasePageQuery into string using encodeURIComponent.
Here BasePageQuery and QueryPagination defined in usecache library as following
export interface BasePageQuery {
pagination?: QueryPagination
umi?: string
}
export interface QueryPagination{
pageSize?: number,
current?: number,
sKey?: string,
sKeyType?: "TypeObjectId" | "TypeString" | "TypeNumber",
sort?: number,
lastId?: string,
fKey?: string,
filters?: string[]
}
export const encodeUmi = (umi: QueryPagination) => encodeURIComponent(JSON.stringify(umi))
An example which pageSize is 20:
const initialQuery:XXXQueryParams = { domainId: searchParams["domainId"], typeId: searchParams["typeId"], pagination: { pageSize: 20 } }
DataBox/transformDataBoxFromResponseJson
The footprint of every response from remote should be unified, so front end handle it with one way.
DataBox is defined in usecache and back-end library ktorkit
The main fields are code: 'OK' means everything is ok, 'KO' means something wrong in back end.
If OK, data is the real payload. else msg is error message.
If your backend return different data structure, should be transformed by providing transformDataBoxFromResponseJson when call useCache or useCacheList:
pseudo-code:
const MyTransformDataBoxFromResponseJson = (json: any) => DataBox<T>{
return DataBox("OK", data)
return DataBox("KO", msg)
}
DataBox definition in usecache
export interface DataBoxBase {
code: string,
msg?: string
type: number,
tId?: string,
host?: string
}
export interface DataBox<T> extends DataBoxBase {
data?: T
}
Config UseCache
In react App init page, config UseCache
Where init Exampe 1:
initConfig()
const e = document.getElementById('app')
if (e) {
createRoot(e).render(React.createElement(App))
}else{
console.error("not found id: document.getElementById('app')")
}
const initConfig = () => {
console.log("initConfig...")
UseCacheConfig.EnableLog = true
}
Where init Exampe 2:
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<BrowserRouter>
<App />
</BrowserRouter>,
)
function App() {
UseCacheConfig.EnableLog = true
return useRoutes(AppRoutes);
}
export default App;
Init eg:
UseCacheConfig.cacheSpace = () => "/myapp"
UseCacheConfig.showLoading = (text) => { console.log("TODO: show loading with text: "+ text) }
UseCacheConfig.hideLoading = () => { console.log("TODO: hide loading...") }
UseCacheConfig.showToast = (text) => { console.log("TODO: showToast with text: "+ text) }
The default config in usecahce:
export const UseCacheConfig: IUseCacheConfig = {
EnableLog: false,
cacheSpace: () => "",
defaultIdentiyKey: "_id",
defaultStorageType: StorageType.OnlySessionStorage,
PageSize: 10,
request: fetchRequest,
authheaders: () => undefined
}
If using wxlogin library:
WxLoginConfig.AppKeyPrefix = "/kf";
UseCacheConfig.cacheSpace = WebAppLoginHelper.getCacheSpace
UseCacheConfig.showLoading = (text) => { console.log("TODO: show loading with text: "+ text) }
UseCacheConfig.hideLoading = () => { console.log("TODO: hide loading...") }
UseCacheConfig.showToast = (text) => { console.log("TODO: showToast with text: "+ text) }
UseCacheConfig.authheaders = () => WxAuthHelper.getHeaders()
IUseCacheConfig definition:
interface IUseCacheConfig {
EnableLog: boolean
cacheSpace: () => string
defaultIdentiyKey: string
defaultStorageType: number
PageSize: number
request: IRequest,
authheaders: () => {} | undefined
showLoading?: (text?: string) => void
hideLoading?: () => void
showToast?: (msg?: string) => void
}
Customize Request
export interface IRequest {
get: (url: string, data?: object) => Promise<Response>
post: (url: string, data?: object) => Promise<Response>
upload: (url: string, data: ArrayBuffer|Blob) => Promise<Response>
getWithoutAuth: (url: string, data?: object, crossDomain?: boolean) => Promise<Response>
postWithoutAuth: (url: string, data?: object, crossDomain?: boolean) => Promise<Response>
}
We can use customized request by using
UseCacheConfig.request = YourCustomizedRequest
The default request is based on fetch:
export const fetchRequest: IRequest = {
get: (url: string, data?: object) => fetch(url + (data ? ("?" + serializeObject(data)) : ''),
{
method: 'GET',
headers: new Headers({
...UseCacheConfig.authheaders(),
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
})
}),
post: (url: string, data?: object) => fetch(url, {
method: 'POST',
body: JSON.stringify(data),
headers: new Headers({
...UseCacheConfig.authheaders(),
'Content-Type': 'application/json; charset=UTF-8'
})
}),
upload: (url: string, data: ArrayBuffer | Blob) => fetch(url,
{
body: data,
method: 'POST',
headers: new Headers({
...UseCacheConfig.authheaders(),
'Content-Type': 'application/octet-stream',
})
}),
getWithoutAuth: (url: string, data?: object, crossDomain: boolean = false) => fetch(url + (data ? ("?" + serializeObject(data)) : ''),
{
method: 'GET',
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
"Referrer-Policy": crossDomain ? "no-referrer" : "origin"
})
}),
postWithoutAuth: (url: string, data?: object, crossDomain: boolean = false) => fetch(url,
{
body: JSON.stringify(data),
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json; charset=UTF-8',
"Referrer-Policy": crossDomain ? "no-referrer" : "origin"
})
})
}
Utils
Cache
Cache provids many helper functions to update cache:
- findOne
- findMany
- onAddOne: add new one into list
- onAddOneInList
- onEditOne: call it when update one successully
- onEditOneInList
- onEditMany
- onEditManyInList
- onDelOneById: call it when delete one successfully
- onDelOneByIdInList
- onDelOne: call it when delete one successfully
- onDelOneInList
- onDelManyByIds
- onDelManyByIdsInList
- onDelMany: call it when batch delete manys successfully
- onDelManyInList
- evictCache: evict the given key cache with storageType
- evictAllCaches: evict all cache with storageType
TreeCache
support tree data, provids helper functions:
- getElementsByPathIdsInTreeFromCache
- getPathFromTree
- onAddOneInTreeCache
- onAddOneInTree
- onEditOneInTreeCache
- onEditOneInTree
- onDelOneInTreeCache
- onDelOneInTree
CacheStorage
provides help functions:
- getItem/saveItem
- getObject/saveObject
- remove
support StorageType
export const StorageType = {
OnlySessionStorage: 1,
OnlyLocalStorage: 2,
BothStorage: 3,
NONE: 0
}
DateTimeUtil
covinient util: format date/time
DateTimeUtil.dateFormat(new Date(), "MM-dd hh:mm")
DateTimeUtil.formatYearDateTime(e.createdAt)
DateTimeUtil.formatDateTime
DateTimeUtil.formatDate
DateTimeUtil.formatDuration
ArrayUtil
Support array list, including contains, findOne, findMany, removeOne, removeMany
Support tree data, including:
- getArrayByPathInTree,find one by id path array in tree
- findOneFromTree: find one by id in tree
- findAllFromTree: find many by id in tree
- trimTreeByPath: trim tree, the given path specified left data
- transformTree: transform tree data by lamada function in tree
- traverseTree: traverse similar forEach in array in tree
Misc utils
export const currentHref = () => window.location.protocol + "//" + window.location.host
export const serializeObject = (obj?: object, enableEmptyLog: boolean = false) => string|undefined
export function query2Params<Q extends BasePageQuery>(query?: Q) => string
export function deepCopy(data: object, ignoreDeepKeys?: string[], hash = new WeakMap()) => object
About UI/Cache invalidate
Generally, usecache lib update cache automatically when add/edit/delete one or many in table list, and UI display the latest updated data.
But if you have special requirements, you should manually call onAddXX/OnEditXX/OnDelXXX in Cache or TreeCache after save or del one/many item(s) in table, and tell UI data updated and need refresh UI such as send event using use-bus.