From 37ae1f7de1bb2bd6b4446489898a3440d15ba615 Mon Sep 17 00:00:00 2001 From: Donkie Date: Sun, 15 Oct 2023 21:20:56 +0200 Subject: [PATCH] Generalized the useLiveify hook --- client/src/components/liveify.ts | 62 ++++++++++++++++++++++++++++++ client/src/pages/spools/list.tsx | 65 +++----------------------------- 2 files changed, 68 insertions(+), 59 deletions(-) create mode 100644 client/src/components/liveify.ts diff --git a/client/src/components/liveify.ts b/client/src/components/liveify.ts new file mode 100644 index 0000000..ae3d28b --- /dev/null +++ b/client/src/components/liveify.ts @@ -0,0 +1,62 @@ +import { LiveEvent } from "@refinedev/core"; +import React from "react"; +import liveProvider from "./liveProvider"; + +const liveProviderInstance = liveProvider(import.meta.env.VITE_APIURL); + +/** + * Hook that subscribes to live updates for the items in the dataSource + * @param dataSource Original dataSource + * @returns dataSource that is updated with live data + */ +export function useLiveify( + resource: string, + dataSource: Data[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transformPayload: (payload: any) => Data +) { + // TODO: The hooks in this function is quite janky, and should be refactored to be more efficient + // New state that holds the dataSource with updated values from the live provider + const [updatedDataSource, setUpdatedDataSource] = React.useState(dataSource); + + // If the original dataSource changes, update the updatedDataSource + React.useEffect(() => { + setUpdatedDataSource(dataSource); + }, [dataSource]); + + // Create a constant reference to itemIds. This is to prevent the useEffect below from triggering extra times. + const itemIds = dataSource.map((item) => item.id); + const [prevItemIds, setPrevItemIds] = React.useState(itemIds); + if (JSON.stringify(itemIds) !== JSON.stringify(prevItemIds)) { + setPrevItemIds(itemIds); + } + + // Subscribe to changes for all items in the dataSource + React.useEffect(() => { + const subscription = liveProviderInstance?.subscribe({ + channel: `${resource}-list`, + params: { + resource: resource, + ids: prevItemIds, + subscriptionType: "useList", + }, + types: ["update"], + callback: (event: LiveEvent) => { + setUpdatedDataSource((prev) => + prev.map((item) => { + return item.id === event.payload.data.id ? transformPayload(event.payload.data) : item; + }) + ); + }, + }); + + // Unsubscribe when the component unmounts + return () => { + if (subscription) { + liveProviderInstance?.unsubscribe(subscription); + } + }; + }, [resource, prevItemIds, transformPayload]); + + return updatedDataSource; +} diff --git a/client/src/pages/spools/list.tsx b/client/src/pages/spools/list.tsx index 9e8f23c..6c0d73b 100644 --- a/client/src/pages/spools/list.tsx +++ b/client/src/pages/spools/list.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { IResourceComponentsProps, LiveEvent, useInvalidate, useTranslate } from "@refinedev/core"; +import { IResourceComponentsProps, useInvalidate, useTranslate } from "@refinedev/core"; import { useTable, List, EditButton, ShowButton, CloneButton } from "@refinedev/antd"; import { Table, Space, Button, Dropdown, Modal } from "antd"; import dayjs from "dayjs"; @@ -23,7 +23,7 @@ import { useSpoolmanLotNumbers, useSpoolmanMaterials, } from "../../components/otherModels"; -import liveProvider from "../../components/liveProvider"; +import { useLiveify } from "../../components/liveify"; dayjs.extend(utc); @@ -59,61 +59,6 @@ function translateColumnI18nKey(columnName: string): string { const namespace = "spoolList-v2"; -const liveProviderInstance = liveProvider(import.meta.env.VITE_APIURL); - -/** - * Hook that subscribes to live updates for the spools in the dataSource - * @param dataSource Original dataSource - * @returns dataSource that is updated with live data - */ -function useLiveify(dataSource: ISpoolCollapsed[]) { - // TODO: The hooks in this function is quite janky, and should be refactored to be more efficient - // New state that holds the dataSource with updated values from the live provider - const [updatedDataSource, setUpdatedDataSource] = React.useState(dataSource); - - // If the original dataSource changes, update the updatedDataSource - React.useEffect(() => { - setUpdatedDataSource(dataSource); - }, [dataSource]); - - // Create a constant reference to itemIds. This is to prevent the useEffect below from triggering extra times. - const itemIds = dataSource.map((item) => item.id); - const [prevItemIds, setPrevItemIds] = React.useState(itemIds); - if (JSON.stringify(itemIds) !== JSON.stringify(prevItemIds)) { - setPrevItemIds(itemIds); - } - - // Subscribe to changes for all items in the dataSource - React.useEffect(() => { - const subscription = liveProviderInstance?.subscribe({ - channel: "spool-list", - params: { - resource: "spool", - ids: prevItemIds, - subscriptionType: "useList", - }, - types: ["update"], - callback: (event: LiveEvent) => { - const data = event.payload.data as ISpool; - setUpdatedDataSource((prev) => - prev.map((item) => { - return item.id === data.id ? collapseSpool(data) : item; - }) - ); - }, - }); - - // Unsubscribe when the component unmounts - return () => { - if (subscription) { - liveProviderInstance?.unsubscribe(subscription); - } - }; - }, [prevItemIds]); - - return updatedDataSource; -} - export const SpoolList: React.FC = () => { const t = useTranslate(); const invalidate = useInvalidate(); @@ -191,9 +136,11 @@ export const SpoolList: React.FC = () => { useStoreInitialState(namespace, tableState); // Collapse the dataSource to a mutable list and add a filament_name field - const dataSource: ISpoolCollapsed[] = useLiveify( - React.useMemo(() => (tableProps.dataSource ?? []).map(collapseSpool), [tableProps.dataSource]) + const queryDataSource = React.useMemo( + () => (tableProps.dataSource ?? []).map(collapseSpool), + [tableProps.dataSource] ); + const dataSource = useLiveify("spool", queryDataSource, collapseSpool); // Function for opening an ant design modal that asks for confirmation for archiving a spool const archiveSpool = async (spool: ISpoolCollapsed, archive: boolean) => {