TanStackT
TanStack3y ago
2 replies
technological-jade

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.

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 />
  );
};
Was this page helpful?