fix(app): harden account and workflow state
This commit is contained in:
@@ -1,31 +1,50 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { api } from "../api";
|
||||
import { AUTH_USER_CHANGED_EVENT, getAuthUserKey } from "../auth";
|
||||
import { Company } from "../types";
|
||||
import { useViewResource, ViewResourceError } from "./useViewResource";
|
||||
|
||||
let cachedCompanies: Company[] | null = null;
|
||||
let inflight: Promise<Company[]> | null = null;
|
||||
let cacheOwner = "";
|
||||
|
||||
function resetForOwner(owner: string) {
|
||||
if (cacheOwner === owner) return;
|
||||
cacheOwner = owner;
|
||||
cachedCompanies = null;
|
||||
inflight = null;
|
||||
}
|
||||
|
||||
function cachedForCurrentOwner() {
|
||||
const owner = getAuthUserKey();
|
||||
resetForOwner(owner);
|
||||
return cachedCompanies;
|
||||
}
|
||||
|
||||
async function fetchCompanies(): Promise<Company[]> {
|
||||
const owner = getAuthUserKey();
|
||||
resetForOwner(owner);
|
||||
if (cachedCompanies) return cachedCompanies;
|
||||
if (inflight) return inflight;
|
||||
|
||||
inflight = api
|
||||
const request = api
|
||||
.get<Company[]>('/companies')
|
||||
.then((r) => {
|
||||
cachedCompanies = r.data;
|
||||
if (cacheOwner === owner && getAuthUserKey() === owner) cachedCompanies = r.data;
|
||||
return r.data;
|
||||
})
|
||||
.finally(() => {
|
||||
inflight = null;
|
||||
if (inflight === request) inflight = null;
|
||||
});
|
||||
inflight = request;
|
||||
|
||||
return inflight;
|
||||
}
|
||||
|
||||
export function invalidateCompaniesCache() {
|
||||
cachedCompanies = null;
|
||||
inflight = null;
|
||||
}
|
||||
|
||||
export function useCompanies(): {
|
||||
@@ -37,7 +56,7 @@ export function useCompanies(): {
|
||||
} {
|
||||
const [cacheBust, setCacheBust] = useState(0);
|
||||
const resource = useViewResource(fetchCompanies, {
|
||||
initialData: cachedCompanies ?? [],
|
||||
initialData: cachedForCurrentOwner() ?? [],
|
||||
errorMessage: 'Unable to load companies right now.',
|
||||
deps: [cacheBust],
|
||||
});
|
||||
@@ -48,6 +67,15 @@ export function useCompanies(): {
|
||||
}
|
||||
}, [resource.data, resource.error]);
|
||||
|
||||
useEffect(() => {
|
||||
const reloadForAccount = () => {
|
||||
resetForOwner(getAuthUserKey());
|
||||
setCacheBust((value) => value + 1);
|
||||
};
|
||||
window.addEventListener(AUTH_USER_CHANGED_EVENT, reloadForAccount);
|
||||
return () => window.removeEventListener(AUTH_USER_CHANGED_EVENT, reloadForAccount);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
companies: resource.data,
|
||||
loading: resource.loading,
|
||||
|
||||
@@ -66,6 +66,7 @@ export function useViewResource<T>(
|
||||
const [error, setError] = useState<ViewResourceError | null>(null);
|
||||
const hasLoadedRef = useRef(hasLoaded);
|
||||
const loadRef = useRef(load);
|
||||
const requestSequence = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
hasLoadedRef.current = hasLoaded;
|
||||
@@ -78,18 +79,22 @@ export function useViewResource<T>(
|
||||
const reload = useCallback(async () => {
|
||||
if (!enabled) return;
|
||||
|
||||
const requestId = ++requestSequence.current;
|
||||
const alreadyLoaded = hasLoadedRef.current;
|
||||
setLoading(!alreadyLoaded);
|
||||
setRefreshing(alreadyLoaded);
|
||||
try {
|
||||
const next = await loadRef.current();
|
||||
if (requestId !== requestSequence.current) return;
|
||||
setData(next);
|
||||
setError(null);
|
||||
setHasLoaded(true);
|
||||
} catch (err: any) {
|
||||
if (requestId !== requestSequence.current) return;
|
||||
setError(normalizeError(err, errorMessage));
|
||||
setHasLoaded(true);
|
||||
} finally {
|
||||
if (requestId !== requestSequence.current) return;
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
@@ -97,6 +102,7 @@ export function useViewResource<T>(
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
requestSequence.current += 1;
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user