fix frigate hosts infinity rendering after save

This commit is contained in:
NlightN22 2024-03-02 16:13:28 +07:00
parent 67c27ff614
commit ea48a538a5
5 changed files with 70 additions and 43 deletions

View File

@ -51,7 +51,7 @@
"zod": "^3.21.4"
},
"scripts": {
"start": "react-scripts start",
"dev": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"

View File

@ -1,16 +1,20 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import FrigateHostsTable from '../widgets/FrigateHostsTable';
import { Button, Flex, Text } from '@mantine/core';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import { deleteFrigateHostSchema, GetFrigateHost, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
import CenterLoader from '../shared/components/loaders/CenterLoader';
import RetryErrorPage from './RetryErrorPage';
import { observer } from 'mobx-react-lite';
import { useContext, useEffect, useRef, useState } from 'react';
import { Context } from '..';
import { strings } from '../shared/strings/strings';
import { Button, Flex } from '@mantine/core';
import { observer } from 'mobx-react-lite'
import { useAdminRole } from '../hooks/useAdminRole';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import { GetFrigateHost, deleteFrigateHostSchema, putFrigateHostSchema } from '../services/frigate.proxy/frigate.schema';
import CenterLoader from '../shared/components/loaders/CenterLoader';
import { isProduction } from '../shared/env.const';
import { strings } from '../shared/strings/strings';
import FrigateHostsTable from '../widgets/FrigateHostsTable';
import Forbidden from './403';
import RetryErrorPage from './RetryErrorPage';
import { notifications } from '@mantine/notifications';
import { IconAlertCircle } from '@tabler/icons-react';
const FrigateHostsPage = () => {
const executed = useRef(false)
@ -36,6 +40,10 @@ const FrigateHostsPage = () => {
if (data) setPageData(data)
}, [data])
useEffect(() => {
if (!isProduction) console.log('pageData', pageData)
}, [pageData])
const { mutate } = useMutation({
mutationFn: (tableData: GetFrigateHost[]) => {
let fetchPromises = []
@ -48,16 +56,29 @@ const FrigateHostsPage = () => {
const parsedChanged = putFrigateHostSchema.array().parse(tableData)
fetchPromises.push(frigateApi.putHosts(parsedChanged))
}
return Promise.all(fetchPromises)
return Promise.all(fetchPromises).catch(error => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data);
}
return Promise.reject(error);
})
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
},
onError: () => {
onError: (e) => {
if (e && e.message) {
notifications.show({
id: e.message,
withCloseButton: true,
autoClose: 5000,
title: "Error",
message: e.message,
color: 'red',
icon: <IconAlertCircle />,
})
}
queryClient.invalidateQueries({ queryKey: [frigateQueryKeys.getFrigateHosts] })
},
onSettled: () => {
if (data) setPageData([...data])
}
})
@ -79,18 +100,16 @@ const FrigateHostsPage = () => {
if (hostsPending || adminLoading) return <CenterLoader />
if (!isAdmin) return <Forbidden />
if (hostsError) return <RetryErrorPage />
if (!pageData) return <Text>Empty server response</Text>
return (
<div>
{
!pageData ? <></> :
<Flex w='100%' h='100%' direction='column'>
<FrigateHostsTable data={pageData} showAddButton changedCallback={handleChange} />
}
<Flex justify='center'>
<Button m='0.5rem' onClick={handleDiscard}>{strings.discard}</Button>
<Button m='0.5rem' onClick={handleSave}>{strings.save}</Button>
</Flex>
</div>
</Flex>
);
}

View File

@ -1,17 +1,16 @@
import { Button, Flex, Table } from '@mantine/core';
import { IconPlus, IconTrash } from '@tabler/icons-react';
import ObjectId from 'bson-objectid';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu';
import SortedTh from '../shared/components/table.aps/SortedTh';
import { isProduction } from '../shared/env.const';
import { strings } from '../shared/strings/strings';
import { debounce } from '../shared/utils/debounce';
import StateCell from './hosts.table/StateCell';
import SwitchCell from './hosts.table/SwitchCell';
import TextInputCell from './hosts.table/TextInputCell';
import { isProduction } from '../shared/env.const';
interface TableProps<T> {
data: T[],
@ -21,22 +20,22 @@ interface TableProps<T> {
}
const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps<GetFrigateHost>) => {
if (!isProduction) console.log('FrigateHostsTable rendered')
const [tableData, setTableData] = useState(data)
const [reversed, setReversed] = useState(false)
const [sortedName, setSortedName] = useState<string | null>(null)
useEffect(() => {
setTableData(data)
}, [data])
const debouncedChanged = useCallback(debounce((tableData: GetFrigateHost[]) => {
if (changedCallback) changedCallback(tableData)
}, 200), [tableData])
console.log('data', data)
console.log('tableData', tableData)
useEffect(() => {
debouncedChanged(tableData)
}, [tableData, debouncedChanged])
setTableData(data);
}, [data]);
useEffect(() => {
if (!isProduction) console.log('TableData', tableData)
if (changedCallback)
changedCallback(tableData)
}, [tableData])
function sortByKey<T, K extends keyof T>(array: T[], key: K): T[] {
return array.sort((a, b) => {
@ -80,7 +79,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
)
})
const handleTextChange = (id: string | number, propertyName: string, value: string,) => {
const handleTextChange = (id: string | number, propertyName: string, value: string | number | boolean | undefined,) => {
setTableData(tableData.map(item => {
if (item.id === id) {
return {
@ -116,7 +115,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
name: '',
enabled: true
}
setTableData(prevTableData => [...prevTableData, newHost])
setTableData([...tableData, newHost])
}
const rows = tableData.map(item => {
@ -135,7 +134,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
</tr>
)
})
if (!isProduction) console.log('FrigateHostsTable rendered')
return (
<div>
<Table >

View File

@ -1,6 +1,7 @@
import { Flex, Switch, useMantineTheme } from '@mantine/core';
import { IconBulbFilled, IconBulbOff } from '@tabler/icons-react';
import React from 'react';
import { boolean } from 'zod';
interface SwithCellProps {
value?: boolean,
@ -14,7 +15,7 @@ interface SwithCellProps {
export const SwitchCell = ( { value, defaultValue, width, id, propertyName, toggle }: SwithCellProps ) => {
const theme = useMantineTheme();
if (!value && !defaultValue) value = defaultValue
if (typeof value !== 'boolean') value = defaultValue
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (id && toggle && propertyName) toggle(id, propertyName, event.target.value)
}

View File

@ -1,5 +1,6 @@
import { TextInput } from '@mantine/core';
import React from 'react';
import { useDebouncedValue } from '@mantine/hooks';
import React, { useEffect, useState } from 'react';
interface TextImputCellProps {
text?: string | number | boolean,
@ -9,18 +10,25 @@ interface TextImputCellProps {
onChange?: (
id: string | number,
propertyName: string,
value: string,
value?: string | number | boolean,
) => void,
}
const TextInputCell = ({ text, width, id, propertyName, onChange }: TextImputCellProps) => {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const [value, setValue] = useState(text);
const [debouncedValue] = useDebouncedValue(value, 300)
useEffect(() => {
if (debouncedValue !== text) {
if (id && propertyName && onChange)
onChange(id, propertyName, event.currentTarget.value)
onChange(id, propertyName, debouncedValue)
}
}, [debouncedValue, id, propertyName, onChange, text])
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.currentTarget.value)
}
return (
<td style={{ width: width, textAlign: 'center' }}>
<TextInput onChange={handleChange} size='sm' value={String(text)} />
<TextInput onChange={handleChange} size='sm' value={String(value)} />
</td>
)
}