[React] useState와 useEffect 활용 예제

[React] useState와 useEffect 활용 예제

React 개발을 하면서 useState와 useEffect를 언제 사용해야 하는지 고민했던 경험과 실전에서 활용 가능한 몇가지 예제들을 공유하고자 한다.

사용자 입력 관리

폼(Form) 데이터를 다룰 때 useState는 필수적이다.

function SignUpForm() {
    const [formData, setFormData] = useState({
        username: '',
        email: '',
        password: ''
    });

    const handleChange = (e) => {
        setFormData({
            ...formData,
            [e.target.name]: e.target.value
        });
    };

    return (
        <form>
            <input
                name="username"
                value={formData.username}
                onChange={handleChange}
            />
            {/* 다른 입력 필드들... */}
        </form>
    );
}

토글 상태 관리

모달, 드롭다운, 메뉴 등의 UI 요소의 열림/닫힘 상태를 관리할 때 유용하다.

function Dropdown() {
    const [isOpen, setIsOpen] = useState(false);

    return (
        <div>
            <button onClick={() => setIsOpen(!isOpen)}>
                메뉴 {isOpen ? '닫기' : '열기'}
            </button>
            {isOpen && (
                <ul>
                    <li>메뉴 1</li>
                    <li>메뉴 2</li>
                </ul>
            )}
        </div>
    );
}

로딩 상태 관리

function ProductList() {
    const [products, setProducts] = useState([]);
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState(null);

    // ...fetch 로직
}

브라우저 이벤트 리스너 관리

스크롤 위치 감지나 화면 크기 변경 감지 등에 활용한다.

function ScrollIndicator() {
    const [scrollPercentage, setScrollPercentage] = useState(0);

    useEffect(() => {
        const handleScroll = () => {
            const windowHeight = window.innerHeight;
            const documentHeight = document.documentElement.scrollHeight;
            const scrollTop = window.scrollY;
            
            const percentage = (scrollTop / (documentHeight - windowHeight)) * 100;
            setScrollPercentage(Math.round(percentage));
        };

        window.addEventListener('scroll', handleScroll);
        return () => window.removeEventListener('scroll', handleScroll);
    }, []);

    return <div>스크롤 진행도: {scrollPercentage}%</div>;
}

WebSocket 연결 관리

RTSP 통신등 실시간 데이터 통신이 필요할 때 사용한다.

function ChatRoom({ roomId }) {
    const [messages, setMessages] = useState([]);
    
    useEffect(() => {
        const ws = new WebSocket(`ws://chat.server.com/${roomId}`);
        
        ws.onmessage = (event) => {
            const message = JSON.parse(event.data);
            setMessages(prev => [...prev, message]);
        };

        return () => ws.close(); // 연결 정리
    }, [roomId]);

    return (
        // ... 채팅방 UI
    );
}

기본적인 데이터 fetching

function UserProfile({ userId }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const fetchUser = async () => {
            try {
                const response = await axios.get(`/api/users/${userId}`);
                setUser(response.data);
            } catch (error) {
                setError(error.message);
            } finally {
                setLoading(false);
            }
        };

        fetchUser();
    }, [userId]);

    if (loading) return <div>로딩중...</div>;
    if (error) return <div>에러 발생: {error}</div>;

    return (
        <div>
            <h2>{user.name}</h2>
            <p>{user.email}</p>
        </div>
    );
}

POST 처리

function SignUpForm() {
    const [formData, setFormData] = useState({
        username: '',
        email: '',
        password: ''
    });
    const [submitStatus, setSubmitStatus] = useState('idle');

    const handleSubmit = async (e) => {
        e.preventDefault();
        setSubmitStatus('loading');

        try {
            const response = await axios.post('/api/signup', formData);
            setSubmitStatus('success');
            // 성공 처리 로직
        } catch (error) {
            setSubmitStatus('error');
            // 에러 처리 로직
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            {/* 폼 필드들... */}
        </form>
    );
}

Interceptors 활용

API 요청마다 공통적으로 처리해야 하는 로직이 있을 때 유용하다.

const api = axios.create({
    baseURL: 'https://api.example.com'
});

function TodoList() {
    const [todos, setTodos] = useState([]);

    useEffect(() => {
        // 요청 인터셉터
        api.interceptors.request.use(
            config => {
                // 요청 전에 헤더에 토큰 추가
                const token = localStorage.getItem('token');
                if (token) {
                    config.headers.Authorization = `Bearer ${token}`;
                }
                return config;
            },
            error => {
                return Promise.reject(error);
            }
        );

        // 응답 인터셉터
        api.interceptors.response.use(
            response => response,
            error => {
                if (error.response.status === 401) {
                    // 인증 에러 처리
                    localStorage.removeItem('token');
                    window.location.href = '/login';
                }
                return Promise.reject(error);
            }
        );

        const fetchTodos = async () => {
            const response = await api.get('/todos');
            setTodos(response.data);
        };

        fetchTodos();
    }, []);

    return (
        // 렌더링 로직
    );
}

검색기능 구현(디바운스 적용)

function SearchComponent() {
    const [searchTerm, setSearchTerm] = useState('');
    const [results, setResults] = useState([]);
    const [isSearching, setIsSearching] = useState(false);

    useEffect(() => {
        const searchProducts = async () => {
            if (!searchTerm) {
                setResults([]);
                return;
            }

            setIsSearching(true);
            try {
                const response = await axios.get(`/api/search`, {
                    params: { q: searchTerm }
                });
                setResults(response.data);
            } catch (error) {
                console.error('검색 중 에러 발생:', error);
            } finally {
                setIsSearching(false);
            }
        };

        const delay = setTimeout(searchProducts, 500);
        return () => clearTimeout(delay);
    }, [searchTerm]);

    return (
        <div>
            <input
                type="text"
                value={searchTerm}
                onChange={(e) => setSearchTerm(e.target.value)}
                placeholder="검색어를 입력하세요"
            />
            {isSearching && <div>검색중...</div>}
            <ul>
                {results.map(item => (
                    <li key={item.id}>{item.name}</li>
                ))}
            </ul>
        </div>
    );
}

병렬요청 처리

function Dashboard() {
    const [dashboardData, setDashboardData] = useState({
        users: [],
        posts: [],
        comments: []
    });
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        const fetchDashboardData = async () => {
            try {
                const [usersResponse, postsResponse, commentsResponse] = 
                    await axios.all([
                        axios.get('/api/users'),
                        axios.get('/api/posts'),
                        axios.get('/api/comments')
                    ]);

                setDashboardData({
                    users: usersResponse.data,
                    posts: postsResponse.data,
                    comments: commentsResponse.data
                });
            } catch (error) {
                console.error('데이터 로딩 실패:', error);
            } finally {
                setLoading(false);
            }
        };

        fetchDashboardData();
    }, []);

    if (loading) return <div>대시보드 로딩중...</div>;

    return (
        // 대시보드 렌더링 로직
    );
}

댓글