• navigate(-1)의 위험성: 브라우저 히스토리에서 이전 위치로 이동, 앱 내부 네비게이션과 혼란을 초래할 수 있음
  • 대신 Link 컴포넌트의 state 속성을 활용하여 안전하게 앱 내에서의 “Back” 네비게이션 구현 가능
  • 사용자에게 현재 URL을 반환하는 커스텀 훅 useCurrentURL 구현 및 재사용의 용이성을 제공하는 useBackNavigation 훅 정의
function PreserveStateLink(props) {
  const location = useLocation()
  const currentURL = location.pathname + location.search

  return (
    <Link state={{ back: currentURL }} {...props}>
      {children}
    </Link>
  )
}

function BackLink() {
  const navigate = useNavigate()
  const location = useLocation()

  const handleClick: LinkProps['onClick'] = (e) => {
    const back = location.state?.back

    if (back) {
      e.preventDefault()

      navigate(back)
    }
  }

  return (
    <Link to="/todos" onClick={handleBack}>
      Back
    </Link>
  )
}
#373
@value b from "./b.module.css";

.root {
  color: aquamarine;
}

.root :global(.b) {
  text-decoration: line-through;
}

CSS 모듈에서 변수를 값으로 내보내고 사용하는 방법

  • PostCSS와 postcss-modules-values 플러그인을 사용하여 CSS 모듈 내에서 변수 값 내보내기 지원
  • 색상 변수를 정의하는 파일 생성
    • 변수 선언: @value 구문 사용
  • 다른 CSS 모듈 파일에서 해당 변수를 가져와서 사용
    • 변수 가져오기 및 CSS 클래스에 적용
#371

URL.parse() 메서드를 활용하여 URL 객체를 생성하고 처리하는 방법

  • URL.parse(url) 메서드는 주어진 URL에 따라 새 URL 객체를 생성
  • 유효하지 않은 URL 값이 주어질 경우 null을 반환
  • 두 번째 파라미터 base는 상대 URL을 해석하기 위한 기준 URL로 사용되며, 이를 통해 URL의 경로가 올바르게 조정됨
  • URL 객체나 다른 문자열을 파라미터로 사용할 수 있으며, 내부에서 문자열로 변환됨
describe('null 입력에 대한 URL API 동작 테스트', () => {
  test('new URL(null)은 TypeError를 발생시킨다', () => {
    expect(() => new URL(null)).toThrow(TypeError)
  })

  test('URL.canParse(null)은 false를 반환한다', () => {
    expect(URL.canParse(null)).toBe(false)
  })

  test('URL.parse(null)은 null을 반환한다', () => {
    expect(() => URL.parse(null)).toBe(null)
  })
})
#368
  • 검색 매개변수별로 데이터 유효성 검증
  • useSearchParams와 Zod를 활용해 모든 검색 매개변수를 병합하고 유효성 검증
import { useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'
import { z } from 'zod'

const CombinedSchema = PaginationSchema.merge(FilterSchema)
  .merge(SortSchema)
  .merge(SearchSchema)
  .partial()

export function useSearchParamsWithSchema() {
  const [searchParams, setSearchParams] = useSearchParams()

  const parsedParams = useMemo(() => {
    // searchParams를 객체로 변환
    const paramsObject = Object.fromEntries(searchParams.entries())
    // 결합된 스키마로 유효성 검사 및 파싱
    const result = CombinedSchema.safeParse(paramsObject)

    if (result.success) {
      return result.data
    }

    console.error(result.error)

    return {}
  }, [searchParams])

  /**
   * 새로운 검색 매개변수로 업데이트하는 함수입니다.
   *
   * 1. 현재 검색 매개변수의 복사본을 생성합니다.
   * 2. 새로운 매개변수를 순회하며 값을 설정하거나 삭제합니다.
   * 3. 최종적으로 업데이트된 매개변수를 state에 설정합니다.
   */
  const updateSearchParams = (newParams: Record<string, string>) => {
    const updatedParams = new URLSearchParams(searchParams)

    Object.entries(newParams).forEach(([key, value]) => {
      if (value) {
        // 업데이트된 매개변수로 상태 갱신
        updatedParams.set(key, value)
      } else {
        // 값이 없으면 삭제
        updatedParams.delete(key)
      }
    })

    // 업데이트된 매개변수로 상태 갱신
    setSearchParams(updatedParams)
  }

  return {
    parsedParams,
    updateSearchParams,
  }
}
#367

취소 가능한 fetch 요청을 생성하는 createCancelableFetch 함수 구현

/**
 * 취소 가능한 fetch 요청
 *
 * @param url - 요청 URL
 * @param options - fetch 옵션
 * @returns {run, cancel} - run: 요청 실행 함수, cancel: 요청 취소 함수
 *
 * @example
 *
 * ```ts
 * const { run, cancel } = createCancelableFetch('/api/data')
 *
 * run()
 *   .then((data) => console.log('Fetched data:', data))
 *   .catch((err) => console.error('Error or canceled:', err))
 *
 * cancel()
 * ```
 */
function createCancelableFetch(url: string, options: RequestInit = {}) {
  const abortController = new AbortController()

  const run = async () => {
    const response = await fetch(url, {
      ...options,
      signal: abortController.signal,
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    return response.json()
  }

  const cancel = () => abortController.abort()

  return {
    run,
    cancel,
  }
}
  • AbortController를 사용하여 요청을 중단할 수 있는 기능 제공
  • run 함수로 fetch 요청을 실행하고, cancel 함수로 요청을 취소할 수 있음
  • 요청 실패 시 오류를 처리하고 예외를 발생시킴

#360

field-sizing를 사용하면 콘텐츠를 기반으로 크기 조절을 사용 설정하는 데 CSS 한 줄이 필요합니다. 이 콘텐츠 기반 크기 조절 스타일은 textarea 외에도 다른 요소에도 적용됩니다.

#358

비동기 호출의 결과와 오류를 튜플 형식으로 반환

const [error, data] = await tuple(someAsyncFunction())
// 성공: [null, 결과값]
// 실패: [error] 또는 [new TupleItError(error)]

오류 처리 간소화 - 단일 체크로 오류 관리 가능

#357
💡

useStateObject는 React의 useState를 확장한 가벼운 래퍼로, 객체 상태 관리를 간편하게 할 수 있도록 설계되었습니다.


export type StateObject<T extends object> = T & {
  set: React.Dispatch<React.SetStateAction<T>>
  setItem: <K extends keyof T>(key: K, value: T[K]) => void
  merge: (newState: Partial<T>) => void
  reset: () => void
}

그렇다면 Map과 Set도 시도해보기

function useStateMap<K, V>(init: Iterable<[K, V]> = []) {
  const [map, setMap] = useState(new Map<K, V>(init))

  const update = useCallback(
    (updater: (currentMap: Map<K, V>) => void) => {
      setMap((prev) => {
        const newMap = new Map(prev)
        updater(newMap)
        return newMap
      })
    },
    [setMap]
  )

  return {
    map,
    set: (key: K, value: V) => update((m) => m.set(key, value)),
    delete: (key: K) => update((m) => m.delete(key)),
    clear: () => setMap(new Map()),
    has: (key: K) => map.has(key),
    get: (key: K) => map.get(key),
    entries: () => Array.from(map.entries()),
    size: map.size,
  }
}
function useStateSet<T>(init: Iterable<T> = []) {
  const [set, setSet] = useState(new Set<T>(init))

  const update = useCallback(
    (updater: (currentSet: Set<T>) => void) => {
      setSet((prev) => {
        const newSet = new Set(prev)
        updater(newSet)
        return newSet
      })
    },
    [setSet]
  )

  return {
    set,
    add: (value: T) => update((s) => s.add(value)),
    delete: (value: T) => update((s) => s.delete(value)),
    has: (value: T) => set.has(value),
    clear: () => setSet(new Set()),
    entries: () => Array.from(set),
    size: set.size,
  }
}

#354

로컬·테스트 서버에서 크롬 인증서 경고를 강제로 통과한다.

언제: 이 연결은 비공개로 설정되지 않았습니다 화면에 NET::ERR_CERT_AUTHORITY_INVALID·NET::ERR_CERT_COMMON_NAME_INVALID 가 뜨고, 고급 → 안전하지 않음(계속)이 없거나 매번 누르기 번거로울 때.

절차

  1. 경고 화면 아무 곳이나 클릭한다 — 페이지에 포커스를 준다
  2. 입력창 없이 그대로 thisisunsafe 를 타이핑한다

입력창이 없는 게 정상이다. 경고 페이지의 keypress 핸들러가 한 글자씩 받아 BYPASS_SEQUENCE 와 대조하고, 다 맞으면 CMD_PROCEED 를 보낸다 — “안전하지 않음(계속)“을 누른 것과 같은 명령이다.

확인: 마지막 글자를 치는 순간 넘어간다. 안 넘어가면 포커스가 주소창에 있는 것이다 — 1번부터.

출처

#352
30 중 11페이지