Skip to content

Commit

Permalink
useInterval hook for autosave
Browse files Browse the repository at this point in the history
Replace `useEffect` hooks with a `useInterval` hook that sets a timeout.
  • Loading branch information
eatyourgreens committed Mar 5, 2024
1 parent 2cc4429 commit 43a182b
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 88 deletions.
31 changes: 11 additions & 20 deletions frontend-v2/src/features/drug/Drug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ import {
import { useFieldArray, useForm, useFormState } from "react-hook-form";
import FloatField from "../../components/FloatField";
import UnitField from "../../components/UnitField";
import { FC, useEffect, useState } from "react";
import { FC, useCallback, useEffect, useMemo, useState } from "react";
import DeleteIcon from "@mui/icons-material/Delete";
import TextField from "../../components/TextField";
import useDirty from "../../hooks/useDirty";
import useInterval from "../../hooks/useInterval";
import ConfirmationDialog from "../../components/ConfirmationDialog";
import SelectField from "../../components/SelectField";
import { selectIsProjectShared } from "../login/loginSlice";


const Drug: FC = () => {
const projectId = useSelector(
(state: RootState) => state.main.selectedProject,
Expand Down Expand Up @@ -79,9 +81,7 @@ const Drug: FC = () => {
reset(compound);
}, [compound, reset]);



const submit = handleSubmit((data) => {
const submit = useMemo(() => handleSubmit((data) => {
if (data && compound && JSON.stringify(data) !== JSON.stringify(compound)) {
// strange bug in react-hook-form is creating efficancy_experiments with undefined compounds, remove these for now.
data.efficacy_experiments = data.efficacy_experiments.filter(
Expand All @@ -105,24 +105,15 @@ const Drug: FC = () => {
}
});
}
});

useEffect(() => {
const intervalId = setInterval(() => {
if (isDirty) {
submit();
}
}, 1000);
}), [compound, handleSubmit, updateCompound]);

return () => clearInterval(intervalId);
}, [submit, isDirty]);

useEffect(
() => () => {
const autoSaveCallback = useCallback(() => {
if (isDirty) {
submit();
},
[],
);
}
}, [isDirty, submit]);

useInterval(autoSaveCallback, 1000);

const addNewEfficacyExperiment = () => {
append([
Expand Down
33 changes: 12 additions & 21 deletions frontend-v2/src/features/model/ParameterRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { FC, useCallback, useEffect } from "react";
import { useForm } from "react-hook-form";
import { TableCell, TableRow, Tooltip, Typography } from "@mui/material";
import {
Expand All @@ -11,6 +11,7 @@ import {
} from "../../app/backendApi";
import UnitField from "../../components/UnitField";
import useDirty from "../../hooks/useDirty";
import useInterval from '../../hooks/useInterval';
import FloatField from "../../components/FloatField";
import { selectIsProjectShared } from "../login/loginSlice";
import { useSelector } from "react-redux";
Expand All @@ -23,7 +24,7 @@ interface Props {
units: UnitRead[];
}

const ParameterRow: React.FC<Props> = ({ project, model, variable, units }) => {
const ParameterRow: FC<Props> = ({ project, model, variable, units }) => {
const {
control,
handleSubmit,
Expand All @@ -41,27 +42,17 @@ const ParameterRow: React.FC<Props> = ({ project, model, variable, units }) => {

const isSharedWithMe = useSelector((state: RootState) => selectIsProjectShared(state, project));

const submit = handleSubmit((data) => {
if (JSON.stringify(data) !== JSON.stringify(variable)) {
updateVariable({ id: variable.id, variable: data });
}
});

useEffect(() => {
const intervalId = setInterval(() => {
if (isDirty) {
submit();
const autoSaveCallback = useCallback(() => {
const submit = handleSubmit((data) => {
if (JSON.stringify(data) !== JSON.stringify(variable)) {
updateVariable({ id: variable.id, variable: data });
}
}, 1000);
return () => clearInterval(intervalId);
}, [submit, isDirty]);

useEffect(
() => () => {
});
if (isDirty) {
submit();
},
[],
);
}
}, [isDirty, handleSubmit, updateVariable, variable]);
useInterval(autoSaveCallback, 1000);

if (variable.constant !== true) {
return null;
Expand Down
42 changes: 16 additions & 26 deletions frontend-v2/src/features/projects/Project.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// src/components/ProjectTable.tsx
import { FC, useEffect, useState } from "react";
import { FC, useCallback, useEffect, useState } from "react";
import { useForm, useFieldArray } from "react-hook-form";
import { useDispatch, useSelector } from "react-redux";
import VisibilityIcon from '@mui/icons-material/Visibility';
Expand Down Expand Up @@ -31,6 +31,7 @@ import UserAccess from "./UserAccess";
import { setProject } from "../main/mainSlice";
import TextField from "../../components/TextField";
import useDirty from "../../hooks/useDirty";
import useInterval from '../../hooks/useInterval';
import ConfirmationDialog from "../../components/ConfirmationDialog";
import { selectCurrentUser, selectIsProjectShared } from "../login/loginSlice";
import { RootState } from "../../app/store";
Expand Down Expand Up @@ -125,34 +126,23 @@ const ProjectRow: FC<Props> = ({
reset({ project, compound });
}, [project, compound, reset]);

const handleSave = handleSubmit((data: FormData) => {
if (compound && project) {
if (JSON.stringify(compound) !== JSON.stringify(data.compound)) {
updateCompound({ id: compound.id, compound: data.compound });
}
if (JSON.stringify(project) !== JSON.stringify(data.project)) {
updateProject({ id: project.id, project: data.project });
const autoSaveCallback = useCallback(() => {
const handleSave = handleSubmit((data: FormData) => {
if (compound && project) {
if (JSON.stringify(compound) !== JSON.stringify(data.compound)) {
updateCompound({ id: compound.id, compound: data.compound });
}
if (JSON.stringify(project) !== JSON.stringify(data.project)) {
updateProject({ id: project.id, project: data.project });
}
}
});
if (isDirty) {
handleSave();
}
});


useEffect(() => {
const intervalId = setInterval(() => {
if (isDirty) {
handleSave();
}
}, 1000);

return () => clearInterval(intervalId);
}, [handleSave, isDirty]);
}, [isDirty, handleSubmit, compound, project, updateCompound, updateProject]);

useEffect(
() => () => {
handleSave();
},
[],
);
useInterval(autoSaveCallback, 1000);

if (isLoading) {
return <div>Loading...</div>;
Expand Down
33 changes: 12 additions & 21 deletions frontend-v2/src/features/trial/Doses.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, useEffect } from "react";
import { FC, useCallback, useEffect } from "react";
import { TableCell, TableRow, IconButton, Button, Stack, Typography } from "@mui/material";
import {
ProjectRead,
Expand All @@ -14,6 +14,7 @@ import UnitField from "../../components/UnitField";
import FloatField from "../../components/FloatField";
import IntegerField from "../../components/IntegerField";
import useDirty from "../../hooks/useDirty";
import useInterval from '../../hooks/useInterval';
import HelpButton from "../../components/HelpButton";
import { useSelector } from "react-redux";
import { RootState } from "../../app/store";
Expand Down Expand Up @@ -56,29 +57,19 @@ const Doses: FC<Props> = ({ project, protocol, units }) => {
reset(protocol);
}, [protocol, reset]);

const handleSave = handleSubmit((data: Protocol) => {
if (JSON.stringify(data) !== JSON.stringify(protocol)) {
updateProtocol({ id: protocol.id, protocol: data });
const autosaveCallback = useCallback(() => {
const handleSave = handleSubmit((data: Protocol) => {
if (JSON.stringify(data) !== JSON.stringify(protocol)) {
updateProtocol({ id: protocol.id, protocol: data });
}
});
if (isDirty) {
handleSave();
}
});
}, [isDirty, handleSubmit, protocol, updateProtocol])

// save protocol every second if dirty
useEffect(() => {
const intervalId = setInterval(() => {
if (isDirty) {
handleSave();
}
}, 1000);

return () => clearInterval(intervalId);
}, [handleSave, isDirty]);

useEffect(
() => () => {
handleSave();
},
[],
);
useInterval(autosaveCallback, 1000);

if (isVariableLoading) {
return <div>Loading...</div>;
Expand Down
24 changes: 24 additions & 0 deletions frontend-v2/src/hooks/useInterval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect, useRef } from 'react';

export default function useInterval(callback: () => void, delay: number) {
const savedCallback = useRef<() => void>();

useEffect(() => {
savedCallback.current = callback;
}, [callback]);

useEffect(() => {
function tick() {
savedCallback.current!();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => {
if (savedCallback.current) {
savedCallback.current();
}
clearInterval(id);
};
}
}, [delay]);
}

0 comments on commit 43a182b

Please sign in to comment.