0. Closure + HOC클로저: 이벤트가 발생할 때마다 이전의 상태를 기억해야 하기 때문에 클로저가 필요. 반환된 함수가 실행될 때마다 클로저를 사용하여 함수 실행여부를 결정할 수 있음고차 함수: 인자로 실행하려는 함수를 받아, 이 함수를 조건에 따라 실행하는 새로운 함수를 반환1. Debouncingtype Debounce = (...args: T) => void;function debounce(func: (...args: T) => void, delay: number): Debounce { let timer: ReturnType | null = null; return (...args: T) => { if (timer !== null) clearTimeout(timer); timer..
TIL
1. 싱글톤 패턴이란? Zustand 는 싱글톤 패턴?싱글톤 패턴(Singleton Pattern)은 소프트웨어 디자인 패턴 중 하나로, 하나의 클래스에서 단 하나의 인스턴스만 생성되도록 보장하는 패턴입니다. 즉, 애플리케이션 전체에서 특정 클래스의 객체가 오직 하나만 존재하고, 어디서든 이 객체에 접근할 수 있는 전역적인 접근점을 제공합니다. 정의:싱글톤 패턴은 클래스의 인스턴스를 하나로 제한하고, 해당 인스턴스가 전역적으로 사용될 수 있도록 하는 디자인 패턴입니다. 동작 방식:• 클래스 내에 하나의 인스턴스만을 생성하며, 외부에서 해당 인스턴스를 직접 생성할 수 없도록 생성자를 private으로 설정합니다.• 클래스가 처음 호출되면 내부적으로 인스턴스를 생성하고, 이후부터는 동일한 인스턴스를 반환하여..
자주써도 헷갈리는 parallel routes 와 intercepting routes 로기본적인 modal 만드는 법을 정리해보려고 합니다. 1. Parallel RoutesParallel Routes 는 모달을 만들기 위한 기능은 아닙니다.공식문서에 따르면, Parallel Routes를 사용하면 동일한 레이아웃 내에서 하나 이상의 페이지를 동시에 또는 조건부로 렌더링할 수 있습니다. 대시보드나 피드와 같이 앱의 매우 동적인 섹션에 유용합니다. 예를 들어 대시보드에서 Parallel Routes를 사용하여팀 페이지와 분석 페이지를 동시에 렌더링할 수 있습니다 라고 합니다.만드는 법은 아래처럼 @ 를 붙여서(슬롯이라고 합니다) 폴더를 생성하고 page.tsx 만들면 됩니다.app/ ├─ @moda..
여기 에 작성했던 내용중 부족한 내용이 있었습니다.컴포넌트와 값을 반환하는 커스텀 훅 사용시 주의사항을 다시 정리합니다.1. useCallback 사용리렌더링 통제가 어렵습니다.반환할 컴포넌트를 useCallback 처리합니다.function usePreferTheme({ mode }: UsePreferThemeProps) { const [selectedTheme, setSelectedTheme] = useState([]); const handleThemeChange = () => { // setSelectedTheme() 어쩌구 등등.. } // useCallback 사용! const PreferThemeToRender = useCallback(() => { ..
useReducer는 React에서 상태 관리를 위해 사용하는 훅 중 하나로, 복잡한 상태 로직을 보다 체계적으로 관리하고 싶을 때 유용합니다. 특히 상태 변경 로직이 여러 단계로 나누어져 있거나, 상태 업데이트가 명확한 액션에 의해 이루어져야 할 때 사용됩니다. Redux의 상태 관리 방식과 유사하게 리듀서 패턴을 사용하여 상태를 업데이트합니다.useReducer의 기본 구조useReducer는 세 가지 요소를 인자로 받습니다:리듀서 함수: 현재 상태와 액션을 받아서 새로운 상태를 반환하는 함수입니다.초기 상태: 상태의 초기 값입니다.초기화 함수 (선택적): 초기 상태를 더 복잡한 방식으로 설정해야 할 경우에 사용할 수 있습니다.useReducer는 dispatch 함수를 반환하며, 이 dispatch..
cn : tailwind-merge 와 clsx를 결합한 유틸리티 함수import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs));} 이런 유틸리티 함수를 찾았는데아주 유용해 보였습니다. 그런데 이전 프로젝트에서 twMerge 함수만으로도clsx 없이 유사하게 썼던 기억이 있어서차이점에 대해 다시 짚어봤습니다. 결론은위처럼 cn (이름이야뭐,,,) 함수를 쓰는 것이clsx 의 여러 기능도 사용하면서 tailwind 유틸리티 클래스들을효과적으로 병합할 수 있는 좋은 방법이다였습니다..
React.FC와 React.ReactNode는 React에서 각각 다른 목적을 가진 타입입니다. 두 타입의 차이를 이해하면 타입스크립트로 React 애플리케이션을 개발할 때 보다 명확하게 타입을 지정할 수 있습니다.React.FC (React.FunctionComponent)설명: React.FC는 함수형 컴포넌트를 정의할 때 사용하는 타입입니다.용도: 컴포넌트의 props 타입을 지정하고, 해당 컴포넌트가 React 요소를 반환한다는 것을 명시합니다.특징:제네릭 타입으로 Props의 타입을 지정할 수 있습니다.기본적으로 children 프로퍼티를 포함하고 있습니다.컴포넌트의 반환 타입이 ReactElement임을 명시합니다.예시:import React from 'react'; interface MyC..
next font 와 tailwind 로 모노레포에 폰트 적용하기모노레포 구성은 다음과 같은 상황입니다. /apps/admin => nextjs app/apps/invitation => nextjs app/apps/www => nextjs app/apps/storybook => storybook app/packages/config-tailwind => 테일윈드 공용 설정들 모여 있는 곳/packages/shared => 공용 컴포넌트 디렉토리(nextjs 아님) 이때 같은 폰트들을 각 app 들이 모두 사용합니다.처음에 생각한 것은 shared 에만 폰트를 설정하고각 app 에서 임포트 해서 쓰는 방법이었는데shared는 nextjs가 아니어서 next font를 사용할 수 없었습니다. 또한 storyb..
pnpm 설치가 중복으로 되어서..쓸데없이 시간을 잡아먹은 부분을역시 GPT o1쌤과 함께 해결해봤고 이 내용을 정리합니다..알겠습니다. npm uninstall -g pnpm 명령어가 제대로 작동하지 않는다면, 몇 가지 이유가 있을 수 있습니다. 다음 단계를 통해 문제를 해결해보세요.pnpm의 설치 방법 확인하기:Homebrew를 통해 설치된 경우:위 명령어를 실행했을 때 pnpm이 나열된다면 Homebrew를 통해 설치된 것입니다.brew list pnpmpnpm이 npm을 통해 설치되지 않았을 수 있습니다. 예를 들어, Homebrew나 다른 패키지 매니저를 사용하여 설치했을 수 있습니다.Homebrew를 통해 pnpm 제거하기:brew uninstall pnpm만약 pnpm이 Homebrew로 설..
새로 나온 chatGPT-o1 에게 ISR 에 관해 물어봤습니다.revalidatePath 를 통해 데이터가 업데이트 되면 메인페이지를 ISR 하도록 하였습니다.이것이 next.js 에서 적용되는 원리와 과정에 대해 물어보았습니다.GPT 쌤의 답변당신은 Next.js 14 프로젝트에서 App Router를 사용하고 있으며, 라우트 핸들러 내에서 revalidatePath 함수를 구현하여 서버 데이터가 업데이트될 때 Incremental Static Regeneration(ISR)을 트리거했습니다. 이 메커니즘은 전체 재빌드나 수동 개입 없이도 정적으로 생성된 페이지가 최신 데이터를 반영하도록 보장합니다. 이제 이것이 어떻게 작동하는지 원리와 과정을 자세히 살펴보겠습니다.Incremental Static ..
zod는 TypeScript와 JavaScript에서 사용할 수 있는 스키마 선언 및 검증 라이브러리입니다. zod를 사용하면 객체, 배열, 숫자, 문자열 등 다양한 데이터 구조를 스키마로 정의하고, 이를 기반으로 데이터의 유효성을 검증할 수 있습니다. 이를 통해 타입 안정성과 런타임에서의 데이터 검증을 동시에 수행할 수 있습니다.왜 zod를 사용하는가?타입 안전성: zod는 TypeScript와 자연스럽게 통합되어 스키마에서 정의한 데이터 구조에 대해 TypeScript 타입을 자동으로 추론합니다.런타임 검증: TypeScript는 컴파일 타임에서만 타입을 검사합니다. zod는 런타임에서도 데이터가 올바른지 확인할 수 있어, API 응답이나 폼 입력 데이터를 검증할 때 유용합니다.간편한 사용법: zod..
이 오류는 React의 Hydration 과정에서 발생하는 문제로, 서버에서 렌더링된 HTML과 클라이언트에서 렌더링된 HTML이 일치하지 않을 때 발생합니다. 주어진 상황에서 오류가 발생하는 원인으로는 다음과 같은 가능성을 고려할 수 있습니다:1. 비동기 렌더링클라이언트 컴포넌트 EveryWorks의 map 함수 내에서 async 함수를 사용하고 있습니다. map 함수 내에서 비동기 작업을 처리하게 되면, React는 서버와 클라이언트 간의 UI가 일치하지 않는다고 인식할 수 있습니다. map 함수 내에서 async를 사용하는 것은 권장되지 않습니다.해결 방법:blurredImages는 이미 서버 컴포넌트에서 비동기 처리로 생성되었기 때문에, 클라이언트 컴포넌트에서는 추가적으로 비동기 처리를 하지 않는..
부트캠프를 수료했습니다. 저는 원래 미술 중에서도 순수미술을 하던 사람이었습니다. 캠프를 통해 여러가지 배움을 얻었고, 당연히 코딩 스킬도 많이 상승되었지만제가 가장 크게 배우고 얻은 점은 이 개발 세계가 정말 매력적인 곳임을 깊이 알게되었다는 것입니다. 과정을 통과하며 컬쳐핏을 개발보다도 많이 강조받았는데이런 부분에서 저는 이 세계가 제가 가야할 곳이라고 확실히 알게 되었습니다. 한편으로 제가 있던 미술계의 평균적인 컬쳐핏 수준이 정말정말 개판이었기 때문에개발자들의 문화가 더욱 사랑스럽게 느껴지는 것 같기도 합니다. 컬처핏이 어쩌면 가장 중요할 수 있다는 점을다시한번 깨닳았다는 내용을 캠프 마지막 TIL 로 정리하고자 합니다. 짧은 소회였습니다 ㅎㅎ내일 부터 다시 달리자~~~
내배캠 수료짧다면 짧고 길다면 긴 부트캠프가 내일이면 끝납니다.til 은 계속 해서 작성할 예정이고기존에 작성했던 것들은 분류작업을 해보려고 합니다.시원섭섭합니다.supabase.rpc내배캠 막바지에 supabase.rpc 라는 메서드를 사용할 일이 있었습니다.sql 을 몰라서 생소한 기능이었는데 gpt 쌤에게 물어본 내용을 정리합니다. Supabase에서 supabase.rpc 메서드는 PostgreSQL의 저장 프로시저(Stored Procedure) 또는 사용자 정의 함수(User-defined Function)를 호출하는 데 사용됩니다. 이를 통해 복잡한 로직을 데이터베이스 내에서 실행하고, 결과를 가져올 수 있습니다.1. 함수 생성먼저 PostgreSQL에서 함수를 생성해야 합니다. 예를 들어,..
openai api 로 이미지 생성 후 supabase 에 쓰기(route handler)대략 아래와 같이 하면 됩니다.근데 한번 생성할 때 마다 돈나가고 10달러 충천해놔야 합니다.if (!tripImageFile && mode === 'new' && tripData) { const openai = new OpenAI({ apiKey: OPEN_AI_SECRET_KEY, }); // [서울/경기, 서울시] 이런 값입니다 대충 const [continent, country] = tripData.trip_final_destination?.split(' ') || []; // OpenAI를 사용하여 이미지를 생성 const imageGeneration = a..
tailwind-merge 라이브러리import { clsx, type ClassValue } from 'clsx';import { twMerge } from 'tailwind-merge';export function tailwindMerge(...inputs: ClassValue[]) { // clsx를 사용하여 입력된 모든 클래스네임을 결합하고, // 이를 twMerge에 전달하여 최종 병합된 클래스네임을 반환합니다. return twMerge(clsx(...inputs));} 간단히 위처럼 사용할 수 있습니다.GPT 쌤의 자세한 설명을 첨부합니다.clsx와 twMerge를 함께 사용하여 tailwindMerge 함수를 구현하면, 이 함수는 clsx만을 사용하는 경우보다 더 강력하고 ..
scrollIntoView이 좋은 걸 몰랐다니..요소를 뷰포트 내에 보이도록 자동 스크롤해주는 기능이라고 합니다.. 아래 예시에서는 div 의 아래에 버튼들이 있고인덱스를 state 로 관리하는 상황인데요. 인덱스가 변할 때 마다 useEffect 가 실행되고자동으로 인덱스에 해당하는 버튼이 화면의 중앙에 오게 됩니다. 많이 써먹을 것 같습니다!useEffect(() => { if (scrollRef.current) { const selectedButton = scrollRef.current.children[ selectedIndex ] as HTMLElement; if (selectedButton) { se..
당겨서 새로고침을 막고싶을 때모바일 버전 개발을 하다보면당겨서 새로고침이 되는 것이 매우 귀찮습니다. 특히 화면내에서 드래그 해야하는 요소들이 있을 때자꾸만 새로고침 되어서 짜증납니다. 그래서 방법을 찾아 보았습니다.역시 방법이 있었고요.. 전에는 div 같은 걸로 감싸서 높이를 제한하고 뭐 어쩌고 했던것 같은데그냥 간단하게 해결하는 방법이 있었습니다. 검색해보면 body 에다가 넣든지 html 에다 넣든지 하라고하는데저는 특정페이지에서만 막고 싶어서아래처럼 해보았습니다. useEffect(() => { document.documentElement.style.overscrollBehavior = 'none'; return () => { document.documentElement...
supabase 를 사용하다 보니 데이터를 join 해서 가져오면 매우 편리한 경우가 많았습니다.그런데 방법의 의미를 정확하게 몰라서 gpt 쌤한테 하나하나 물어봤습니다.첫번째 질문supabase 에서 데이터를 join 해서 가져오는 방법에 관해 질문이 있어요. 첫번째 사례는 다음과 같고요.await supabase .from('stories') .select('*, buddies:story_created_by (*)') .order('story_created_at', { ascending: false });두번째는 다음과 같아요.await supabase .from('trips') .select('*, contract:contract!contract_contract_trip_..
모바일에서 테스트해야만 할때개발을 하다보면 모바일에서 직접 보고 싶을때가 있습니다. 그럴때는 자신의 와이파이 IP 를 알아낸 뒤모바일기기에서 localhost:3000 대신192.168.xx.xx:3000 으로 접속하면 됩니다. 그런데 해당 상황에서 nextjs 라우트핸들러를쓰거나 뭐 하면 fetch 가 다 실패하게 됩니다. 왜냐하면 localhost:3000으로 잡혀있는데날라오는곳은 192.168.xx.xx 여서 그런것 같습니다. 예를들어 로그인 시 api/auth/login 으로 날린다~ 라면이 요청이 실패하게 되고 loaded failed 등의 에러가 잡힐 것입니다. 이럴때 임시적으로 대처할 수 있는 방법을 찾았습니다.package.json 수정방법은 간단했습니다.package.json 에 아래와 ..
Auth session missing!수파베이스 auth 사용중에자꾸만 마주치는 auth session missing!이것을 해결하기 위해 별 방법을 다 써보고 했지만레딧이나 깃헙이슈에도 같은 문제를 겪는 사람들이 많은 걸 보니뭔가 수파베이스에 문제가 있는 것 같기도 합니다. 아무튼 서버측에서 날리는 fetch 의 결과가tanstack query 의 prefetch query 와 함께 쓸 때 미들웨어서는 getUser 가 잡히지만prefetch query에서는 안 잡힌다거나혹은 자꾸 있었다 없었다 하는 문제를 해결해보고자(십중팔구 auth session missing...)안되면 fetch를 할 때 쿠키를 직접 같이 배달해보자라고 생각했습니다.그래서 fetch 함수를 다음과 같이 작성했습니다.fetche..
얼러트 모달이 여기저기 필요해서서버, 클라이언트 관계없이 아무데서나 호출해서 쓸 수 있도록아래처럼 사용하고 있었습니다. 그런데 이 방법에 문제가 조금 있었습니다.그냥 써도 무방한 정도였지만 해결해보기로 하였습니다. 문제는 번들 사이즈가 너무 크다는 점 이었습니다.여기저기서 호출 하는 함수인데 무거우면 안될 것이라 판단했습니다.원래 버전원일을 찾아보니 createRoot 를 하기 위해 가져온ReactDom 이 번들사이즈가 132kb 정도로 무거웠습니다.그래서 이것을 제거하고 다른 방법을 사용하기로 했습니다.하지만 그러면 컴포넌트를 사용해서 아무데서나 body에얼러트 모달을 부착할 수가 없었습니다.import CustomAlert from '@/components/organisms/common/CustomA..
webp 로 변환이 필요해사용자가 만약 몇십메가짜리 이미지를 넣으면 어떡할 것인가?라는 문제에서 이미지를 webp로 변환하는 로직을 작성해보았습니다. 서버로 파일을 보내면 라우트핸들러에서webp로 변환하고 수파베이스에 저장하는 단순한 방법입니다.클라이언트 에서일단 useState 든 뭐든 File 이 변수에 저장되어 있어야 합니다.const formData = new FormData();formData.append('imageFile', imageFile);const payload: StoryData = formData;mutate(payload); 클라이언트에서는 위처럼 처리합니다.애초에 form 태그를 쓰고 있다면 new FormData(여기에 e.target 넣을것) 요렇게 하면 될 것 같고아니라면..
Lazy LoadingLazy loading은 Next.js에서 애플리케이션의 초기 로딩 성능을 개선하는 데 도움을 주며, 경로를 렌더링하는 데 필요한 JavaScript의 양을 줄여줍니다. 클라이언트 컴포넌트와 가져온 라이브러리의 로딩을 지연시킬 수 있으며, 필요할 때만 클라이언트 번들에 포함되도록 합니다. 예를 들어, 사용자가 모달을 열려고 클릭할 때까지 모달의 로딩을 지연시킬 수 있습니다. Next.js에서 lazy loading을 구현하는 두 가지 방법이 있습니다:next/dynamic을 사용한 동적 가져오기React.lazy()와 Suspense를 사용기본적으로 서버 컴포넌트는 자동으로 코드 분할이 되며, 스트리밍을 사용하여 서버에서 클라이언트로 UI 조각을 점진적으로 전송할 수 있습니다. La..
Supabase를 사용하여 데이터를 가져온 후, 변경된 값을 업데이트할 수 있습니다. 그러나 데이터 객체를 직접 수정하기보다는 새로운 객체를 만들어서 업데이트하는 것이 더 안전합니다. 다음은 그 예제입니다:데이터를 가져와 필요한 필드를 업데이트합니다.업데이트된 데이터를 Supabase를 통해 upsert로 저장합니다.아래는 코드 예제입니다:import { useState, useEffect } from 'react';import { createClient } from '@supabase/supabase-js';import { Tables } from '@/types/supabase.ts'import useAuth from "@/context/auth.context.ts"const supabase = cr..
실시간 채팅 메시지를 로컬 상태가 아닌 Supabase 테이블에 저장하기 위해서는 메시지를 전송할 때마다 Supabase 데이터베이스에 삽입(insert) 작업을 수행하고, 채팅 페이지가 로드될 때 메시지를 불러와야 합니다. 이를 위해 다음과 같은 작업을 수행할 수 있습니다:메시지 전송 시 Supabase 테이블에 저장: 사용자가 메시지를 전송할 때, Supabase 테이블에 메시지를 저장합니다.메시지 불러오기: 채팅 컴포넌트가 마운트될 때 Supabase 테이블에서 메시지를 불러옵니다.먼저, Supabase 클라이언트 설정을 하고 메시지를 저장하고 불러오는 기능을 추가합니다."use client";import supabaseClient from "@/supabase/supabaseClient";impo..
5. useMutation 을 위한 세팅route handler 를 향해 fetch 를 날릴건데,이것을 useMutation 으로 처리하고자합니다.먼저 mutationFn 에 들어갈 함수를 만듭니다.export async function postNaverLogIn(): Promise { if (!window.location.hash) return null; const hash = window.location.hash.substring(1); const params = new URLSearchParams(hash); const accessToken = params.get('access_token'); const url = '/api/auth/callback/naver'; t..
supabase OAuth 는 naver provider 미지원!! 상황은 이러합니다.supabase auth 를 쓰고 있다.그런데 supabase auth 는 naver Provider 를 지원하지 않는다...하지만 naver login 구현도 하고 싶다!여기에 더해 우리는 기본 auth 스키마만 쓰는 것이 아니라유저를 커스텀하기 위한 buddies 테이블도 public 에 두고 있습니다.따라서 아래와 같이 되는 것이 가장 좋겠죠네이버 로그인시 auth 스키마에 insert 될 수 있다면sql trigger 에 따라 자동으로 buddies 테이블에는 insert된다.하지만 역시 쉽지않습니다. auth 스키마에 직접 insert 하는 방법은supabase 문서 어디에도 없습니다. 공식적으로 없는 것 같습..
state와 UI를 리턴하는 커스텀 훅이 필요해우리에겐 state와 UI(컴포넌트)를 리턴하는 커스텀 훅이 필요합니다. 여기서 state는 전역상태로 관리되면 안되는 상황이므로커스텀 훅에서 리턴하는 state 는 독립적이어야 합니다. 또한 비슷한 UI를 반복적으로 사용해야 하는데 렌더링해야하는데이터종류는 2가지(여정테마/버디즈성향)인 상황입니다. 따라서 커스텀 훅의 리턴은 named export가 될 수 없으므로 객체가 아닌 배열어야 합니다.그렇다면 [컴포넌트, 스테이트] 이렇게 반환하기만 하면 될 것 같습니다.리턴할 컴포넌트먼저 리턴할 컴포넌트를 atoms 레벨에 만들었습니다.type PreferThemeProps = { selectedTheme: string[]; handleThemeCha..
처음 생각한 다이나믹 헤더 의사 코드export default function Page({ params, searchParams,}: { params: { slug: string } searchParams: { [key: string]: string | string[] | undefined }}) { return My Page}Next.js 에서 slug 와 query string 을 가져올 수 있는 위 코드를 사용해서layout.tsx 혹은 page.tsx : (서버컴포넌트 겠죠..? 아마) 에서동적으로 주소에따라 MobileHeader.tsx 컴포넌트로 넘겨주는 props 를 다르게 한다!위 방법은 동적 라우팅에서만 사용가능! 따라서 middleware 설정 추가확인해보니 위 방법은 동적 라우팅..