Openstatus www.openstatus.dev

fix: hydration errors (#1507)

authored by

Maximilian Kaske and committed by
GitHub
cc877dae ab02e206

+28 -14
+2 -2
apps/status-page/src/app/(public)/client.tsx
··· 141 141 data-active={k === t} 142 142 data-slot="theme-card" 143 143 data-theme={k} 144 - className="relative h-40 cursor-pointer overflow-hidden rounded-md border outline-none transition-all focus:outline-ring/50 focus:ring-2 focus:ring-ring/50 data-[active=true]:border-ring data-[active=true]:outline-[3px] data-[active=true]:outline-ring/50" 144 + className="relative h-40 cursor-pointer overflow-hidden rounded-md border border-border outline-none transition-all focus:outline-ring/50 focus:ring-2 focus:ring-ring/50 data-[active=true]:border-ring data-[active=true]:outline-[3px] data-[active=true]:outline-ring/50" 145 145 onClick={() => setSearchParams({ t: k })} 146 146 role="button" 147 147 tabIndex={0} ··· 165 165 </div> 166 166 <div className="flex items-start justify-between gap-2"> 167 167 <div className="space-y-0.5 truncate"> 168 - <div className="font-medium text-foreground text-sm leading-none truncate"> 168 + <div className="truncate font-medium text-foreground text-sm leading-none"> 169 169 {theme.name} 170 170 </div> 171 171 <div className="font-mono text-xs">
+15 -2
apps/status-page/src/components/nav/footer.tsx
··· 3 3 import { Link } from "@/components/common/link"; 4 4 import { TimestampHoverCard } from "@/components/content/timestamp-hover-card"; 5 5 import { ThemeDropdown } from "@/components/themes/theme-dropdown"; 6 + import { Skeleton } from "@/components/ui/skeleton"; 6 7 import { useTRPC } from "@/lib/trpc/client"; 7 8 import { useQuery } from "@tanstack/react-query"; 8 9 import { Clock } from "lucide-react"; 9 10 import { useParams } from "next/navigation"; 11 + import { useEffect, useState } from "react"; 10 12 11 13 export function Footer(props: React.ComponentProps<"footer">) { 12 14 const { domain } = useParams<{ domain: string }>(); 15 + const [isMounted, setIsMounted] = useState(false); 13 16 const trpc = useTRPC(); 14 17 const { data: page, dataUpdatedAt } = useQuery( 15 18 trpc.statusPage.get.queryOptions({ slug: domain }), 16 19 ); 17 20 const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 21 + 22 + useEffect(() => { 23 + setIsMounted(true); 24 + }, []); 18 25 19 26 if (!page) return null; 20 27 ··· 40 47 align="end" 41 48 className="flex items-center gap-1.5 text-muted-foreground/70" 42 49 > 43 - <Clock className="size-3" /> 44 - <span className="font-mono text-xs">{timezone}</span> 50 + {isMounted ? ( 51 + <> 52 + <Clock className="size-3" /> 53 + <span className="font-mono text-xs">{timezone}</span> 54 + </> 55 + ) : ( 56 + <Skeleton className="h-4 w-28" /> 57 + )} 45 58 </TimestampHoverCard> 46 59 <ThemeDropdown /> 47 60 </div>
+11 -10
apps/status-page/src/components/themes/theme-select.tsx
··· 10 10 SelectTrigger, 11 11 SelectValue, 12 12 } from "@/components/ui/select"; 13 + import { Skeleton } from "@/components/ui/skeleton"; 13 14 import { cn } from "@/lib/utils"; 14 15 import { Laptop, Moon, Sun } from "lucide-react"; 15 16 import { useState } from "react"; ··· 26 27 setMounted(true); 27 28 }, []); 28 29 29 - // NOTE: hydration error if we don't do this 30 30 if (!mounted) { 31 - return ( 32 - <Select> 33 - <SelectTrigger className={cn("w-[180px]", className)} {...props}> 34 - <SelectValue placeholder="Select theme" /> 35 - </SelectTrigger> 36 - </Select> 37 - ); 31 + return <Skeleton className="h-9 rounded-md border border-border" />; 38 32 } 39 33 40 34 return ( 41 35 <Select value={theme} onValueChange={setTheme}> 42 - <SelectTrigger className={cn("w-[180px]", className)} {...props}> 43 - <SelectValue defaultValue={theme} placeholder="Select theme" /> 36 + <SelectTrigger 37 + className={cn("min-w-[110px] max-w-[110px]", className)} 38 + {...props} 39 + > 40 + <SelectValue 41 + className="w-full" 42 + defaultValue={theme} 43 + placeholder="Select theme" 44 + /> 44 45 </SelectTrigger> 45 46 <SelectContent> 46 47 <SelectItem value="light">