[Dev] @vitejs/plugin-react
@vitejs/plugin-react는 Vite에서 React를 원활하게 사용할 수 있도록 도와주는 공식 플러그인입니다.
📌 역할:
- Vite에서 React Fast Refresh 지원
- JSX/TSX 변환 최적화
- Babel을 활용한 코드 트랜스파일링
react-infinite-scroller
import InfiniteScroll from "react-infinite-scroller";
import {useInfiniteQuery} from "@tanstack/react-query";
import {People} from "./People";
interface PageData {
next: string | null;
results: People[];
}
const initialUrl = "https://swapi.py4e.com/api/people/";
const fetchUrl = async (url: string) => {
const response = await fetch(url);
return response.json();
};
export function InfinitePeople() {
const {data, fetchNextPage, hasNextPage, isFetching, isError, error} = useInfiniteQuery({
queryKey: ["sw-people"],
queryFn: ({pageParam = initialUrl}) => fetchUrl(pageParam),
getNextPageParam: (lastPage: PageData) => lastPage.next || undefined,
initialPageParam: initialUrl
});
if (isError) return <div>Error: {error.message}</div>;
return (
<div className="content__wrap">
<h2 className="title">People - Infinite Scroll</h2>
<div className="infinite__wrap" style={{overflow: "hidden", padding: "0"}}>
<ul className="infinite__wrap-item" style={{height: "450px", overflow: "auto"}}>
{data && (
<InfiniteScroll
initialLoad={false}
useWindow={false}
loadMore={() => {
if (!isFetching) {
fetchNextPage();
}
}}
hasMore={hasNextPage}
>
{data?.pages.map(pageData => {
return pageData.results.map(people => {
return (
<People
key={people.name}
name={people.name}
hair_color={people.hair_color}
eye_color={people.eye_color}
/>
);
});
})}
</InfiniteScroll>
)}
</ul>
</div>
{isFetching && (
<div className="loading-bar">
<div className="loading"></div>
</div>
)}
</div>
);
}
[Dev] globals
globals는 Node.js 및 브라우저 환경에서 사용되는 전역 변수 목록을 제공하는 npm 패키지입니다.
즉, 어떤 전역 변수들이 사용할 수 있는지 쉽게 확인할 수 있도록 도와주는 모듈입니다.

browser
import globals from 'globals';
console.log(globals.browser);
// { window: true, document: true, fetch: true, console: true, ... }
// 브라우저 전역 객체 사용
window.alert("Hello, world!");
document.body.style.backgroundColor = "lightblue";
Node.js
import globals from 'globals';
console.log(globals.node);
// { process: true, __dirname: true, Buffer: true, global: true, ... }
// Node.js 전역 객체 사용
console.log(__dirname); // 현재 실행 중인 파일의 디렉토리 경로
console.log(process.env.NODE_ENV); // 환경 변수 출력
ES6
import globals from 'globals';
console.log(globals.es6);
// { Promise: true, Map: true, Set: true, Symbol: true, ... }
// ES6 전역 객체 사용
const map = new Map();
map.set("key", "value");
console.log(map.get("key")); // value
Web Worker
import globals from 'globals';
console.log(globals.worker);
// { self: true, postMessage: true, importScripts: true, ... }
// Web Worker 내부 코드
self.onmessage = (event) => {
console.log("Received:", event.data);
self.postMessage("Hello from Worker!");
};
CommonJs
import globals from 'globals';
console.log(globals.commonjs);
// { require: true, module: true, exports: true, ... }
// CommonJS 모듈 사용
const fs = require("fs");
module.exports = {
readFile: (path) => fs.readFileSync(path, "utf-8")
};
react-toastify
toast.success(<h4>🔔 새로운 알림</h4>, {
toastId: `copy-account-number`,
autoClose: 1000,
position: "bottom-center",
pauseOnHover: false,
closeButton: false,
closeOnClick: true,
hideProgressBar: true
});
isbot
User-Agent 헤더를 읽어 봇을 감지할 수 있습니다.
import express from "express";
import isbot from "isbot";
const app = express();
app.get("/", (req, res) => {
if (isbot(req.get("User-Agent"))) {
return res.status(403).send("Bot access denied");
}
res.send("Hello, Human! 🤖❌");
});
app.listen(3000, () => console.log("Server is running on port 3000"));
sass
1. styles.scss 파일 작성
// styles.scss
$primary-color: #007bff;
.button {
background-color: $primary-color;
color: white;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
&:hover {
background-color: darken($primary-color, 10%);
}
}
2. React 컴포넌트에서 SCSS 파일 불러오기
import React from "react";
import "./styles.scss"; // SCSS 파일을 직접 import
const App = () => {
return <button className="button">클릭하세요</button>;
};
export default App;
zustand
persist는 상태를 로컬 스토리지에 저장하고, 페이지 새로고침 후에도 상태를 유지하게 해줍니다.
import create from 'zustand';
import { persist } from 'zustand/middleware';
interface CounterState {
count: number;
increase: () => void;
reset: () => void;
}
const useCounterStore = create(
persist<CounterState>(
(set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
reset: () => set({ count: 0 }),
}),
{
name: 'counter-storage', // 로컬 스토리지 키 이름
}
)
);
export default useCounterStore;
devtools를 사용하면 Redux DevTools에 상태 변경 기록을 표시할 수 있습니다.
import create from 'zustand';
import { devtools } from 'zustand/middleware';
interface CounterState {
count: number;
increase: () => void;
reset: () => void;
}
const useCounterStore = create<CounterState>()(
devtools((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
reset: () => set({ count: 0 }),
}))
);
export default useCounterStore;
subscribeWithSelector를 사용하면 특정 상태만 감지할 수 있습니다.
import create from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
interface CounterState {
count: number;
increase: () => void;
}
const useCounterStore = create(
subscribeWithSelector<CounterState>((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
}))
);
// 상태 변경 감지
const unsubscribe = useCounterStore.subscribe(
(state) => state.count, // count 상태만 감지
(count) => {
console.log(`Count changed to: ${count}`);
}
);
// unsubscribe()를 호출해 감지 중단 가능
@uidotdev/usehooks
@uidotdev/usehooks는 React에서 자주 사용하는 유용한 훅들을 모아 놓은 라이브러리입니다. 이 라이브러리는 커스텀 훅을 제공하여 개발자가 쉽게 재사용할 수 있도록 돕습니다. 다양한 UI 및 상태 관리 작업을 간소화할 수 있는 훅들이 포함되어 있어 개발 시간을 줄이는 데 유용합니다.
useDebounce:값이 변경될 때마다 딜레이를 두고 업데이트하도록 하는 훅입니다. 일반적으로 입력 필드에서 사용되어, 입력이 끝난 후에만 값을 업데이트합니다.
import React, { useState } from 'react';
import { useDebounce } from '@uidotdev/usehooks';
const DebounceInput = () => {
const [inputValue, setInputValue] = useState('');
const debouncedValue = useDebounce(inputValue, 500); // 500ms 후에 업데이트
return (
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Type something..."
/>
<p>Debounced Value: {debouncedValue}</p>
</div>
);
};
export default DebounceInput;
useThrottle:일정 시간 간격으로 값의 변화를 제한하는 훅입니다. 연속적인 이벤트에서 과도한 호출을 방지하는 데 유용합니다.
const ThrottleButton = () => {
const [count, setCount] = useState(0);
const throttledCount = useThrottle(count, 1000); // 1초 간격으로 업데이트
const handleClick = () => {
setCount((prev) => prev + 1);
};
return (
<div>
<button onClick={handleClick}>Click me!</button>
<p>Throttled Count: {throttledCount}</p>
</div>
);
};
그 외.... 버튼 throttle (중복클릭방지)
const ThrottleButton = () => {
const [count, setCount] = useState(0);
const isThrottled = useRef(false); // 클릭 제한 상태
const handleClick = useCallback(() => {
if (isThrottled.current) return; // 클릭이 제한된 경우 함수 종료
isThrottled.current = true; // 클릭 제한 시작
setCount((prev) => prev + 1);
setTimeout(() => {
isThrottled.current = false; // 1초 후 클릭 제한 해제
}, 1000); // 1초 후
}, []);
return (
<div>
<button onClick={handleClick}>Click me!</button>
<p>Throttled Count: {count}</p>
</div>
);
};
useFetch:API 요청을 간편하게 처리하는 훅입니다. URL을 입력하면 데이터를 가져오고, 로딩 상태와 오류 처리를 자동으로 관리합니다.
import React from 'react';
import { useFetch } from '@uidotdev/usehooks';
const DataFetchingComponent = () => {
const { data, error, loading } = useFetch('https://jsonplaceholder.typicode.com/posts');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Fetched Posts:</h1>
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default DataFetchingComponent;
useLocalStorage:로컬 스토리지에 값을 저장하고, 해당 값을 쉽게 읽고 쓸 수 있도록 돕는 훅입니다. 페이지를 새로 고침해도 데이터가 유지됩니다.
import React from 'react';
import { useLocalStorage } from '@uidotdev/usehooks';
const LocalStorageComponent = () => {
const [name, setName] = useLocalStorage('name', ''); // 초기값은 ''
const handleChange = (e) => {
setName(e.target.value);
};
return (
<div>
<input
type="text"
value={name}
onChange={handleChange}
placeholder="Enter your name"
/>
<p>Your name in local storage: {name}</p>
</div>
);
};
export default LocalStorageComponent;
useWindowSize:브라우저 창의 크기를 추적하는 훅으로, 반응형 디자인에 유용합니다. 창 크기가 변경될 때마다 자동으로 업데이트됩니다.
import React from 'react';
import { useWindowSize } from '@uidotdev/usehooks';
const WindowSizeComponent = () => {
const { width, height } = useWindowSize();
return (
<div>
<h1>Window Size</h1>
<p>Width: {width}px</p>
<p>Height: {height}px</p>
</div>
);
};
export default WindowSizeComponent;
@emotion/styled, @emotion/react
Emotion 은 JS 로 css 스타일을 작성하도록 설계된 라이브러리다.
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
export default function TestMain() {
return (
<>
<div>
<p>Test Main...</p>
<div
css={css`
padding: 32px;
background-color: hotpink;
font-size: 24px;
border-radius: 4px;
&:hover {
color: red;
}
`}
>
Hover to change color.
</div>
</div>
</>
);
}
@headlessui/react
https://headlessui.com/react/listbox#installation
Headless UI
Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
headlessui.com
React 애플리케이션을 위한 UI 컴포넌트를 제공하는 라이브러리입니다. 이 라이브러리는 접근성이 뛰어난 기본 UI 요소를 만드는 데 도움을 주며, 스타일링을 개발자가 직접 할 수 있도록 "헤드리스" 방식으로 설계되었습니다.
@lottiefiles/react-lottie-player
Lottie 애니메이션 파일을 React에서 쉽게 재생하고 제어할 수 있는 라이브러리입니다.
Lottie는 Adobe After Effects로 만든 애니메이션을 JSON 형식으로 변환하여 웹과 모바일에서 사용할 수 있게 해주는 라이브러리입니다.
import animationData from '@/assets/lottie.json';
import React, { useEffect, useState } from 'react';
const LottieAnimation: React.FC = () => {
const [isClient, setIsClient] = useState(false);
const [LottiePlayer, setLottiePlayer] = useState<any>(null);
useEffect(() => {
setIsClient(true);
import('@lottiefiles/react-lottie-player').then((module) => {
setLottiePlayer(() => module.Player);
});
}, []);
if (!isClient || !LottiePlayer) {
return null; // 클라이언트가 아니거나 LottiePlayer가 로드될 때까지 아무것도 렌더링하지 않음
}
return (
<LottiePlayer
autoplay
loop
src={animationData}
style={{ height: '300px', width: '300px' }}
/>
);
};
export default LottieAnimation;
무료 로티 애니메이션, 모션 그래픽을 위한 모든 플러그인과 도구를 한 곳에 - 로티파일즈/LottieFi
로티파일즈/LottieFiles는 오픈소스 애니메이션 포맷 로티를 제공하는 세상에서 가장 큰 무료 플랫폼입니다. 웹, iOS, 안드로이드, 윈도우를 위한 모션 그래픽 작업을 지원하는 플러그인과 툴을 통
lottiefiles.com
motion
framer-motion은 강력한 애니메이션 라이브러리로, 다양한 애니메이션을 쉽게 구현할 수 있도록 도와줍니다
https://motion.dev/docs/react-use-velocity
useVelocity | Motion for React (prev Framer Motion)
Create a motion value that tracks the velocity of another motion value.
motion.dev
react-spring
react-spring은 애니메이션과 물리 기반 시뮬레이션을 위해 설계된 라이브러리입니다.
https://www.react-spring.dev/examples
Examples | React Spring
Examples Got an example you want to see here & share with the community? Check out this guide.
www.react-spring.dev
react-use
`react-use` 는 다양한 React 훅을 제공하는 유틸리티 라이브러리입니다.
브라우저 API, 상태 관리, 센서 감지, 애니메이션, 타이머 등을 쉽게 다룰 수 있는 훅들이 포함되어 있어 생산성을 높여줍니다.
Vite의 vite.config.ts 파일에서 ssr.noExternal 옵션을 설정하여 react-use를 ESM 방식으로 번들링하도록 강제할 수 있습니다
vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
ssr: {
noExternal: ['react-use']
}
});
useLocalStroage - 로컬스토리지 활용
import { useLocalStorage } from 'react-use';
const Example = () => {
const [value, setValue, remove] = useLocalStorage('my-key', '초기값');
return (
<div>
<p>저장된 값: {value}</p>
<button onClick={() => setValue('새로운 값')}>값 변경</button>
<button onClick={remove}>삭제</button>
</div>
);
};
usePrevious - 이전 상태값
import { useState } from 'react';
import { usePrevious } from 'react-use';
const Example = () => {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>현재 카운트: {count}</p>
<p>이전 카운트: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
};
useMeasure - dom요소 크기 측정
import { useMeasure } from 'react-use';
const Example = () => {
const [ref, { width, height }] = useMeasure<HTMLDivElement>();
return (
<div ref={ref} style={{ resize: 'both', overflow: 'auto', padding: '20px', border: '1px solid black' }}>
<p>너비: {width}px</p>
<p>높이: {height}px</p>
</div>
);
};
useMouse - 마우스 위치 감지
import { useMouse } from "react-use";
import { useRef } from "react";
const Example = () => {
const ref = useRef<HTMLDivElement | null>(null);
const { elX, elY } = useMouse(ref as React.RefObject<HTMLDivElement>); // 타입 단언 사용
return (
<div
ref={ref}
style={{
width: "300px",
height: "200px",
border: "1px solid black",
position: "relative",
}}
>
<p>요소 기준 X: {elX}px</p>
<p>요소 기준 Y: {elY}px</p>
</div>
);
};
useInterval - 간격 반복 실행
마운트 시 자동으로 정리(clean-up)됨.
import { useState } from 'react';
import { useInterval } from 'react-use';
const Example = () => {
const [count, setCount] = useState(0);
useInterval(() => {
setCount(count + 1);
}, 1000); // 1초마다 실행
return <p>카운트: {count}</p>;
};
useClickAway - 외부 클릭 감지
import React, { useRef } from 'react';
import { useClickAway } from 'react-use';
const Example = () => {
const ref = useRef(null);
const [open, setOpen] = React.useState(false);
useClickAway(ref, () => setOpen(false));
return (
<div>
<button onClick={() => setOpen(true)}>메뉴 열기</button>
{open && (
<div ref={ref} style={{ border: '1px solid black', padding: '10px' }}>
외부 클릭하면 닫힘
</div>
)}
</div>
);
};
useToggle - 값 토글 (true ↔ false)
import { useToggle } from 'react-use';
const Example = () => {
const [isOn, toggle] = useToggle(false);
return (
<div>
<p>상태: {isOn ? 'ON' : 'OFF'}</p>
<button onClick={toggle}>토글</button>
</div>
);
};
useWindowSize - 브라우저 창 크기 가져오기
import { useWindowSize } from 'react-use';
const Example = () => {
const { width, height } = useWindowSize();
return (
<p>브라우저 크기: {width}px × {height}px</p>
);
};
useNetworkState - 네트워크 상태 감지 (online/offline)
import { useNetworkState } from 'react-use';
const Example = () => {
const { online } = useNetworkState();
return (
<p>네트워크 상태: {online ? '🟢 온라인' : '🔴 오프라인'}</p>
);
};
useBattery - 배터리 상태 감지
import { useBattery } from 'react-use';
const Example = () => {
const battery = useBattery();
if (!battery.isSupported) return <p>배터리 상태를 확인할 수 없는 기기입니다.</p>;
if (!battery.fetched) return <p>배터리 정보를 가져오는 중...</p>;
return (
<div>
<p>배터리 레벨: {Math.round(battery.level * 100)}%</p>
<p>{battery.charging ? '🔌 충전 중' : '🔋 방전 중'}</p>
</div>
);
};
useLockBodyScroll - 스크롤 잠금 (modal 등에서 유용)
import { useLockBodyScroll } from "react-use";
import { useEffect, useState } from "react";
const Example = () => {
const [open, setOpen] = useState(false);
useLockBodyScroll(open); // 모달이 열리면 스크롤 잠금
return (
<div>
<button onClick={() => setOpen(true)}>모달 열기</button>
{open && (
<div
style={{
position: "fixed",
inset: 0,
background: "rgba(0,0,0,0.5)",
color: "white",
padding: "20px",
}}
>
<p>모달 내용</p>
<button onClick={() => setOpen(false)}>닫기</button>
</div>
)}
</div>
);
};
useFullscreen - 풀스크린 모드 전환
import { useRef } from "react";
import { useFullscreen, useToggle } from "react-use";
const Example = () => {
const ref = useRef<HTMLDivElement>(null);
const [show, toggle] = useToggle(false);
const isFullscreen = useFullscreen(ref as (React.RefObject<HTMLDivElement>), show, {
onClose: () => toggle(false),
});
return (
<div ref={ref} style={{ backgroundColor: "white" }}>
<div>{isFullscreen ? "Fullscreen" : "Not fullscreen"}</div>
<button onClick={() => toggle()}>Toggle</button>
<video
src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
autoPlay
/>
</div>
);
};
@lukemorales/query-key-factory
React Query의 키(key)를 생성하고 관리하는 데 도움을 주는 유틸리티 라이브러리입니다.
import { createQueryKey } from '@lukemorales/query-key-factory';
// 기본 쿼리 키 생성
const userQueryKey = createQueryKey('user', { id: 1 });
// 쿼리 키를 사용하는 React Query
const { data } = useQuery(userQueryKey, fetchUserById);
compare-versions
`compare-versions`는 두 버전 문자열을 비교할 수 있도록 도와주는 JavaScript 라이브러리입니다
import {compareVersions} from "compare-versions";
export default function TestMain() {
// 두 버전 비교
const version1 = "1.0.0";
const version2 = "1.1.0";
// 버전 비교
const result = compareVersions(version1, version2);
if (result < 0) {
console.log(`${version1} is less than ${version2}`);
} else if (result > 0) {
console.log(`${version1} is greater than ${version2}`);
} else {
console.log(`${version1} is equal to ${version2}`);
}
return <></>;
}
const versions = ['1.0.0', '1.1.0', '0.9.0', '1.0.1'];
const sortedVersions = versions.sort(compareVersions);
console.log(sortedVersions); // ['0.9.0', '1.0.0', '1.0.1', '1.1.0']
react-barcode:
React에서 바코드를 생성하기 위한 라이브러리입니다.
import Barcode from 'react-barcode';
const BarcodeExample = () => {
return (
<div>
<h1>바코드 생성 예시</h1>
<Barcode
value="123456789012" // 바코드로 변환할 문자열
width={2} // 바코드 너비
height={100} // 바코드 높이
displayValue={true} // 바코드 아래에 값을 표시할지 여부
fontSize={20} // 표시할 글꼴 크기
/>
</div>
);
};
react-qr-code
React에서 QR 코드를 생성하기 위한 라이브러리입니다.
import QRCode from 'react-qr-code';
const Example = () => {
return (
<div>
<h1>QR Code Example</h1>
<QRCode value="https://www.naver.com" size={256} />
</div>
);
};
react-tooltip
React에서 툴팁을 쉽게 구현할 수 있도록 지원하는 라이브러리입니다.
import { Tooltip } from 'react-tooltip';
const Example = () => {
return (
<div style={{ padding: "50px" }}>
<h1>React Tooltip Example</h1>
<button data-tooltip-id="tooltip" data-tooltip-content="This is a tooltip!">
Hover over me!
</button>
<Tooltip id="tooltip" place="top" delayShow={500}/>
</div>
);
};
react-cookie
: 서버 사이드 렌더링(SSR)에서 쿠키를 사용하려면 universal-cookie도 함께 설치하는 것이 좋습니다.
React에서 쿠키를 관리하고 사용할 수 있도록 도와주는 라이브러리입니다.
import { useCookies } from 'react-cookie';
const CookieExample = () => {
const [cookies, setCookie, removeCookie] = useCookies(['user']);
const handleSetCookie = () => {
setCookie('user', 'JohnDoe', { path: '/', maxAge: 3600 }); // 1시간 동안 유지
};
const handleRemoveCookie = () => {
removeCookie('user', { path: '/' });
};
return (
<div>
<h1>React Cookie Example</h1>
<p>쿠키 값: {cookies.user || '쿠키 없음'}</p>
<button onClick={handleSetCookie}>쿠키 설정</button>
<button onClick={handleRemoveCookie}>쿠키 삭제</button>
</div>
);
};
export default CookieExample;
slick-carousel
: slick-carousel을 React에서 사용하려면 react-slick 라이브러리를 함께 설치해야 합니다.
슬라이드/캐러셀 컴포넌트를 구현하기 위한 라이브러리입니다
import Slider from 'react-slick';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
const images = [
'https://via.placeholder.com/600x300?text=Slide+1',
'https://via.placeholder.com/600x300?text=Slide+2',
'https://via.placeholder.com/600x300?text=Slide+3',
];
const SlickCarouselExample = () => {
const settings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 2000,
};
return (
<div style={{ width: '600px', margin: 'auto' }}>
<Slider {...settings}>
{images.map((src, index) => (
<div key={index}>
<img src={src} alt={`Slide ${index + 1}`} style={{ width: '100%', borderRadius: '10px' }} />
</div>
))}
</Slider>
</div>
);
};
export default SlickCarouselExample;
.styled-components
컴포넌트 기반으로 스타일링할 수 있는 CSS-in-JS 라이브러리입니다.
아래 두개의 소스는 같은 소스
styled.button<StyledButtonProps>`
background-color: ${props => (props.primary ? 'blue' : 'gray')};
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
`;
const StyledButton = styled.button((props: StyledButtonProps) => {
return `
background-color: ${props.primary ? 'blue' : 'gray'};
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
`;
});
react-errorboundry
`react-error-boundary` 라이브러리는 React 컴포넌트에서 발생하는 오류를 잡아서 대체 UI(fallback)를 표시할 수 있도록 해줍니다.
이때, fallback UI를 제공하는 3가지 방법이 있습니다

1. fallback
import { ErrorBoundary } from "react-error-boundary";
function App() {
return (
<ErrorBoundary fallback={<p>Something went wrong!</p>}>
<ProblematicComponent />
</ErrorBoundary>
);
}
2. fallbackRender
import { ErrorBoundary } from "react-error-boundary";
function ErrorFallback({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) {
return (
<div>
<p>Oops! An error occurred: {error.message}</p>
<button onClick={resetErrorBoundary}>Try Again</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<ErrorFallback error={error} resetErrorBoundary={resetErrorBoundary} />
)}
>
<ProblematicComponent />
</ErrorBoundary>
);
}
3. fallbackComponent
import { ErrorBoundary, FallbackProps } from "react-error-boundary";
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div>
<p>Oops! An error occurred: {error.message}</p>
<button onClick={resetErrorBoundary}>Try Again</button>
</div>
);
}
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ProblematicComponent />
</ErrorBoundary>
);
}
react-slick, slick-carousel
`react-slick`은 React에서 슬라이더(캐러셀, Carousel) 를 쉽게 만들 수 있도록 해주는 라이브러리입니다.
이 라이브러리는 내부적으로 Slick.js(jQuery 기반)를 감싸서 사용합니다.
따라서, CSS 스타일과 기능을 정상적으로 사용하려면 `slick-carousel`도 함께 설치해야 합니다.
import Slider from "react-slick";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import type { JSX } from "react/jsx-runtime";
const images = [
"https://via.placeholder.com/600x300?text=Slide+1",
"https://via.placeholder.com/600x300?text=Slide+2",
"https://via.placeholder.com/600x300?text=Slide+3",
];
export default function ReactSlickDemo() {
const settings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 1, //한 번에 화면에 보이는 슬라이드 개수를 1로 설정합니다.
slidesToScroll: 1,//슬라이드를 넘길 때 한 번에 이동하는 슬라이드 개수를 1로 설정합니다.
autoplay: true,
autoplaySpeed: 200,
};
function retrun(arg0: JSX.Element) {
throw new Error("Function not implemented.");
}
return (
<>
<Slider {...settings}>
{images.map((src, index) => {
return (
<div key={index}>
<img
src={src}
alt={`Slide ${index + 1}`}
style={{ width: "100%", borderRadius: "10px" }}
/>
</div>
);
})}
</Slider>
</>
);
}
overlay-kit
import { overlay, OverlayProvider } from "overlay-kit";
import { useState } from "react";
import { AnimatePresence, motion, type Variants } from "framer-motion";
import { useRef, type PropsWithChildren } from "react";
export function OverlayKitDemo() {
return (
<OverlayProvider>
<div>
<DemoWithState />
<DemoWithEsOverlay />
</div>
</OverlayProvider>
);
}
function DemoWithState() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<p>Demo with useState</p>
<button onClick={() => setIsOpen(true)}>open modal</button>
<Modal isOpen={isOpen}>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<p>MODAL CONTENT</p>
<button onClick={() => setIsOpen(false)}>
close modal
</button>
</div>
</Modal>
</div>
);
}
function DemoWithEsOverlay() {
return (
<div>
<p>Demo with overlay-kit</p>
<button
onClick={() => {
overlay.open(({ isOpen, close, unmount }) => {
return (
<Modal isOpen={isOpen} onExit={unmount}>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<p>MODAL CONTENT</p>
<button onClick={close}>close modal</button>
</div>
</Modal>
);
});
}}
>
open modal
</button>
</div>
);
}
type ModalProps = {
isOpen?: boolean;
onExit?: () => void;
};
export function Modal({
children,
isOpen = false,
onExit,
}: PropsWithChildren<ModalProps>) {
const prevIsOpenRef = useRef(isOpen);
if (isOpen !== prevIsOpenRef.current) {
prevIsOpenRef.current = isOpen;
if (prevIsOpenRef.current === false) {
setTimeout(() => onExit?.(), 300);
}
}
return (
<AnimatePresence>
{isOpen === true && (
<ModalContent isOpen={isOpen}>{children}</ModalContent>
)}
</AnimatePresence>
);
}
const MODAL_CONTENT_VARIANTS: Variants = {
hidden: { opacity: 0, scale: 0.75 },
show: { opacity: 1, scale: 1 },
};
function ModalContent({ children, isOpen }: PropsWithChildren<ModalProps>) {
return (
<div
style={{
zIndex: 100,
position: "fixed",
top: 0,
left: 0,
bottom: 0,
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<motion.section
variants={MODAL_CONTENT_VARIANTS}
initial="hidden"
exit="hidden"
animate={isOpen ? "show" : "hidden"}
style={{
padding: 120,
borderWidth: 1,
borderStyle: "solid",
borderColor: "gray",
borderRadius: 12,
}}
>
{children}
</motion.section>
</div>
);
}
react-infinite-scroller
React에서 무한 스크롤(Infinite Scroll) 을 구현할 때 주로 사용하는 기능입니다.
- InfiniteScroll: 사용자가 페이지를 스크롤할 때 데이터를 추가로 불러오는 UI 컴포넌트
- useInfiniteQuery: React Query 라이브러리에서 제공하는 무한 스크롤을 위한 데이터 패칭 함수
import { useInfiniteQuery } from "@tanstack/react-query";
import InfiniteScroll from "react-infinite-scroller";
export interface People {
name: string;
hair_color: string;
eye_color: string;
}
type PeopleProps = People;
export function People({ name, hair_color, eye_color }: PeopleProps) {
return (
<li>
{name}
<ul>
<li>hair: {hair_color}</li>
<li>eyes: {eye_color}</li>
</ul>
</li>
);
}
interface PageData {
next: string | null;
results: People[];
}
interface PageData {
next: string | null;
results: People[];
}
const initialUrl = "https://swapi.py4e.com/api/people/";
const fetchUrl = async (url: string) => {
const response = await fetch(url);
return response.json();
};
export function InfinitePeople() {
const { data, fetchNextPage, hasNextPage, isFetching, isError, error } =
useInfiniteQuery({
queryKey: ["sw-people"],
queryFn: ({ pageParam = initialUrl }) => fetchUrl(pageParam),
getNextPageParam: (lastPage: PageData) =>
lastPage.next || undefined,
initialPageParam: initialUrl,
});
if (isError) return <div>Error: {error.message}</div>;
return (
<div className="content__wrap">
<h2 className="title">People - Infinite Scroll</h2>
<div
className="infinite__wrap"
style={{ overflow: "hidden", padding: "0" }}
>
<ul
className="infinite__wrap-item"
style={{ height: "450px", overflow: "auto" }}
>
{data && (
<InfiniteScroll
initialLoad={false}
useWindow={false}
loadMore={() => {
if (!isFetching) {
fetchNextPage();
}
}}
hasMore={hasNextPage}
>
{data?.pages.map((pageData) => {
return pageData.results.map((people) => {
return (
<People
key={people.name}
name={people.name}
hair_color={people.hair_color}
eye_color={people.eye_color}
/>
);
});
})}
</InfiniteScroll>
)}
</ul>
</div>
{isFetching && (
<div className="loading-bar">
<div className="loading"></div>
</div>
)}
</div>
);
}
예시응답
{
"count": 87,
"next": "https://swapi.py4e.com/api/people/?page=2",
"previous": null,
"results": [
{
"name": "Obi-Wan Kenobi",
"height": "182",
"mass": "77",
"hair_color": "auburn, white",
"skin_color": "fair",
"eye_color": "blue-gray",
"birth_year": "57BBY",
"gender": "male",
"homeworld": "https://swapi.py4e.com/api/planets/20/",
"films": [
"https://swapi.py4e.com/api/films/1/",
"https://swapi.py4e.com/api/films/2/",
"https://swapi.py4e.com/api/films/3/",
"https://swapi.py4e.com/api/films/4/",
"https://swapi.py4e.com/api/films/5/",
"https://swapi.py4e.com/api/films/6/"
],
"species": [
"https://swapi.py4e.com/api/species/1/"
],
"vehicles": [
"https://swapi.py4e.com/api/vehicles/38/"
],
"starships": [
"https://swapi.py4e.com/api/starships/48/",
"https://swapi.py4e.com/api/starships/59/",
"https://swapi.py4e.com/api/starships/64/",
"https://swapi.py4e.com/api/starships/65/",
"https://swapi.py4e.com/api/starships/74/"
],
"created": "2014-12-10T16:16:29.192000Z",
"edited": "2014-12-20T21:17:50.325000Z",
"url": "https://swapi.py4e.com/api/people/10/"
}
]
}
'WEB개발 > REACT' 카테고리의 다른 글
React 참고, 오류 (0) | 2025.02.20 |
---|---|
[환경구성] tsconfig.js, tsc (0) | 2025.02.13 |
[React] Next, Typescript... 오류모음 (0) | 2024.01.26 |
[React] web성능 메트릭 (0) | 2023.12.18 |
[React] Typescript (1) | 2023.12.18 |