The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord

perf: memorize values and eliminate states

shi.gg ad5ab34a 10125e3d

verified
Changed files
+98 -107
app
dashboard
[guildId]
custom-commands
starboard
login
[social]
components
+4 -4
app/dashboard/[guildId]/custom-commands/page.tsx
··· 48 48 return params.toString(); 49 49 }, [search]); 50 50 51 + const setTagId = (id: string) => { 52 + router.push(pathname + "?" + createQueryString("id", id)); 53 + }; 54 + 51 55 useEffect(() => { 52 56 if (!Array.isArray(data)) return; 53 57 if (data && !tag && data[0]) setTagId(data[0].id); ··· 69 73 } 70 74 71 75 if (isLoading || !data) return <></>; 72 - 73 - const setTagId = (id: string) => { 74 - router.push(pathname + "?" + createQueryString("id", id)); 75 - }; 76 76 77 77 const editTag = <T extends keyof ApiV1GuildsModulesTagsGetResponse>(k: keyof ApiV1GuildsModulesTagsGetResponse, value: ApiV1GuildsModulesTagsGetResponse[T]) => { 78 78 if (!tag) return;
+3 -6
app/dashboard/[guildId]/layout.tsx
··· 43 43 const cookies = useCookies(); 44 44 const params = useParams(); 45 45 46 - const [error, setError] = useState<string>(); 47 46 const [loaded, setLoaded] = useState<string[]>([]); 48 47 49 48 const guild = guildStore((g) => g); ··· 88 87 } 89 88 ); 90 89 91 - useEffect(() => { 92 - if (data && "message" in data) { 93 - setError(data?.message); 94 - return; 95 - } 90 + const error = data && "message" in data ? data.message : undefined; 96 91 92 + useEffect(() => { 93 + if (!data || "message" in data) return; 97 94 guildStore.setState(data); 98 95 }, [data]); 99 96
+30 -60
app/dashboard/[guildId]/starboard/hooks.ts
··· 1 1 import { StarboardStyle } from "@/typings"; 2 - import { useEffect, useState } from "react"; 2 + import { useMemo } from "react"; 3 3 4 4 interface Example { 5 5 avatar: string | null; ··· 7 7 } 8 8 9 9 export function useExample(style: StarboardStyle) { 10 - const [example, setExample] = useState<Example>({ 11 - avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 12 - username: "@spacewolf." 13 - }); 14 - 15 - useEffect( 16 - () => { 17 - switch (style) { 18 - case StarboardStyle.Username: 19 - setExample((e) => { 20 - return { 21 - ...e, 22 - avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 23 - username: "@spacewolf." 24 - }; 25 - }); 26 - break; 27 - case StarboardStyle.GlobalName: 28 - setExample((e) => { 29 - return { 30 - ...e, 31 - avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 32 - username: "Space Wolf" 33 - }; 34 - }); 35 - break; 36 - case StarboardStyle.Nickname: 37 - setExample((e) => { 38 - return { 39 - ...e, 40 - avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 41 - username: "Luna’s Grandpa <3" 42 - }; 43 - }); 44 - break; 45 - case StarboardStyle.NicknameAndGuildAvatar: 46 - setExample((e) => { 47 - return { 48 - ...e, 49 - avatar: "https://cdn.waya.one/r/a_3a2fa421f079827d31f4fd1b7a9971ba.gif", 50 - username: "Luna’s Grandpa <3" 51 - }; 52 - }); 53 - break; 54 - case StarboardStyle.Anonymous: 55 - setExample((e) => { 56 - return { 57 - ...e, 58 - avatar: null, 59 - username: null 60 - }; 61 - }); 62 - break; 63 - } 64 - }, 65 - [style] 66 - ); 67 - 68 - return example; 10 + return useMemo<Example>(() => { 11 + switch (style) { 12 + case StarboardStyle.GlobalName: 13 + return { 14 + avatar: "/space.webp", 15 + username: "Space Wolf" 16 + }; 17 + case StarboardStyle.Nickname: 18 + return { 19 + avatar: "/space.webp", 20 + username: "Luna’s Grandpa <3" 21 + }; 22 + case StarboardStyle.NicknameAndGuildAvatar: 23 + return { 24 + avatar: "/shiggy.gif", 25 + username: "Luna’s Grandpa <3" 26 + }; 27 + case StarboardStyle.Anonymous: 28 + return { 29 + avatar: null, 30 + username: null 31 + }; 32 + default: 33 + return { 34 + avatar: "/space.webp", 35 + username: "@spacewolf." 36 + }; 37 + } 38 + }, [style]); 69 39 }
+1 -1
app/dashboard/[guildId]/starboard/page.tsx
··· 237 237 defaultState={data.blacklistRoleIds || []} 238 238 max={500} 239 239 disabled={!enabled} 240 - onSave={(o) => edit("blacklistChannelIds", o.map(({ value }) => value))} 240 + onSave={(o) => edit("blacklistRoleIds", o.map(({ value }) => value))} 241 241 /> 242 242 </div> 243 243 </div>
-1
app/login/[social]/route.ts
··· 84 84 } 85 85 86 86 redirect("/profile/connections?success=true"); 87 - 88 87 }
+2 -5
components/inputs/dumb-color-input.tsx
··· 1 1 import { cn } from "@/utils/cn"; 2 2 import { AnimatePresence, motion } from "framer-motion"; 3 - import React, { useEffect, useState } from "react"; 3 + import React, { useState } from "react"; 4 4 import { AiOutlineEdit } from "react-icons/ai"; 5 5 6 6 interface Props { ··· 37 37 ); 38 38 39 39 // this cuz there can be multiple color inputs on the same page, so it will bug, so we need to identify them 40 - const [inputId, setInputId] = useState<string>(""); 41 - useEffect(() => { 42 - setInputId(Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15)); 43 - }, []); 40 + const [inputId] = useState<string>(() => Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15)); 44 41 45 42 const [isHovered, setIsHovered] = useState<boolean>(false); 46 43
+2 -6
components/inputs/dumb-text-input.tsx
··· 1 1 import { cn } from "@/utils/cn"; 2 - import React, { useEffect, useState } from "react"; 2 + import React, { useMemo } from "react"; 3 3 4 4 interface Props { 5 5 name?: string; ··· 40 40 disabled && "cursor-not-allowed opacity-50" 41 41 ); 42 42 43 - const [length, setLength] = useState(0); 44 - 45 - useEffect(() => { 46 - setLength(dataName ? JSON.parse(value)[dataName]?.length : value?.length || 0); 47 - }, [value]); 43 + const length = useMemo(() => dataName ? JSON.parse(value)[dataName]?.length : value?.length || 0, [dataName, value]); 48 44 49 45 return ( 50 46 <div className="relative select-none w-full max-w-full mb-3">
+1
components/inputs/multi-select-menu.tsx
··· 98 98 99 99 switch (res.status) { 100 100 case 200: { 101 + onSave?.(values); 101 102 setState(State.Success); 102 103 setTimeout(() => setState(State.Idle), 1_000 * 8); 103 104 break;
+29 -10
components/list.tsx
··· 23 23 24 24 export function ListTab({ tabs, url, searchParamName, disabled }: ListProps) { 25 25 const [position, setPosition] = useState(0); 26 + const [scrollMetrics, setScrollMetrics] = useState<{ canScroll: boolean; maxScroll: number; }>({ canScroll: false, maxScroll: 0 }); 26 27 27 28 const path = usePathname(); 28 29 const params = useSearchParams(); ··· 47 48 function scroll(direction: "left" | "right") { 48 49 if (!ref.current) return; 49 50 50 - const scrollAmount = ref.current.clientWidth * 0.8; // Scroll 80% of the visible width 51 + const scrollAmount = ref.current.clientWidth * 0.8; 51 52 52 53 ref.current.scrollBy({ 53 54 top: 0, ··· 62 63 setPosition(scrollLeft); 63 64 } 64 65 65 - const isScrollable = ref.current ? ref.current.scrollWidth > ref.current.clientWidth : false; 66 + useEffect(() => { 67 + const element = ref.current; 68 + if (!element) return; 69 + 70 + const updateMetrics = () => { 71 + const canScroll = element.scrollWidth > element.clientWidth; 72 + const maxScroll = Math.max(element.scrollWidth - (element.clientWidth + 10), 0); 73 + setScrollMetrics((prev) => { 74 + if (prev.canScroll === canScroll && prev.maxScroll === maxScroll) return prev; 75 + return { canScroll, maxScroll }; 76 + }); 77 + setPosition(element.scrollLeft); 78 + }; 79 + 80 + const handleScroll = () => { 81 + setScrollPosition(); 82 + updateMetrics(); 83 + }; 66 84 67 - useEffect(() => { 68 - if (!ref.current) return; 85 + const resizeObserver = new ResizeObserver(updateMetrics); 69 86 70 - ref.current.addEventListener("scroll", setScrollPosition); 71 - setScrollPosition(); 87 + element.addEventListener("scroll", handleScroll); 88 + resizeObserver.observe(element); 89 + updateMetrics(); 72 90 73 91 return () => { 74 - ref.current?.removeEventListener("scroll", setScrollPosition); 92 + element.removeEventListener("scroll", handleScroll); 93 + resizeObserver.disconnect(); 75 94 }; 76 95 }, []); 77 96 ··· 88 107 > 89 108 <TabsList 90 109 ref={ref} 91 - className="bg-inherit border-b-2 border-wamellow p-0 w-full justify-start rounded-none overflow-y-auto overflow-x-auto scrollbar-hide" 110 + className="bg-inherit border-b-2 border-wamellow p-0 w-full justify-start rounded-none overflow-y-auto overflow-x-auto scrollbar-none" 92 111 > 93 112 {tabs.map((tab) => ( 94 113 <TabsTrigger ··· 104 123 </TabsList> 105 124 </Tabs> 106 125 107 - {isScrollable && position > 0 && ( 126 + {scrollMetrics.canScroll && position > 0 && ( 108 127 <Button 109 128 className="absolute bottom-2 left-0 backdrop-blur-lg" 110 129 onClick={() => scroll("left")} ··· 114 133 </Button> 115 134 )} 116 135 117 - {isScrollable && ref.current && position < (ref.current.scrollWidth - (ref.current.clientWidth + 10)) && ( 136 + {scrollMetrics.canScroll && position < scrollMetrics.maxScroll && ( 118 137 <Button 119 138 className="absolute bottom-2 right-0 backdrop-blur-lg" 120 139 onClick={() => scroll("right")}
+18 -9
components/modal.tsx
··· 3 3 import type { ApiError } from "@/typings"; 4 4 import { cn } from "@/utils/cn"; 5 5 import Link from "next/link"; 6 - import { useEffect, useState } from "react"; 6 + import { useState } from "react"; 7 7 import { HiFire } from "react-icons/hi"; 8 8 9 9 import Notice, { NoticeType } from "./notice"; ··· 56 56 const [state, setState] = useState<State>(State.Idle); 57 57 const [error, setError] = useState<string | null>(null); 58 58 59 - useEffect(() => { 59 + const reset = () => { 60 60 setError(null); 61 61 setState(State.Idle); 62 - }, [isOpen]); 62 + }; 63 + 64 + const handleClose = () => { 65 + reset(); 66 + onClose(); 67 + }; 63 68 64 69 async function submit() { 65 70 if (state === State.Loading) return; 66 71 if (!onSubmit) { 67 - onClose(); 72 + handleClose(); 68 73 return; 69 74 } 70 75 71 - setError(null); 76 + reset(); 72 77 setState(State.Loading); 73 78 const data = onSubmit?.(); 74 79 75 80 if (!data) { 76 - onClose(); 81 + handleClose(); 77 82 return; 78 83 } 79 84 ··· 88 93 setState(State.Idle); 89 94 90 95 if (res.ok) { 91 - onClose(); 96 + handleClose(); 92 97 onSuccess?.(res.status === 204 ? null : await res.json()); 93 98 return; 94 99 } ··· 100 105 return ( 101 106 <Dialog 102 107 open={isOpen} 103 - onOpenChange={(open) => !open && onClose()} 108 + onOpenChange={(open) => { 109 + if (!open) { 110 + handleClose(); 111 + } 112 + }} 104 113 > 105 114 <DialogContent> 106 115 <DialogHeader> ··· 130 139 {onSubmit && ( 131 140 <Button 132 141 variant="link" 133 - onClick={() => state !== State.Loading && onClose()} 142 + onClick={() => state !== State.Loading && handleClose()} 134 143 className="hidden md:block ml-auto text-sm font-medium" 135 144 disabled={state !== State.Idle} 136 145 >
+8 -5
components/ui/input-base.tsx
··· 49 49 }: InputBaseProps) { 50 50 const [focused, setFocused] = React.useState(false); 51 51 const controlRef = React.useRef<HTMLElement>(null); 52 + const handleClick = React.useCallback<React.MouseEventHandler<HTMLDivElement>>((event) => { 53 + onClick?.(event); 54 + if (event.defaultPrevented) return; 55 + if (controlRef.current && event.currentTarget === event.target) { 56 + controlRef.current.focus(); 57 + } 58 + }, [onClick]); 52 59 53 60 return ( 54 61 <InputBaseContext.Provider ··· 61 68 > 62 69 <Primitive.div 63 70 data-slot="input-base" 64 - onClick={composeEventHandlers(onClick, (event) => { 65 - if (controlRef.current && event.currentTarget === event.target) { 66 - controlRef.current.focus(); 67 - } 68 - })} 71 + onClick={handleClick} 69 72 className={cn( 70 73 "border-input dark:bg-input/30 flex min-h-9 cursor-text items-center gap-2 rounded-lg border bg-transparent px-3 py-1 text-base shadow-2xs transition-[color,box-shadow] outline-hidden md:text-sm", 71 74 disabled && "pointer-events-none cursor-not-allowed opacity-50",