fix: show loading component state while images arent fully loaded (#2609)

*  (ProfilePictureForm): add loading state to handle initial loading state

* ♻️ (use-get-profile-pictures.ts): refactor profile pictures query to process data on the server side
♻️ (ProfilePictureForm): simplify state management by removing redundant loading state

* ♻️ (use-get-profile-pictures.ts): rename ProfilePicturesResponse to ProfilePicturesQueryResponse for clarity
♻️ (use-preload-images.tsx): add loading check to useEffect to prevent unnecessary execution
♻️ (profilePictureChooserComponent): update profilePictures prop type to handle undefined and add loading to usePreloadImages
♻️ (ProfilePictureForm): remove unnecessary state and use response directly from useGetProfilePicturesQuery

* 🐛 (use-preload-images.tsx): add missing dependency 'loading' to useEffect dependency array to ensure images are preloaded correctly
This commit is contained in:
Cristhian Zanforlin Lousa 2024-07-09 19:57:46 -03:00 committed by GitHub
commit 7174e6ef7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 39 additions and 45 deletions

View file

@ -6,31 +6,46 @@ import { UseRequestProcessor } from "../../services/request-processor";
interface ProfilePicturesQueryParams {}
export interface ProfilePicturesResponse {
export interface ProfilePicturesQueryResponse {
files: string[];
}
export const useGetProfilePicturesQuery: useQueryFunctionType<
ProfilePicturesQueryParams,
ProfilePicturesResponse
{ [key: string]: string[] }
> = () => {
const { query } = UseRequestProcessor();
const getProfilePicturesFn = async () => {
const response = await api.get<ProfilePicturesResponse>(
`${getURL("FILES")}/profile_pictures/list`,
);
const getProfilePicturesFn =
async (): Promise<ProfilePicturesQueryResponse> => {
const response = await api.get<ProfilePicturesQueryResponse>(
`${getURL("FILES")}/profile_pictures/list`,
);
return response.data;
return response.data;
};
const responseFn = async () => {
const data = await getProfilePicturesFn();
const profilePictures = {};
data?.files?.forEach((profile_picture) => {
const [folder, path] = profile_picture.split("/");
if (profilePictures[folder]) {
profilePictures[folder].push(path);
} else {
profilePictures[folder] = [path];
}
});
return profilePictures;
};
const queryResult = query(
["useGetProfilePicturesQuery"],
getProfilePicturesFn,
{
placeholderData: keepPreviousData,
},
);
const queryResult = query(["useGetProfilePicturesQuery"], responseFn, {
placeholderData: keepPreviousData,
});
return queryResult;
};

View file

@ -4,6 +4,7 @@ import { BASE_URL_API } from "../../../../../../../../../constants/constants";
const usePreloadImages = (
profilePictures: { [key: string]: string[] },
setImagesLoaded: (value: boolean) => void,
loading: boolean,
) => {
const preloadImages = async (imageUrls) => {
return Promise.all(
@ -20,6 +21,7 @@ const usePreloadImages = (
};
useEffect(() => {
if (loading) return;
const imageArray: string[] = [];
Object.keys(profilePictures).flatMap((folder) =>
@ -33,7 +35,7 @@ const usePreloadImages = (
preloadImages(imageArray).then(() => {
setImagesLoaded(true);
});
}, [profilePictures]);
}, [profilePictures, loading]);
return;
};

View file

@ -7,7 +7,7 @@ import { cn } from "../../../../../../../../utils/utils";
import usePreloadImages from "./hooks/use-preload-images";
type ProfilePictureChooserComponentProps = {
profilePictures: { [key: string]: string[] };
profilePictures: { [key: string]: string[] } | undefined;
loading: boolean;
value: string;
onChange: (value: string) => void;
@ -29,21 +29,21 @@ export default function ProfilePictureChooserComponent({
}
}, [ref, value]);
usePreloadImages(profilePictures, setImagesLoaded);
usePreloadImages(profilePictures!, setImagesLoaded, loading);
return (
<div className="flex flex-col justify-center gap-2">
{loading || !imagesLoaded ? (
<Loading />
) : (
Object.keys(profilePictures).map((folder, idx) => (
Object.keys(profilePictures!).map((folder, idx) => (
<div className="flex flex-col gap-2">
<div className="edit-flow-arrangement">
<span className="font-normal">{folder}</span>
</div>
<div className="block overflow-hidden">
<div className="flex items-center gap-1 overflow-x-auto rounded-lg bg-muted px-1 custom-scroll">
{profilePictures[folder].map((path, idx) => (
{profilePictures![folder].map((path, idx) => (
<Button
ref={value === folder + "/" + path ? ref : undefined}
unstyled

View file

@ -1,7 +1,4 @@
import {
ProfilePicturesResponse,
useGetProfilePicturesQuery,
} from "@/controllers/API/queries/files";
import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files";
import * as Form from "@radix-ui/react-form";
import { useEffect, useState } from "react";
import { Button } from "../../../../../../components/ui/button";
@ -20,7 +17,7 @@ type ProfilePictureFormComponentProps = {
profilePicture: string;
handleInput: (event: any) => void;
handlePatchProfilePicture: (gradient: string) => void;
handleGetProfilePictures: () => ProfilePicturesResponse | undefined;
handleGetProfilePictures: () => undefined;
userData: any;
};
const ProfilePictureFormComponent = ({
@ -30,28 +27,8 @@ const ProfilePictureFormComponent = ({
handleGetProfilePictures,
userData,
}: ProfilePictureFormComponentProps) => {
const [profilePictures, setProfilePictures] = useState<{
[key: string]: string[];
}>({});
const { data: response, isFetching } = useGetProfilePicturesQuery({});
useEffect(() => {
if (response?.files) {
response?.files?.forEach((profile_picture) => {
const [folder, path] = profile_picture.split("/");
setProfilePictures((prev) => {
if (prev[folder]) {
prev[folder].push(path);
} else {
prev[folder] = [path];
}
return prev;
});
});
}
}, [response]);
return (
<Form.Root
onSubmit={(event) => {
@ -69,7 +46,7 @@ const ProfilePictureFormComponent = ({
<CardContent>
<div className="py-2">
<ProfilePictureChooserComponent
profilePictures={profilePictures}
profilePictures={response}
loading={isFetching}
value={
profilePicture == ""