Mutation in provider
I'd like to save some database bandwidth so I decided to debounced the save.
I'm passing the localstorage version of my data to children of provider and mutate on debounced value after 3 seconds.
The problem is after 3 seconds the mutation is triggered and cause a rerender of my provider and all the child components, which is an huge performance issue.
Maybe I'm doing it the wrong way but I'm looking for a solution.
PS : I don't need mutation result.
I'm passing the localstorage version of my data to children of provider and mutate on debounced value after 3 seconds.
The problem is after 3 seconds the mutation is triggered and cause a rerender of my provider and all the child components, which is an huge performance issue.
Maybe I'm doing it the wrong way but I'm looking for a solution.
PS : I don't need mutation result.
export const UserTrainingProvider: FC<PropsWithChildren> = ({ children }) => {
const [userTrainingLocal, setUserTrainingLocal] = useLocalStorage<UserTraining>(
"lms-training-user",
defaultUserTraining
);
const [isLoadingUserTraining, setIsLoadingUserTraining] = useState(true);
const { internalId } = useParams();
const {user: {roles, username}} = useAuth(); // prettier-ignore
if (!internalId || !username || !roles)
throw new LMSUnExpectedBehavior("Impossible d'avoir accès à un cours sans être authentifié", 400);
const productInternalId = Base64.decode(internalId);
const {
data: userTrainingFromAPI,
isLoading: isLoadingUserTrainingFromAPI,
isError,
} = trpcClient.user.getTraining.useQuery({ productInternalId, userIdentifier: username });
const { data: training, isLoading: isLoadingTraining } = trpcClient.training.findOneByProductInternalId.useQuery(
{ productInternalId },
{ enabled: (!isLoadingUserTrainingFromAPI && !userTrainingFromAPI) || isError }
);
const updateUserProgressInTreeMutation = trpcClient.user.saveUserProgressionOnTraining.useMutation({
onError(error: unknown) {
throw new LMSUnExpectedBehavior(
"Une erreur s'est produite durant le sauvegarde en base de données de la progression de l'utilisateur",
400,
{ error }
);
},
});
// Can use debounce hook from io-ts as well
useEffect(() => {
const timer = setTimeout(() => updateUserProgressInTreeMutation.mutate(userTrainingLocal), 3000);
return () => {
clearTimeout(timer);
};
}, [userTrainingLocal]);
const handleUpdateUserTraining = (userTraining: UserTraining) => {
const newUserTraining = { ...userTraining, updatedAt: now().toISOString() };
const userTrainingToSave = produce(userTraining, () => JSON.parse(JSON.stringify(newUserTraining)));
setUserTrainingLocal(userTrainingToSave);
};
const checkIfUserTrainingInLocalStorageIsMoreRecentThanDatabase = (
userTrainingLocalStorage: UserTraining,
userTrainingFromAPI: UserTraining
): boolean => {
return dayjsFr(userTrainingLocalStorage.updatedAt).isAfter(dayjsFr(userTrainingFromAPI.updatedAt));
};
const createEmptyUserTrainingMutation = trpcClient.user.buildEmptyUserTraining.useMutation({
onSuccess(userTraining: UserTraining) {
handleUpdateUserTraining(userTraining);
setIsLoadingUserTraining(false);
},
});
useEffect(() => {
if (userTrainingFromAPI) {
if (checkIfUserTrainingInLocalStorageIsMoreRecentThanDatabase(userTrainingLocal, userTrainingFromAPI)) {
handleUpdateUserTraining(userTrainingLocal);
}
setIsLoadingUserTraining(false);
}
}, [userTrainingFromAPI]);
useEffect(() => {
if (!isLoadingUserTrainingFromAPI && !userTrainingFromAPI && !isLoadingTraining && training) {
createEmptyUserTrainingMutation.mutate({ training, username });
}
}, [training]);
return !isLoadingUserTraining ? (
<UserTrainingContext.Provider
value={{ userTraining: userTrainingLocal, updateUserTraining: handleUpdateUserTraining }}
>
{children}
</UserTrainingContext.Provider>
) : (
<Loader />
);
};export const UserTrainingProvider: FC<PropsWithChildren> = ({ children }) => {
const [userTrainingLocal, setUserTrainingLocal] = useLocalStorage<UserTraining>(
"lms-training-user",
defaultUserTraining
);
const [isLoadingUserTraining, setIsLoadingUserTraining] = useState(true);
const { internalId } = useParams();
const {user: {roles, username}} = useAuth(); // prettier-ignore
if (!internalId || !username || !roles)
throw new LMSUnExpectedBehavior("Impossible d'avoir accès à un cours sans être authentifié", 400);
const productInternalId = Base64.decode(internalId);
const {
data: userTrainingFromAPI,
isLoading: isLoadingUserTrainingFromAPI,
isError,
} = trpcClient.user.getTraining.useQuery({ productInternalId, userIdentifier: username });
const { data: training, isLoading: isLoadingTraining } = trpcClient.training.findOneByProductInternalId.useQuery(
{ productInternalId },
{ enabled: (!isLoadingUserTrainingFromAPI && !userTrainingFromAPI) || isError }
);
const updateUserProgressInTreeMutation = trpcClient.user.saveUserProgressionOnTraining.useMutation({
onError(error: unknown) {
throw new LMSUnExpectedBehavior(
"Une erreur s'est produite durant le sauvegarde en base de données de la progression de l'utilisateur",
400,
{ error }
);
},
});
// Can use debounce hook from io-ts as well
useEffect(() => {
const timer = setTimeout(() => updateUserProgressInTreeMutation.mutate(userTrainingLocal), 3000);
return () => {
clearTimeout(timer);
};
}, [userTrainingLocal]);
const handleUpdateUserTraining = (userTraining: UserTraining) => {
const newUserTraining = { ...userTraining, updatedAt: now().toISOString() };
const userTrainingToSave = produce(userTraining, () => JSON.parse(JSON.stringify(newUserTraining)));
setUserTrainingLocal(userTrainingToSave);
};
const checkIfUserTrainingInLocalStorageIsMoreRecentThanDatabase = (
userTrainingLocalStorage: UserTraining,
userTrainingFromAPI: UserTraining
): boolean => {
return dayjsFr(userTrainingLocalStorage.updatedAt).isAfter(dayjsFr(userTrainingFromAPI.updatedAt));
};
const createEmptyUserTrainingMutation = trpcClient.user.buildEmptyUserTraining.useMutation({
onSuccess(userTraining: UserTraining) {
handleUpdateUserTraining(userTraining);
setIsLoadingUserTraining(false);
},
});
useEffect(() => {
if (userTrainingFromAPI) {
if (checkIfUserTrainingInLocalStorageIsMoreRecentThanDatabase(userTrainingLocal, userTrainingFromAPI)) {
handleUpdateUserTraining(userTrainingLocal);
}
setIsLoadingUserTraining(false);
}
}, [userTrainingFromAPI]);
useEffect(() => {
if (!isLoadingUserTrainingFromAPI && !userTrainingFromAPI && !isLoadingTraining && training) {
createEmptyUserTrainingMutation.mutate({ training, username });
}
}, [training]);
return !isLoadingUserTraining ? (
<UserTrainingContext.Provider
value={{ userTraining: userTrainingLocal, updateUserTraining: handleUpdateUserTraining }}
>
{children}
</UserTrainingContext.Provider>
) : (
<Loader />
);
};