Skip to content

Commit

Permalink
feat: autorefresh fields (#738)
Browse files Browse the repository at this point in the history
* feat: autorefresh fields in form

gisce/webclient#1415

* fix: adjust seconds

* fix: mantain values touched when autorefreshing

gisce/webclient#1415

* chore: remove comment

* fix: adjust id retrieval for autoRefresh

gisce/webclient#1415

* feat: improve hooks and tree autorefreshable fields

gisce/webclient#1415
  • Loading branch information
mguellsegarra authored Dec 12, 2024
1 parent 89cba68 commit a880c0f
Show file tree
Hide file tree
Showing 8 changed files with 421 additions and 84 deletions.
2 changes: 1 addition & 1 deletion src/context/ActionViewContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export const useActionViewContext = () => {
searchTreeNameSearch: undefined,
goToResourceId: async () => {},
limit: DEFAULT_SEARCH_LIMIT,
isActive: false,
isActive: undefined,
formIsSaving: false,
setFormIsSaving: () => {},
formHasChanges: false,
Expand Down
122 changes: 122 additions & 0 deletions src/hooks/useAutorefreshableFormFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { ConnectionProvider } from "..";
import { useNetworkRequest } from "./useNetworkRequest";
import { useDeepCompareEffect } from "use-deep-compare";
import { useRef, useState, useCallback, useEffect } from "react";
import { useBrowserVisibility } from "./useBrowserVisibility";

const AUTOREFRESH_INTERVAL_SECONDS = 3 * 1000;

export type UseAutorefreshableFormFieldsOpts = {
model: string;
id?: number;
context: any;
autorefreshableFields?: string[];
fieldDefs: any;
onAutorefreshableFieldsChange: (newValues: any) => void;
isActive?: boolean;
};

export const useAutorefreshableFormFields = (
opts: UseAutorefreshableFormFieldsOpts,
) => {
const {
model,
id,
context,
autorefreshableFields,
fieldDefs,
onAutorefreshableFieldsChange,
isActive,
} = opts;

const intervalRef = useRef<NodeJS.Timeout | null>(null);
const [internalIsActive, setInternalIsActive] = useState(true);

const [fetchRequest, cancelRequest] = useNetworkRequest(
ConnectionProvider.getHandler().readObjects,
);

const tabOrWindowIsVisible = useBrowserVisibility();

useEffect(() => {
if (isActive === false) {
pause();
}
if (
(isActive === undefined || isActive === true) &&
!tabOrWindowIsVisible
) {
pause();
}
if ((isActive === undefined || isActive === true) && tabOrWindowIsVisible) {
resume();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isActive, tabOrWindowIsVisible]);

const refresh = useCallback(async () => {
if (!id || !autorefreshableFields?.length || !internalIsActive) return;

try {
const [result] = await fetchRequest({
model,
ids: [id],
fields: fieldDefs,
fieldsToRetrieve: autorefreshableFields,
context,
});
onAutorefreshableFieldsChange(result);
} catch (err) {
console.error(err);
}
}, [
id,
autorefreshableFields,
internalIsActive,
fetchRequest,
model,
fieldDefs,
context,
onAutorefreshableFieldsChange,
]);

useDeepCompareEffect(() => {
const shouldStart = id && autorefreshableFields?.length && internalIsActive;

if (shouldStart) {
refresh();
intervalRef.current = setInterval(refresh, AUTOREFRESH_INTERVAL_SECONDS);
}

return () => {
cancelRequest();
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [
autorefreshableFields,
fetchRequest,
fieldDefs,
model,
id,
context,
internalIsActive,
]);

const pause = useCallback(() => {
setInternalIsActive(false);
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
cancelRequest();
}, [cancelRequest]);

const resume = useCallback(() => {
setInternalIsActive(true);
}, []);

return { pause, resume };
};
123 changes: 123 additions & 0 deletions src/hooks/useAutorefreshableTreeFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { ConnectionProvider } from "..";
import { useNetworkRequest } from "./useNetworkRequest";
import { useDeepCompareEffect } from "use-deep-compare";
import { useRef, useState, useCallback, useEffect } from "react";
import { InfiniteTableRef } from "@gisce/react-formiga-table";
import { useBrowserVisibility } from "./useBrowserVisibility";

const AUTOREFRESH_INTERVAL_SECONDS = 3 * 1000;

export type UseAutorefreshableTreeFieldsOpts = {
tableRef: React.RefObject<InfiniteTableRef>;
model: string;
context: any;
autorefreshableFields?: string[];
fieldDefs: any;
isActive?: boolean;
};

export const useAutorefreshableTreeFields = (
opts: UseAutorefreshableTreeFieldsOpts,
) => {
const {
tableRef,
model,
context,
autorefreshableFields,
fieldDefs,
isActive,
} = opts;

const intervalRef = useRef<NodeJS.Timeout | null>(null);
const [internalIsActive, setInternalIsActive] = useState(true);

const [fetchRequest, cancelRequest] = useNetworkRequest(
ConnectionProvider.getHandler().readObjects,
);

const tabOrWindowIsVisible = useBrowserVisibility();

useEffect(() => {
if (isActive === false) {
pause();
}
if (
(isActive === undefined || isActive === true) &&
!tabOrWindowIsVisible
) {
pause();
}
if ((isActive === undefined || isActive === true) && tabOrWindowIsVisible) {
resume();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isActive, tabOrWindowIsVisible]);

const refresh = useCallback(async () => {
if (!autorefreshableFields?.length || !internalIsActive) return;

const ids = tableRef.current?.getVisibleRowIds();

if (!ids) return;

try {
const results = await fetchRequest({
model,
ids,
fields: fieldDefs,
fieldsToRetrieve: autorefreshableFields,
context,
});
tableRef.current?.updateRows(results);
} catch (err) {
console.error(err);
}
}, [
autorefreshableFields,
internalIsActive,
tableRef,
fetchRequest,
model,
fieldDefs,
context,
]);

useDeepCompareEffect(() => {
const shouldStart = autorefreshableFields?.length && internalIsActive;

if (shouldStart) {
refresh();
intervalRef.current = setInterval(refresh, AUTOREFRESH_INTERVAL_SECONDS);
}

return () => {
cancelRequest();
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [
autorefreshableFields,
fetchRequest,
fieldDefs,
model,
context,
internalIsActive,
]);

const pause = useCallback(() => {
setInternalIsActive(false);
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
cancelRequest();
}, [cancelRequest]);

const resume = useCallback(() => {
setInternalIsActive(true);
}, []);

return { pause, resume };
};
19 changes: 19 additions & 0 deletions src/hooks/useBrowserVisibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useState, useEffect } from "react";

export const useBrowserVisibility = (): boolean => {
const [isVisible, setIsVisible] = useState<boolean>(!document.hidden);

useEffect(() => {
const handleVisibilityChange = (): void => {
setIsVisible(!document.hidden);
};

document.addEventListener("visibilitychange", handleVisibilityChange);

return (): void => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, []);

return isVisible;
};
3 changes: 3 additions & 0 deletions src/hooks/useSearchTreeState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type SearchTreeState = {
setSearchQuery: (value: SearchQueryParams) => void;
totalItems: number;
setTotalItems: (value: number) => void;
isActive?: boolean;
};

export function useSearchTreeState({
Expand Down Expand Up @@ -76,6 +77,7 @@ export function useSearchTreeState({
setSearchQuery: actionViewContext.setSearchQuery ?? (() => {}),
totalItems: actionViewContext.totalItems ?? 0,
setTotalItems: actionViewContext.setTotalItems ?? (() => {}),
isActive: actionViewContext.isActive,
}
: {
treeIsLoading: localTreeIsLoading,
Expand All @@ -98,5 +100,6 @@ export function useSearchTreeState({
setSearchQuery: setLocalSearchQuery,
totalItems: localTotalItems,
setTotalItems: setLocalTotalItems,
isActive: undefined,
};
}
2 changes: 1 addition & 1 deletion src/views/CurrentTabContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext } from "react";
import { useContext } from "react";

import {
TabManagerContext,
Expand Down
Loading

0 comments on commit a880c0f

Please sign in to comment.