[React Native TIL]동기적으로 useState이용, useRef, 커스텀 Hooks(useFetch hook - useEffect에서 async/await사용하기)

cooking_123·2024년 3월 13일

React Native TIL

목록 보기
16/30

1. useState

setter 함수가 비동기처럼 동작하는 경우

아래 코드의 경우 2씩 올라가야 하는데 각각의 setter함수가 비동기로 동작하여 1씩만 증가한다.

const [count,setCount] = useState(0)
(...)
onPress={()=>{
	setCount(count+1);
	setCount(count+1)
}}

동기적으로 setter함수를 작동하고 싶다면

setter 함수에 함수를 전달해서 상태 변수의 현재 값을 바탕으로 변화를 주어야 한다. setCount 함수에 설정하는 값을 값이 아닌 함수 형태로 전달해서 상태변수의 현재 값을 바탕으로 변화를 주어야 한다.

const [count,setCount] = useState(0)
(...)
onPress={()=>{
	setCount(count => {return count+1});
	setCount(count => {return count+1})
}}

위의 코드처럼 진행하게되면 동기적으로 진행하여 2씩 증가하게 된다.

2. useRef

useRef hook을 사용하면 특정 컴포넌트를 선택할 수 있다.

useRef 특징

  1. 컴포넌트의 ref에 지정을 하고 사용해야 한다. ex.const refInput =useRef(null)
  2. 변수를 바로 사용하는 것이 아니라 current property에 지정이 되기 때문에 반드시 current property를 이용해야 한다. ex.<Input ref={refInput} />
  3. useState와는 다르게 변경된다 하더라도 컴포넌트가 리렌더링 되지 않는다. ex.refInput.current.focus()

React-Native에서 useRef 사용

import React, { useState, useEffect, useRef } from 'react';

const Form = () => {
    const [name, setName] = useState('');
    const [email, setEmail] = useState('');
	//useRef를 이용해서 refName과 refEmail을 만들어줌
    const refName = useRef(null);
    const refEmail = useRef(null);

    useEffect(() => {
      //처음 렌더링 되었을때 refName컴포넌트에 포커스가 가도록 설정
        refName.current.focus();
    }, []);

    return (
        <>
            <StyledText>Name : {name}</StyledText>
            <StyledText>Email : {email}</StyledText>
            <StyledInput
                value={name}
                onChangeText={setName}
                placeholder='name'
				//해당 컴포넌트를 refName으로 설정
                ref={refName}
                returnKeyType='next'
				//next 버튼을 누르게 되면 reEmail로 설정된 컴포넌트로 포커스를 이동
                onSubmitEditing={() => refEmail.current.focus()}
            />
            <StyledInput
                value={email}
                onChangeText={setEmail}
                placeholder='email'
				//해당 컴포넌트를 refEmail로 설정
                ref={refEmail}
                returnKeyType='done'
                onSubmitEditing={제출함수}
            />
        </>
    );
};

export default Form;

3. 커스텀 Hooks

useFetch

//hooks/useFetch.js

import { useState, useEffect } from 'react';
// Fetch를 활용한 API 요청을 보내는 간단한 Hook
export const useFetch = (url) => {
    //파라미터로 url을 전달받음
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);
    // uesFetch에서 url을 전달받아 api 요청을 보내고 성공 여부에 따라 데이터 혹은 에러를 반환해야 한다.
    // useEffect를 이용해서 api 호출을 할건데 비동기 통신이기에 async,await를 이용

    useEffect(async () => {
        try {
            //fetch를 이용해서 response를 받음
            //setData를 이용해서 데이터를 설정
            const res = await fetch(url);
            const result = await res.json();
            setData(result);
        } catch (e) {
            // try, catch문을 이용해 에러 발생시 catch로 들오면 setError를 이용해 에러의 데이터를 설정
            setError(e);
        }
    }, []); // 마운투 되었을때만 실행
    return { data, error };
    // 마지막으로 데이터와 에러를 반환해서 useFetch를 사용했을때 데이터와 에러를 반환
};

위의 코드로 진행시 아래와 같은 오류 코드를 볼 수 있는데 이 경고 메세지는 useEffect에 비동기 함수를 전달했을 때 나타나는 경고메세지입니다.

useEffect must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:

💥 useEffect에서 async/await 사용 오류 이유와 해결방법은?

useEffect에 전달되는 함수는 비동기 함수를 이용할 수 없기 때문에 비동기 함수를 사용해야 할 떄는 useEffect 내부에서 비동기 함수를 정의하고 호출하는 방법으로 해결할 수 있다.

위의 코드에서 useEffect에 전달되는 함수에서 async를 삭제하고 getData라는 함수를 새로만들어 줘서 async await를 이용해주었다.

import { useState, useEffect } from 'react';

export const useFetch = (url) => {
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);

    useEffect(() => {
        const getData = async () => {
            try {
                const res = await fetch(url);
                const result = await res.json();
                setData(result);
            } catch (e) {
                setError(e);
            }
        };
        getData();
    }, []);
    return { data, error };
};

api의 진행 여부를 확인하고 싶다면...api 호출하는 동안 로딩화면 구현

import { useState, useEffect } from 'react';

export const useFetch = (url) => {
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);
    //api의 진행 여부를 확인하는 상태변수. 호출 시작 전이니 false로 설정
    const [inProgress, setInProgress] = useState(false);

    useEffect(() => {
        const getData = async () => {
            try {
                setInProgress(true); // api호출이 시작되었으니 true로 설정
                const res = await fetch(url);
                const result = await res.json();
                setData(result);
            } catch (e) {
                setError(e);
            } finally {
                //정상적으로 종료가 되든 에러가 생기든 api가 끝났으니 false 설정
                setInProgress(false);
            }
        };
        getData();
    }, []);
    return { data, error, inProgress };
};
import React, { useState } from 'react';
import CoinInfo from './components/CoinInfo';
import styled from 'styled-components/native';
import { useFetch } from './hooks/useFetch';

const Container = styled.View`
    flex: 1;
    background-color: #fff;
    align-items: center;
    justify-content: center;
`;

const LoadingText = styled.Text`
    font-size: 30px;
    color: #ff6600;
`;

const App2 = () => {
    const URL = `https://api.coinlore.net/api/tickers/?limit=3`;
    const { data, error, inProgress } = useFetch(URL);
    return (
        <Container>
      // api가 호출되는 동안 Loading 텍스트 브라우저상에 보여줌
            {inProgress && <LoadingText>Loading...</LoadingText>} 
            {data?.data.map(({ symbol, name, price_usd }) => (
                <CoinInfo key={symbol} symbol={symbol} name={name} price={price_usd} />
            ))}
        </Container>
    );
};

export default App2;

0개의 댓글