React HTTP 요청 보내기

맛없는콩두유·2022년 8월 29일
0
post-thumbnail

HTTP 요청 보내기

React는 데이터베이스와 앱으로 직접 연결을하거나 데이터를 직접 가져오는 행위는 외부 환경에서 안되는 일 중 하나이다. 인증 정보를 노출시키는 행위이기 떄문이다. 간단하게 개발자도구를 통해서 코드를 볼 수 있기 때문에 안전하지 않다. 그래서 백엔드 App을 이용해야한다. Node.js가 대표적이다!

시작 앱 및 백엔드

이 과정에서 백엔드 앱인 Start Wars API를 이용해 App을 만들어보겠습니다.
https://swapi.dev/ 을 이용하여

function App() {
  const dummyMovies = [
    {
      id: 1,
      title: 'Some Dummy Movie',
      openingText: 'This is the opening text of the movie',
      releaseDate: '2021-05-18',
    },
    {
      id: 2,
      title: 'Some Dummy Movie 2',
      openingText: 'This is the second opening text of the movie',
      releaseDate: '2021-05-19',
    },
  ];

이 dummy data들을 대체해보겠습니다!

GET 요청 보내기

Fetch API를 이용해서 영화 정보를 가져오도록 하겠습니다.

  return (
    <React.Fragment>
      <section>
        <button>Fetch Movies</button>
      </section>
      <section>
        <MoviesList movies={dummyMovies} />
      </section>
    </React.Fragment>
  );
}

먼저 Button이 클릭될 때마다 영화 정보를 가져오고 이에 대한 결과를 화면에 표시하겠습니다.

function App() {
  const [movies, setMovies] = useState([]);

  function fetchMoviesHandler() {
    fetch('https://swapi.dev/api/films/')
      .then((response) => {
        return response.json();
      })
      .then((data) => {
        const transformedMovies = data.results.map((movieData) => {
          return {
            id: movieData.episode_id,
            title: movieData.title,
            openingText: movieData.opening_crawl,
            releaseDate: movieData.release_date,
          };
        });
        setMovies(transformedMovies);
      });
  }

dummy 데이터를 없애고, fetch()를 통해 얻고자 하는 url을 연결시켜주었습니다.

const Movie = (props) => {
  return (
    <li className={classes.movie}>
      <h2>{props.title}</h2>
      <h3>{props.releaseDate}</h3>
      <p>{props.openingText}</p>
    </li>
  );
};
"results": [
        {
            "title": "A New Hope", 
            "episode_id": 4, 
            "opening_crawl": "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a hidden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....", 
            "director": "George Lucas", 
            "producer": "Gary Kurtz, Rick McCallum", 
            "release_date": "1977-05-25", 
            "characters": [

Movie.js에서 title, relaseDate, opningText라는 key으로 받았기 때문에 api의 key와 일치시키는 이름을 선정해줘야합니다.

const transformedMovies = data.results.map((movieData) => {
          return {
            id: movieData.episode_id,
            title: movieData.title,
            openingText: movieData.opening_crawl,
            releaseDate: movieData.release_date,
          };
        });
        setMovies(transformedMovies);
      });

그러기 위해서 transformedMovie에 api를 하나의 새로운 배열로 담아 api의 key 명을 Movie.js에서 props로 받은 이름들을 일치시켜주는 작업을 진행하였습니다. 그리고 그 일치시킨 것을 setMovies로 다시 셋팅해주면 api에서 불러온 것이 화면에보여지는 것을 볼 수 있습니다.

비동기/대기 사용하기

function App() {
  const [movies, setMovies] = useState([]);

  async function fetchMoviesHandler() {
    const reponse = await fetch("https://swapi.dev/api/films/");
    const data = reponse.json();

    const transformedMovies = data.results.map((movieData) => {
      return {
        id: movieData.episode_id,
        title: movieData.title,
        openingText: movieData.opening_crawl,
        releaseDate: movieData.release_date,
      };
    });
    setMovies(transformedMovies);
  }

async와 await를 이용하여 .then으로 적었던 구문들을 줄일 수 있습니다! await를 이용할 때는 반드시 async를 적어줘야합니다!

로딩 및 데이터 State 처리하기

버튼을 클릭하였을 떄 데이터가 로딩되는 화면을 보여주고싶다.

그러기 위해서는 또 다른 상태를 만들어서 로딩 중임을 알려줘야한다.


function App() {
  const [movies, setMovies] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  async function fetchMoviesHandler() {
    setIsLoading(true);
    const response = await fetch("https://swapi.dev/api/films/");
    const data = await response.json();

    const transformedMovies = data.results.map((movieData) => {
      return {
        id: movieData.episode_id,
        title: movieData.title,
        openingText: movieData.opening_crawl,
        releaseDate: movieData.release_date,
      };
    });
    setMovies(transformedMovies);
    setIsLoading(false);
  }

//  const [isLoading, setIsLoading] = useState(false); 구문을 추가하고
초기값은 false로 사용하고 fetchMoviesHandler 함수가 불러올 때 true로 바꿔주고, 
마지막에 다시 false로 세팅해준다!

return (
    <React.Fragment>
      <section>
        <button onClick={fetchMoviesHandler}>Fetch Movies</button>
      </section>
      <section>
        {!isLoading && movies.length > 0 && <MoviesList movies={movies} />}
        {!isLoading && movies.length === 0 && <p>Found no movies.</p>}
        {isLoading && <p>Loading...</p>}
      </section>
    </React.Fragment>
  );
  
> 로딩이 안될 떄, 무비가 있을 떄에는 목록을 보여주고
> 로딩이 안될 떄, 무비가 없을 때, 로딩중일 때를 각각 다르게 보여준다. 

무비가 없을 때

Loading 중일 때

Http 오류 처리하기

200 201 2xx로 오류가 나면 서버가 정상적으로 반응을 한 것입니다.
하지만 요청할 때 401이나 403은 서버가 요청을 받았으나 원하는 응답을 주지 않았음을 의미합니다.
그리고 500대 오류는 서버의 에러를 의미합니다.

예를 들어, fetch에 잘못된 주소를 입력해보겠스빈다.

 async function fetchMoviesHandler() {
    setIsLoading(true);
    const response = await fetch("https://swapi.dev/api/film/"); 
    //잘못된 주소
    const data = await response.json();


의도했던 대로서버로 갔고 이에 대한 응답을 받았으나, 응답은 404 상태 코드이고, 무언가 문제가 있음을 의미합니다. 이경우 서버가 준비하지 못한 것을 요청한 것 입니다.

이런 오류를 처리하려면 새로운 errorState를 만들어 관리를 해야합니다!

const [error, setError] = useState(null); 를 추가하고 
setError(null); //fetchMoviesHandler함수를 실행할 떄  
이전 에러를  null로 초기화 해줍니다.
    try {
      const response = await fetch("https://swapi.dev/api/films/");
      if (!response.ok) {
        throw new Error("Something went wrong!");
      }

      const data = await response.json();

      const transformedMovies = data.results.map((movieData) => {
        return {
          id: movieData.episode_id,
          title: movieData.title,
          openingText: movieData.opening_crawl,
          releaseDate: movieData.release_date,
        };
      });
      setMovies(transformedMovies);
    } catch (error) {
      setError(error.message);
    }
    setIsLoading(false);
    
if (!response.ok) {
        throw new Error("Something went wrong!");
      }
     //response가 ok가 아니라면 에러메세지를 삽입하여 
     catch문에 error message를 셋팅해줍니다. 
 let content = <p>Found no movies.</p>;

  if (movies.length > 0) {
    content = <MoviesList movies={movies} />;
  }

  if (error) {
    content = <p>{error}</p>;
  }

  if (isLoading) {
    content = <p>Loading...</p>;
  }

  return (
    <React.Fragment>
      <section>
        <button onClick={fetchMoviesHandler}>Fetch Movies</button>
      </section>
      <section>{content}</section>
    </React.Fragment>
  );
}
//각각 content를 상황별로 달리하여 관리할 수 있습니다.

요청에 useEffect() 사용하기

현재 우리는 Fetch Movies 버튼을 눌러야만 API의 데이터를 불러오고 있습니다. 하지만 보통 애플리케이션에서는 첫 화면에 바로 데이터들이 보여지게 됩니다. 즉시 fetch를 하려면 useEffect 훅을 사용하면 됩니다!

주의! 함수로 호출해도 되지만, 의존성 배열 자리에 그 함수를 다시 호출하면 재 랜더링, 재평가되면서 재무한루프에 빠지게됩니다! 이를 피가히 위해서 useeEffect 훅을 사용하겠습니다!

fetchMoviesHandler를 처음 렌더링할 때 보여줄 텐데, fetchMoviesHandler를 포인터로 가리켜 의존성 배열에 넣으면 바뀔 때 마다 재 랜더링된다.

  useEffect(() => {
    fetchMoviesHandler();
  }, [fetchMoviesHandler]);
  -> 하지만 무한 루프에 빠지게된다.
여기서 필요한 것은 불러오고자 하는 함수에 useCallback을 활용하면 된다.

const fetchMoviesHandler = useCallback(async () => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch("https://swapi.dev/api/films/");
      if (!response.ok) {
        throw new Error("Something went wrong!");
      }

      const data = await response.json();

      const transformedMovies = data.results.map((movieData) => {
        return {
          id: movieData.episode_id,
          title: movieData.title,
          openingText: movieData.opening_crawl,
          releaseDate: movieData.release_date,
        };
      });
      setMovies(transformedMovies);
    } catch (error) {
      setError(error.message);
    }
    setIsLoading(false);
  }, []);

무한 루프에 빠지지 않고, 첫 화면과 버튼을 클릭했을 때도 잘 나오는 것을 볼 수 있다!

Firebase를 통한 백엔드 작업

  • 준비 REST API를 제공하는 풀 백엔드 어플리케이션인 FIrebase를 이용하여 POST 방식으로 데이터를 전송해보겠습니다.
    https://console.firebase.google.com/
    구글로 로그인을 하여 데이터베이스를 생성을 하면 URL 주소가 나오게 된다.

Movie를 추가하는 작업을 위해 AddMovie 컴포넌트를 추가하여 POST 요청을 시작하려한다.

 const fetchMoviesHandler = useCallback(async () => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch('https://react-http-ecb71-default-rtdb.firebaseio.com/movies.json');
      if (!response.ok) {
        throw new Error('Something went wrong!');
      }

      const data = await response.json();

      const transformedMovies = data.results.map((movieData) => {
        return {
          id: movieData.episode_id,
          title: movieData.title,
          openingText: movieData.opening_crawl,
          releaseDate: movieData.release_date,
        };
      });
      setMovies(transformedMovies);
    } catch (error) {
      setError(error.message);
    }
    setIsLoading(false);
  }, []);

fetch 주소에 새로운 URL 주소를 넣고, 마지막에 .json을 꼭 넣어줘야한다! 영화를 불러올 것이기 때문에 movies.json 이라는 주소로 요청했다.

본격적으로 Add Movie 버튼을 눌렀을 떄 POST 방식으로 데이터를 전송하는 준비 작업은 끝이났다.

POST 요청 보내기

 async function addMovieHandler(movie) {
    const response = await fetch(
      "https://react-http-ecb71-default-rtdb.firebaseio.com/movies.json",
      {
        method: "POST",
        body: JSON.stringify(movie),
        headers: {
          "content-Type": "application/json",
        },
      }
    );
    const data = await response.json();
    console.log(data);
  }

마찬가지로 addMovieHandler에 fetch를 요청하여 똑같은 url을 집어넣고
이 떄, method를 POST로 적어야하며, 비동기 통신이기 때문에 async와 await를
꼭 적어줘야한다!

    const loadedMovies = [];

      for (const key in data) {
        //data는 객체이기 떄문에 for - in문 사용
        loadedMovies.push({
          id: key,
          title: data[key].title, //동적 접근 방식
          openingText: data[key].openingText,
          releaseDate: data[key].releaseDate,
        });
      }

      setMovies(loadedMovies);
    } catch (error) {
      setError(error.message);
    }
    setIsLoading(false);
  }, []);

data는 한 객체이고, key라는 변수명으로 for문을 이용해 새로운 loadedMovies라는 빈배열에 객체로 추가하였고, 이 떄, 동적 접근방식으로 data[key]속성으로 들어가 title/opningText/releaseData를 용도에 맞는 key명으로 선언하였다.


그리고 Add Movie에 내용을 적으면,


실시간 데이터베이스에 암호화된 ID값인 객체로 전달되는 것을 알 수 있다.

profile
하루하루 기록하기!

0개의 댓글