Loading State & Initial Hydration
Use isLoading to wait for asynchronousinitial files to hydrate before rendering UI or enabling actions. You can also subscribe imperatively via ref.current.onLoadingChange.
Declarative gating with isLoading
Render a skeleton while initial files are loading, then render your preview once hydration is complete.
1import {2 UplofileRoot,3 UplofileTrigger,4 UplofilePreview,5} from "@/components/ui/uplofile";6import { mockUpload } from "@/lib/utils.ts";78export default function LoadingStateDeclarativeDemo() {9 return (10 <UplofileRoot11 upload={mockUpload}12 initial={loadInitial()}13 accept="image/*"14 multiple15 >16 <UplofileTrigger17 render={({ isLoading, open }) => (18 <button19 onClick={open}20 disabled={isLoading}21 className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 shadow"22 >23 {isLoading ? "Loading initial files…" : "Select Images"}24 </button>25 )}26 />2728 <div className="mt-6">29 <UplofilePreview30 render={({ isLoading, items }) => {31 if (isLoading) {32 return (33 <div className="grid grid-cols-3 gap-3">34 {Array.from({ length: 3 }).map((_, i) => (35 <div36 key={i}37 className="aspect-square rounded-lg bg-muted animate-pulse"38 />39 ))}40 </div>41 );42 }4344 return (45 <div className="grid grid-cols-3 gap-3">46 {items.map((item) => (47 <img48 key={item.uid}49 src={item.url || item.previewUrl}50 alt={item.name}51 className="aspect-square w-full h-full object-cover rounded-lg border"52 />53 ))}54 </div>55 );56 }}57 />58 </div>59 </UplofileRoot>60 );61}6263// Simulate async initial files hydration from server64const loadInitial = () =>65 new Promise<Array<{ uid: string; name: string; url: string }>>((resolve) => {66 setTimeout(67 () =>68 resolve([69 {70 uid: "srv-1",71 name: "server-image.jpg",72 url: "https://picsum.photos/id/237/600/400",73 },74 ]),75 3500,76 );77 });
Imperative subscription via ref.onLoadingChange
Subscribe to loading changes using an imperative ref, and toggle your own UI state when hydration completes.
Hydrating initial files…
1import { useEffect, useRef, useState } from "react";2import type { UplofileRootRef } from "uplofile";3import {4 UplofileRoot,5 UplofilePreview,6 UplofileTrigger,7} from "@/components/ui/uplofile";8import { mockUpload } from "@/lib/utils.ts";9import { twMerge } from "tailwind-merge";1011export default function LoadingStateImperativeDemo() {12 const rootRef = useRef<UplofileRootRef>(null);13 const [ready, setReady] = useState(false);1415 useEffect(() => {16 // Subscribe imperatively to hydration status17 if (!rootRef.current) return;18 rootRef.current.onLoadingChange = (loading) => {19 setReady(!loading);20 };21 }, []);2223 return (24 <div className="space-y-4">25 <div className="flex items-center gap-2 text-sm">26 <div27 className={`h-2 w-2 rounded-full ${ready ? "bg-emerald-500" : "bg-amber-500 animate-pulse"}`}28 />29 <span>{ready ? "Hydrated • ready" : "Hydrating initial files…"}</span>30 </div>3132 <UplofileRoot33 ref={rootRef}34 upload={mockUpload}35 initial={loadInitial()}36 accept="image/*"37 >38 <UplofileTrigger39 render={({ open }) => (40 <button41 onClick={open}42 className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 shadow"43 >44 Add more45 </button>46 )}47 />48 <div className={"grid mt-6 gap-3 items-center grid-cols-3"}>49 <UplofilePreview className="!block" />50 {Array.from({ length: 3 }).map((_, i) => (51 <div52 key={i}53 className={twMerge(54 "aspect-square rounded-lg bg-muted animate-pulse",55 ready && "hidden",56 )}57 />58 ))}59 </div>60 </UplofileRoot>61 </div>62 );63}6465const loadInitial = () =>66 new Promise<Array<{ uid: string; name: string; url: string }>>((resolve) => {67 setTimeout(68 () =>69 resolve([70 {71 uid: "srv-2",72 name: "hydrated-from-server.jpg",73 url: "https://picsum.photos/id/1025/600/400",74 },75 ]),76 6000,77 );78 });
Form integration: disable submit until ready
Prevent premature form submission by disabling the submit button until initial files finish hydrating.
1import {2 UplofileRoot,3 UplofileHiddenInput,4 UplofilePreview,5 UplofileTrigger,6} from "@/components/ui/uplofile";7import { mockUpload } from "@/lib/utils.ts";8import { IoSendOutline, IoReloadOutline } from "react-icons/io5";910export default function LoadingStateFormDemo() {11 const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {12 e.preventDefault();13 const fd = new FormData(e.currentTarget);14 alert("Submitted! Check console for payload.");15 console.log(Object.fromEntries(fd));16 };1718 return (19 <form className="space-y-4" onSubmit={handleSubmit}>20 <UplofileRoot21 upload={mockUpload}22 initial={loadInitial()}23 name="attachments"24 >25 <UplofileHiddenInput />2627 <UplofileTrigger28 render={({ isLoading, open }) => (29 <button30 type="button"31 onClick={open}32 disabled={isLoading}33 className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 shadow"34 >35 {isLoading ? (36 <span className="inline-flex items-center gap-2">37 <IoReloadOutline className="h-4 w-4 animate-spin" /> Preparing…38 </span>39 ) : (40 "Add attachments"41 )}42 </button>43 )}44 />4546 <UplofilePreview47 render={({ items, isLoading }) => (48 <div className="mt-4 space-y-2">49 {isLoading && (50 <p className="text-xs text-muted-foreground">51 Hydrating initial attachments…52 </p>53 )}54 {items.map((it) => (55 <div key={it.uid} className="text-sm text-muted-foreground">56 • {it.name}57 </div>58 ))}59 </div>60 )}61 />6263 <div className="pt-2 border-t mt-4">64 <UplofileTrigger65 render={({ isLoading }) => (66 <button67 type="submit"68 disabled={isLoading}69 className="w-full inline-flex items-center justify-center gap-2 rounded-lg text-sm font-bold ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-gray-900 text-white hover:bg-gray-800 h-11 px-8 shadow-lg"70 >71 <IoSendOutline className="h-4 w-4" />72 {isLoading ? "Please wait…" : "Submit"}73 </button>74 )}75 />76 </div>77 </UplofileRoot>78 </form>79 );80}8182const loadInitial = () =>83 new Promise<Array<{ uid: string; name: string; url: string }>>((resolve) => {84 setTimeout(85 () =>86 resolve([87 {88 uid: "srv-3",89 name: "doc-from-server.pdf",90 url: "https://example.com/doc-from-server.pdf",91 },92 ]),93 6000,94 );95 });