fix forms stale (#419)

* feat: create loader component

* fix: prevent stale data in forms
This commit is contained in:
Carlos Valente
2023-05-28 22:21:31 +02:00
committed by GitHub
parent f6e524e865
commit c075f73752
19 changed files with 189 additions and 26 deletions
@@ -5,7 +5,7 @@ import { ALIASES } from '../api/apiConstants';
import { getAliases } from '../api/ontimeApi';
export default function useAliases() {
const { data, status, isError, refetch } = useQuery({
const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: ALIASES,
queryFn: getAliases,
placeholderData: [],
@@ -15,5 +15,5 @@ export default function useAliases() {
networkMode: 'always',
});
return { data, status, isError, refetch };
}
return { data, status, isFetching, isError, refetch };
}
@@ -6,7 +6,7 @@ import { fetchEventData } from '../api/eventDataApi';
import { eventDataPlaceholder } from '../models/EventData';
export default function useEventData() {
const { data, status, isError, refetch } = useQuery({
const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: EVENT_DATA,
queryFn: fetchEventData,
placeholderData: eventDataPlaceholder,
@@ -16,5 +16,5 @@ export default function useEventData() {
networkMode: 'always',
});
return { data, status, isError, refetch };
return { data, status, isFetching, isError, refetch };
}
@@ -8,7 +8,7 @@ import { oscPlaceholderSettings } from '../models/OscSettings';
import { ontimeQueryClient } from '../queryClient';
export default function useOscSettings() {
const { data, status, isError, refetch } = useQuery({
const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: OSC_SETTINGS,
queryFn: getOSC,
placeholderData: oscPlaceholderSettings,
@@ -19,7 +19,7 @@ export default function useOscSettings() {
});
// we need to jump through some hoops because of the type op port
return { data: data! as unknown as OSCSettings, status, isError, refetch };
return { data: data! as unknown as OSCSettings, status, isFetching, isError, refetch };
}
export function useOscSettingsMutation() {
@@ -6,7 +6,7 @@ import { getSettings } from '../api/ontimeApi';
import { ontimePlaceholderSettings } from '../models/OntimeSettings';
export default function useSettings() {
const { data, status, isError, refetch } = useQuery({
const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: APP_SETTINGS,
queryFn: getSettings,
placeholderData: ontimePlaceholderSettings,
@@ -16,5 +16,5 @@ export default function useSettings() {
networkMode: 'always',
});
return { data, status, isError, refetch };
return { data, status, isFetching, isError, refetch };
}
@@ -6,7 +6,7 @@ import { getUserFields } from '../api/ontimeApi';
import { userFieldsPlaceholder } from '../models/UserFields';
export default function useUserFields() {
const { data, status, isError, refetch } = useQuery({
const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: USERFIELDS,
queryFn: getUserFields,
placeholderData: userFieldsPlaceholder,
@@ -16,5 +16,5 @@ export default function useUserFields() {
networkMode: 'always',
});
return { data, status, isError, refetch };
return { data, status, isFetching, isError, refetch };
}
@@ -6,7 +6,7 @@ import { getView } from '../api/ontimeApi';
import { viewsSettingsPlaceholder } from '../models/ViewSettings.type';
export default function useViewSettings() {
const { data, status, isError, refetch } = useQuery({
const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: VIEW_SETTINGS,
queryFn: getView,
placeholderData: viewsSettingsPlaceholder,
@@ -16,5 +16,5 @@ export default function useViewSettings() {
networkMode: 'always',
});
return { data, status, isError, refetch };
return { data, status, isError, refetch, isFetching };
}
@@ -10,7 +10,6 @@ interface ModalWrapperProps {
export default function ModalWrapper(props: PropsWithChildren<ModalWrapperProps>) {
const { isOpen, onClose, title, size = 'xl', children } = props;
return (
<Modal
onClose={onClose}
@@ -1,10 +1,11 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import type { OscSubscription } from 'ontime-types';
import { TimerLifeCycle } from 'ontime-types';
import useOscSettings, { usePostOscSubscriptions } from '../../../common/hooks-query/useOscSettings';
import { useEmitLog } from '../../../common/stores/logger';
import ModalLoader from '../modal-loader/ModalLoader';
import OntimeModalFooter from '../OntimeModalFooter';
import OscSubscriptionRow from './OscSubscriptionRow';
@@ -41,7 +42,7 @@ const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string
};
export default function OscIntegration() {
const { data } = useOscSettings();
const { data, isFetching } = useOscSettings();
const { mutateAsync } = usePostOscSubscriptions();
const { emitError } = useEmitLog();
const {
@@ -53,10 +54,19 @@ export default function OscIntegration() {
} = useForm<OscSubscription>({
defaultValues: data.subscriptions,
values: data.subscriptions,
resetOptions: {
keepDirtyValues: true,
},
});
const [showSection, setShowSection] = useState<OntimeCycle>(TimerLifeCycle.onLoad);
useEffect(() => {
if (data) {
reset(data.subscriptions);
}
}, [data, reset]);
const resetForm = () => {
reset(data.subscriptions);
};
@@ -78,6 +88,10 @@ export default function OscIntegration() {
}
};
if (isFetching) {
return <ModalLoader />;
}
return (
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='osc-subscriptions'>
<OscSubscriptionRow
@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { FormControl, Input, Switch } from '@chakra-ui/react';
@@ -5,12 +6,13 @@ import useOscSettings, { useOscSettingsMutation } from '../../../common/hooks-qu
import { PlaceholderSettings } from '../../../common/models/OscSettings';
import { useEmitLog } from '../../../common/stores/logger';
import { isIPAddress, isOnlyNumbers } from '../../../common/utils/regex';
import ModalLoader from '../modal-loader/ModalLoader';
import OntimeModalFooter from '../OntimeModalFooter';
import styles from '../Modal.module.scss';
export default function OscSettings() {
const { data } = useOscSettings();
const { data, isFetching } = useOscSettings();
const { mutateAsync } = useOscSettingsMutation();
const { emitError } = useEmitLog();
const {
@@ -22,8 +24,16 @@ export default function OscSettings() {
} = useForm<PlaceholderSettings>({
defaultValues: data,
values: data,
resetOptions: {
keepDirtyValues: true,
},
});
useEffect(() => {
if (data) {
reset(data);
}
}, [data, reset]);
const onSubmit = async (values: PlaceholderSettings) => {
const numericPortIn = Number(values.portIn);
const numericPortOut = Number(values.portOut);
@@ -51,6 +61,10 @@ export default function OscSettings() {
reset(data);
};
if (isFetching) {
return <ModalLoader />;
}
return (
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='oscSettings'>
<div className={styles.splitSection}>
@@ -0,0 +1,46 @@
@use '../../../theme/ontimeColours' as *;
.screenLoader {
position: absolute;
z-index: 10;
width: 100%;
height: 100%;
display: grid;
place-content: center;
top: 0;
background-color: $white-60;
}
$loader-size: 48px;
.loader {
width: $loader-size;
height: $loader-size;
background: $blue-500;
display: inline-block;
border-radius: 50%;
box-sizing: border-box;
animation: animloader 1s ease-in infinite;
}
@keyframes animloader {
0% {
transform: scale(0);
opacity: 0.6;
}
100% {
transform: scale(1);
opacity: 0;
}
}
@keyframes animloader {
0% {
transform: scale(0);
opacity: 0.6;
}
100% {
transform: scale(1);
opacity: 0;
}
}
@@ -0,0 +1,9 @@
import style from './ModalLoader.module.scss';
export default function ModalLoader() {
return (
<div className={style.screenLoader}>
<span className={style.loader} />
</div>
);
}
@@ -38,11 +38,16 @@ export default function QuickStart({ onClose, isOpen }: QuickStartProps) {
register,
reset,
formState: { isSubmitting },
} = useForm({ defaultValues: data });
} = useForm({
defaultValues: data,
resetOptions: {
keepDirtyValues: true,
},
});
useEffect(() => {
reset(data);
}, [reset, data]);
if (data) reset(data);
}, [data, reset]);
const onSubmit = async (data: Partial<EventData>) => {
try {
@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { Alert, AlertDescription, AlertIcon, AlertTitle, Button, IconButton, Input, Switch } from '@chakra-ui/react';
import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline';
@@ -9,6 +10,7 @@ import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBt
import useAliases from '../../../common/hooks-query/useAliases';
import { useEmitLog } from '../../../common/stores/logger';
import { handleLinks } from '../../../common/utils/linkUtils';
import ModalLoader from '../modal-loader/ModalLoader';
import { inputProps } from '../modalHelper';
import ModalLink from '../ModalLink';
import OntimeModalFooter from '../OntimeModalFooter';
@@ -21,7 +23,7 @@ const aliasesDocsUrl = 'https://ontime.gitbook.io/v2/features/url-aliases';
type Aliases = { aliases: Alias[] };
export default function AliasesForm() {
const { data, status, refetch } = useAliases();
const { data, status, isFetching, refetch } = useAliases();
const { emitError } = useEmitLog();
const {
control,
@@ -32,12 +34,21 @@ export default function AliasesForm() {
} = useForm<Aliases>({
defaultValues: { aliases: data },
values: { aliases: data || [] },
resetOptions: {
keepDirtyValues: true,
},
});
const { fields, append, remove } = useFieldArray({
name: 'aliases',
control,
});
useEffect(() => {
if (data) {
reset(data);
}
}, [data, reset]);
const onSubmit = async (formData: Aliases) => {
try {
await postAliases(formData.aliases);
@@ -67,6 +78,10 @@ export default function AliasesForm() {
const disableInputs = status === 'loading';
const hasTooManyOptions = fields.length >= 20;
if (isFetching) {
return <ModalLoader />;
}
return (
<form onSubmit={handleSubmit(onSubmit)} id='aliases' className={style.sectionContainer}>
<div style={{ height: '16px' }} />
@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Input, Select } from '@chakra-ui/react';
import type { Settings } from 'ontime-types';
@@ -6,6 +7,7 @@ import { postSettings } from '../../../common/api/ontimeApi';
import useSettings from '../../../common/hooks-query/useSettings';
import { useEmitLog } from '../../../common/stores/logger';
import { isOnlyNumbers } from '../../../common/utils/regex';
import ModalLoader from '../modal-loader/ModalLoader';
import ModalSplitInput from '../ModalSplitInput';
import OntimeModalFooter from '../OntimeModalFooter';
@@ -14,7 +16,7 @@ import ModalPinInput from './ModalPinInput';
import style from './SettingsModal.module.scss';
export default function AppSettingsModal() {
const { data, status, refetch } = useSettings();
const { data, status, isFetching, refetch } = useSettings();
const { emitError } = useEmitLog();
const {
handleSubmit,
@@ -24,8 +26,17 @@ export default function AppSettingsModal() {
} = useForm<Settings>({
defaultValues: data,
values: data,
resetOptions: {
keepDirtyValues: true,
},
});
useEffect(() => {
if (data) {
reset(data);
}
}, [data, reset]);
const onSubmit = async (formData: Settings) => {
try {
await postSettings(formData);
@@ -42,6 +53,10 @@ export default function AppSettingsModal() {
const disableInputs = status === 'loading';
if (isFetching) {
return <ModalLoader />;
}
return (
<form onSubmit={handleSubmit(onSubmit)} id='app-settings' className={style.sectionContainer}>
<ModalSplitInput
@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Alert, AlertDescription, AlertIcon, AlertTitle, Input } from '@chakra-ui/react';
import { UserFields } from 'ontime-types';
@@ -5,6 +6,7 @@ import { UserFields } from 'ontime-types';
import { postUserFields } from '../../../common/api/ontimeApi';
import useUserFields from '../../../common/hooks-query/useUserFields';
import { useEmitLog } from '../../../common/stores/logger';
import ModalLoader from '../modal-loader/ModalLoader';
import { inputProps } from '../modalHelper';
import ModalLink from '../ModalLink';
import ModalSplitInput from '../ModalSplitInput';
@@ -15,7 +17,7 @@ import style from './SettingsModal.module.scss';
const userFieldsDocsUrl = 'https://ontime.gitbook.io/v2/features/user-fields';
export default function CuesheetSettings() {
const { data, status, refetch } = useUserFields();
const { data, status, isFetching, refetch } = useUserFields();
const { emitError } = useEmitLog();
const {
handleSubmit,
@@ -25,8 +27,17 @@ export default function CuesheetSettings() {
} = useForm<UserFields>({
defaultValues: data,
values: data,
resetOptions: {
keepDirtyValues: true,
},
});
useEffect(() => {
if (data) {
reset(data);
}
}, [data, reset]);
const onSubmit = async (formData: UserFields) => {
try {
await postUserFields(formData);
@@ -43,6 +54,10 @@ export default function CuesheetSettings() {
const disableInputs = status === 'loading';
if (isFetching) {
return <ModalLoader />;
}
return (
<form onSubmit={handleSubmit(onSubmit)} id='cuesheet-settings' className={style.sectionContainer}>
<div style={{ height: '16px' }} />
@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Input, Textarea } from '@chakra-ui/react';
import { EventData } from 'ontime-types';
@@ -5,6 +6,7 @@ import { EventData } from 'ontime-types';
import { postEventData } from '../../../common/api/eventDataApi';
import useEventData from '../../../common/hooks-query/useEventData';
import { useEmitLog } from '../../../common/stores/logger';
import ModalLoader from '../modal-loader/ModalLoader';
import { inputProps } from '../modalHelper';
import ModalInput from '../ModalInput';
import OntimeModalFooter from '../OntimeModalFooter';
@@ -12,7 +14,7 @@ import OntimeModalFooter from '../OntimeModalFooter';
import style from './SettingsModal.module.scss';
export default function EventDataForm() {
const { data, status, refetch } = useEventData();
const { data, status, isFetching, refetch } = useEventData();
const { emitError } = useEmitLog();
const {
handleSubmit,
@@ -22,8 +24,17 @@ export default function EventDataForm() {
} = useForm<EventData>({
defaultValues: data,
values: data,
resetOptions: {
keepDirtyValues: true,
},
});
useEffect(() => {
if (data) {
reset(data);
}
}, [data, reset]);
const onSubmit = async (formData: EventData) => {
try {
await postEventData(formData);
@@ -40,6 +51,10 @@ export default function EventDataForm() {
const disableInputs = status === 'loading';
if (isFetching) {
return <ModalLoader />;
}
return (
<form onSubmit={handleSubmit(onSubmit)} id='event-data' className={style.sectionContainer}>
<ModalInput
@@ -18,4 +18,4 @@
.grow {
flex: 1;
}
}
}
@@ -1,3 +1,4 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Input, Switch } from '@chakra-ui/react';
import { ViewSettings } from 'ontime-types';
@@ -10,6 +11,7 @@ import { mtm } from '../../../common/utils/timeConstants';
import { inputProps } from '../modalHelper';
import ModalInput from '../ModalInput';
import ModalSplitInput from '../ModalSplitInput';
import ModalLoader from '../modal-loader/ModalLoader';
import OntimeModalFooter from '../OntimeModalFooter';
import InputMillisWithString from './InputMillisWithString';
@@ -17,7 +19,7 @@ import InputMillisWithString from './InputMillisWithString';
import style from './SettingsModal.module.scss';
export default function ViewSettingsForm() {
const { data, status, refetch } = useViewSettings();
const { data, status, refetch, isFetching } = useViewSettings();
const { emitError } = useEmitLog();
const {
control,
@@ -28,8 +30,17 @@ export default function ViewSettingsForm() {
} = useForm<ViewSettings>({
defaultValues: data,
values: data,
resetOptions: {
keepDirtyValues: true,
},
});
useEffect(() => {
if (data) {
reset(data);
}
}, [data, reset]);
const onSubmit = async (formData: ViewSettings) => {
const parsedWarningThreshold = dirtyFields?.warningThreshold
? // @ts-expect-error -- trust me
@@ -65,6 +76,10 @@ export default function ViewSettingsForm() {
const disableInputs = status === 'loading';
if (isFetching) {
return <ModalLoader />;
}
return (
<form onSubmit={handleSubmit(onSubmit)} id='view-settings' className={style.sectionContainer}>
<span className={style.title}>General view settings</span>
@@ -7,6 +7,7 @@ $white-9: rgba(255, 255, 255, 0.09);
$white-10: rgba(255, 255, 255, 0.10);
$white-13: rgba(255, 255, 255, 0.13);
$white-20: rgba(255, 255, 255, 0.20);
$white-60: rgba(255, 255, 255, 0.60);
$black-10: rgba(0, 0, 0, 0.10);