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>
)
}@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 클래스에 적용
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)
})
})- 검색 매개변수별로 데이터 유효성 검증
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,
}
}취소 가능한 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함수로 요청을 취소할 수 있음- 요청 실패 시 오류를 처리하고 예외를 발생시킴
field-sizing를 사용하면 콘텐츠를 기반으로 크기 조절을 사용 설정하는 데 CSS 한 줄이 필요합니다. 이 콘텐츠 기반 크기 조절 스타일은textarea외에도 다른 요소에도 적용됩니다.
- https://github.com/arthurfiorette/proposal-safe-assignment-operator
- https://github.com/arthurfiorette/tuple-it
비동기 호출의 결과와 오류를 튜플 형식으로 반환
const [error, data] = await tuple(someAsyncFunction())
// 성공: [null, 결과값]
// 실패: [error] 또는 [new TupleItError(error)]
오류 처리 간소화 - 단일 체크로 오류 관리 가능
💡
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,
}
}
로컬·테스트 서버에서 크롬 인증서 경고를 강제로 통과한다.
언제: 이 연결은 비공개로 설정되지 않았습니다 화면에 NET::ERR_CERT_AUTHORITY_INVALID·NET::ERR_CERT_COMMON_NAME_INVALID 가 뜨고, 고급 → 안전하지 않음(계속)이 없거나 매번 누르기 번거로울 때.
절차
- 경고 화면 아무 곳이나 클릭한다 — 페이지에 포커스를 준다
- 입력창 없이 그대로
thisisunsafe를 타이핑한다
입력창이 없는 게 정상이다. 경고 페이지의 keypress 핸들러가 한 글자씩 받아 BYPASS_SEQUENCE 와 대조하고, 다 맞으면 CMD_PROCEED 를 보낸다 — “안전하지 않음(계속)“을 누른 것과 같은 명령이다.
확인: 마지막 글자를 치는 순간 넘어간다. 안 넘어가면 포커스가 주소창에 있는 것이다 — 1번부터.
출처
useSyncExternalStore 활용 가능한 부분들