> For the complete documentation index, see [llms.txt](https://ljh86029926.gitbook.io/coding-apple-react/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ljh86029926.gitbook.io/coding-apple-react/4/4-unsplash-api.md).

# Unsplash API를 이용한 사진첩 만들기

Unsplash API를 이용해서 간단한 사진접 프로젝트를 제작해 보도록 하겠습니다. [Unsplash](https://unsplash.com/) 는 다양한 주제의 사진을 이용할 수 있는 웹 사이트 입니다.

### Unsplash 회원가입

![https://unsplash.com](/files/-LYXo0W0KHjGH-KGDTH_)

회원이신 분들은 로그인 해 주세요. 회원이 아니신 분들은 회원 가입을 부탁 드립니다.

![회원가입은 페이스북을 이용하셔도 가능합니다.](/files/-LYXoCk_HUHyP7CJCYBz)

### Unsplash API 발급받기

API를 사용하기 위해서는 API를 사용할 수 있는 key를 발급 받아야 합니다. Developers 전용 메뉴가 있기 때문에 우선 이동해 주세요. [이동하기](https://unsplash.com/developers)

![https://unsplash.com/developers](/files/-LYXoj3T9hynjTkvNLn6)

Your apps라는 메뉴를 누르고 들어가 주세요. 그렇게 되면 다음과 같은 화면이 나타날 것 입니다.

![현재는 앱을 아무것도 만들지 않았기 때문에 사용할 수 없습니다.](/files/-LYXp484e7M_7pZHQMqU)

New Application버튼을 클릭해 Demo버전의 Application하나를 생성해 주도록 하겠습니다.

![주의 사항에 모두 체크를 하고 다음으로 넘어갑니다.](/files/-LYXpRspyR507NOjMG_A)

![어플리케이션의 기본적인 정보를 기입해 줍니다.](/files/-LYXpbrWU9KWI9waUtz7)

Create Application 버튼을 클릭하게 되면 Demo버전의 Application이 생성될 것 입니다.

![생성되었습니다.](/files/-LYXpq-QMDrVSiBnxbCu)

밑으로 스크롤을 조금 해 보면 Key와 관련된 부분이 보일 것 입니다.

![절대로 키를 알려주지 마세요..!](/files/-LYXrBSvgeVYlClYiAbr)

우리에게 필요한 키는 AccessKey 뿐입니다. 바로 가져다 사용할 수 있는 곳이나 편한 공간에 저장해 주세요.

### 프로젝트 세팅하기

이제 프로젝트를 시작할 준비가 끝났습니다. 리액트 작업에 들어가 보도록 하겠습니다.

`npx create-react-app unsplash` 를 통해 프로젝트를 하나 생성해 주시고,

`yarn add axios node-sass styled-components` or `npm i axios node-sass styled-components`설치하신 패키지 매니저에 맞추어 모듈 설치를 진행해 주세요. axios 라이브러리를 사용하는 이유는 Promise기반이기 때문에 에러 핸들링을 하기에 간편하고,  누구나 빠르게 익힐수 있을 정도로 라이브러리의 복잡도가 낮기 때문에 사용합니다.

![시작해 보도록 하겠습니다.](/files/-LYXsGwwcCKKuGyInbK6)

![프로젝트에 사용할 파일을 먼저 제작 하겠습니다.](/files/-LYXspKZoadE-uVke_p3)

![picture-list, picture컴포넌트와 scss 파일이 추가 되었습니다.](/files/-LYXszB_zWzUWwRpmC6O)

기존 App.css, index.css도 확장자를 변경해 주었습니다. *logo, serviceWorker같은 파일은 지우셔도 되고 안 지우셔도 됩니다.*

### State형태, 메소드 제작하기

모든 값은 App.js에서 핸들링 될 예정입니다. 당연하게도 State가 App.js에 있으니 모든 변경도 App.js에서 일어납니다.

#### State 세팅

기본적으로 어떤 데이터를 가지고 있어야 하는지를 알아야 합니다. 우선 당연히 이미지들의 URL이 string 배열 형태로 담겨져 있는 state 객체 하나면 충분합니다. 에러 핸들링을 위해서 나중에 변수 몇 개가 추가될 예정이지만, 일단 지금은 state안에 배열 하나를 생성하는 것으로 하겠습니다. App.js를 변경해 주세요.

![App.js에 state를 추가한 모습](/files/-LYXwJwyvW6xk_WtRmRT)

이제, 이미지를 불러오는 API를 제작 할 것 입니다. App.js에 메소드를 하나 만들어 주겠습니다.

![이미지를 추가하는 API 입니다.](/files/-LYXxiJvDckW6IMIvEgq)

저 메소드는 딱히 특별한 점이 없습니다. 다만 조금 이상하게 느껴질 부분이 setState부분인데요, 첫 번째 인자는 이미지를 이어준다고 치고, 두 번째 인자는 굉장히 이상한 코드같이 보입니다. 하지만 저 부분은 Unsplash의 API response값 중에서, 이미지만 빼내오려고 했기 때문에 저런 구문이 쓰여졌습니다.

![하나의 큰 배열 객체가 Response로 응답됩니다.](/files/-LYXzPGQXFPMffLfUAbe)

지금 사진에 보이는 count === 30이기 때문에, 랜덤 사진 요청을 보내면 30개의 랜덤 사진 요청응답이 옵니다. 그 30개의 정보들 중에서 우리에게 필요한 사이즈의 이미지 URL만 골라서 state를 업데이트 해야 하기 때문에 response를 setState하는 과정이 이상하게 느껴질 수도 있으나 필요한 부분입니다.

### 사진 컴포넌트 제작하기

사진 하나를 나타내 줄 수 있는 picture 컴포넌트를 제작해 보도록 하겠습니다.

![별로 특별할 것 없는 컴포넌트 입니다.](/files/-LYY33hv-vLs4c31FZfF)

### 사진 리스트 컴포넌트 제작하기

picture, picture-list 컴포넌트 두 개를 만들었습니다. 그 중에 사진들의 전체적인 리스트를 관리하는 picture-list 컴포넌트를 만들어 보도록 하겠습니다.

![그저 이미지를 받아서 Picture 컴포넌트에게 전달해 주는 역할만 합니다.](/files/-LYY3GKu-0GNT_lBqudn)

picture-list styled-components 부분

{% code title="picture-list.js " %}

```javascript
const Content = styled.div`
  margin-top: 1rem;
  /* Prevent vertical gaps */
  line-height: 0;
  @media screen and (max-width: 832px) {
    -webkit-column-count: 1;
    -webkit-column-gap: 0px;
    -moz-column-count: 1;
    -moz-column-gap: 0px;
    column-count: 1;
    column-gap: 0px;
  }
  @media screen and (min-width: 833px) and (max-width: 1232px) {
    -webkit-column-count: 2;
    -webkit-column-gap: 0px;
    -moz-column-count: 2;
    -moz-column-gap: 0px;
    column-count: 2;
    column-gap: 0px;
  }
  @media screen and (min-width: 1233px) and (max-width: 1632px) {
    -webkit-column-count: 3;
    -webkit-column-gap: 0px;
    -moz-column-count: 3;
    -moz-column-gap: 0px;
    column-count: 3;
    column-gap: 0px;
  }
  @media screen and (min-width: 1633px) {
    -webkit-column-count: 4;
    -webkit-column-gap: 0px;
    -moz-column-count: 4;
    -moz-column-gap: 0px;
    column-count: 4;
    column-gap: 0px;
  }
`;

```

{% endcode %}

### App.js에 적용시키기

일단 첫 번째로 해야할 일은, PictureList 컴포넌트를 App.js에 넣어주는 겁니다. props로는 images를 넣어주면 됩니다. 그 다음으로 해야할 일은 기본 API 요청 입니다. 현재 사이트에 접속했을 때에는 빈 배열이기 때문에 접속했을 때 빈 화면이 보입니다. 사진이 보이게 하기 위해서는 API요청이 가야 합니다. 적당한 LifeCycle은 componentDidMount가 있겠네요.&#x20;

![componentDidMount를 이용해서 API를 첫 페이지 로딩 시에 요청 했습니다.](/files/-LYg6xi2tRJrjP2N81Qu)

{% code title="App.scss" %}

```css
.App {
  > span {
    font-size: 2rem;
    font-weight: bold;
    margin: 1rem;
  }
}
```

{% endcode %}

전부 완료한 뒤에 브라우저 화면을 보면 의도한 대로 잘 출력됩니다.

![원했던 응답 값이 오고 있습니다. 더이상 빈 화면이 아닙니다.](/files/-LYY4mkGSt9DfzEMlkgS)

### 더 보기 버튼을 이용해서 더 많은 사진을 불러오기

현재 웹 사이트 로딩 시 새로운 사진 데이터를 불러오는 API를 활용해서 우리가 볼 수 있도록 하고 있습니다. 그러나 더 많은 사진을 보거나 새로운 사진을 보기 위해서는 새로고침이 필요합니다. 더 많은 사진을 불러오는 기능을 추가 해 보도록 하겠습니다.

단순한 버튼 한개가 들어있는 컴포넌트를 제작해 보도록 하겠습니다. src/components폴더에 load-more.js파일을 생성해 주세요.

![load-more.js](/files/-LYfv4xQzh7XtvcIYpIh)

styled 컴포넌트 값 입니다.

{% code title="StyledComponent Values" %}

```javascript
const WrapButton = styled.div`
  display: flex;
  justify-content: center;
`;
const Button = styled.button`
  font-size: 1.5rem;
  padding: 1rem;
  margin: 1rem;
  border: 1px solid #333333;
  border-radius: 0.5rem;
  color: black;
  cursor: pointer;
`;
```

{% endcode %}

이 컴포넌트가 하는 역할은, 단지 버튼을 눌렀을 때에, 부모로부터 받은 loadMore이라는 props를 실행시켜 주는 일 입니다. 이제 부모 Props를 주는 App.js에 LoadMore 컴포넌트를 불러와 주어야 합니다. App.js를 다음과 같이 수정해 주세요.

![render 부분만 수정되었습니다.](/files/-LYg3w8f4iuvCSA4jjos)

이후 브라우저에서 화면을 보면 다음과 같은 모습을 볼 수 있습니다.

![버튼을 누르게 되면 사진을 불러오게 됩니다.](/files/-LYfx6FxUtUlKmAZre0B)

### 사용성 증대를 위한 로딩 효과 구현

지금까지 기능은 잘 동작하고 있네요. 그러나 한가지 문제점은, API를 호출하는 과정에서 응답을 받아 setState가 일어나기 전 까지는 API가 로딩 중인지, 혹시나 버그로 인해서 로딩이 실패했는지를 알 수가 없습니다. 사용자에 어림짐작으로 너무 로딩 시간이 길어지면 오류가 생기지 않았나 생각하게 만들죠. 이것은 사용자로 하여금 불편함을 유발하기 때문에 좋은 웹 페이지라고 할 수가 없습니다. 이것을 개선해 보도록 하겠습니다.

src/components 폴더에 loader.js, loader.scss 파일을 생성해 주세요.

![로딩 컴포넌트 입니다.](/files/-LYg0_jMqVJsRiT2lLhg)

이 컴포넌트는 단순히 svg파일만을 담고 있습니다. css도 로딩되는 것처럼 도와주기 위한 것 밖에는 들어있지 않습니다.

{% code title="loader.js" %}

```javascript
import React, { Component } from 'react';
import './loader.scss';

// Thank you for https://codepen.io/akwright/ Alex
class Loader extends Component {
  render() {
    return (
      <div className="modal transparent-black-background">
        <div className="modal-card loading-container">
          <svg version="1.1" id="preloader6" width="140px" height="140px" viewBox="0 0 200 200">
            <g className="pre load6">
              <path
                fill="#1B1A1C"
                d="M124.5,57L124.5,57c0,3.9-3.1,7-7,7h-36c-3.9,0-7-3.1-7-7v0c0-3.9,3.1-7,7-7h36
	C121.4,50,124.5,53.1,124.5,57z"
              />
              <path
                fill="#1B1A1C"
                d="M147.7,86.9L147.7,86.9c-2.7,2.7-7.2,2.7-9.9,0l-25.5-25.5c-2.7-2.7-2.7-7.2,0-9.9l0,0
	c2.7-2.7,7.2-2.7,9.9,0L147.7,77C150.5,79.8,150.5,84.2,147.7,86.9z"
              />
              <path
                fill="#1B1A1C"
                d="M143,74.5L143,74.5c3.9,0,7,3.1,7,7v36c0,3.9-3.1,7-7,7l0,0c-3.9,0-7-3.1-7-7v-36
	C136,77.6,139.1,74.5,143,74.5z"
              />
              <path
                fill="#1B1A1C"
                d="M148.4,112.4L148.4,112.4c2.7,2.7,2.7,7.2,0,9.9L123,147.7c-2.7,2.7-7.2,2.7-9.9,0h0c-2.7-2.7-2.7-7.2,0-9.9
	l25.5-25.5C141.3,109.6,145.7,109.6,148.4,112.4z"
              />
              <path
                fill="#1B1A1C"
                d="M125.5,143L125.5,143c0,3.9-3.1,7-7,7h-36c-3.9,0-7-3.1-7-7l0,0c0-3.9,3.1-7,7-7h36 C122.4,136,125.5,139.1,125.5,143z"
              />
              <path
                fill="#1B1A1C"
                d="M52.3,113.1L52.3,113.1c2.7-2.7,7.2-2.7,9.9,0l25.5,25.5c2.7,2.7,2.7,7.2,0,9.9h0c-2.7,2.7-7.2,2.7-9.9,0
	L52.3,123C49.5,120.2,49.5,115.8,52.3,113.1z"
              />
              <path
                fill="#1B1A1C"
                d="M57,75.5L57,75.5c3.9,0,7,3.1,7,7v36c0,3.9-3.1,7-7,7h0c-3.9,0-7-3.1-7-7v-36C50,78.6,53.1,75.5,57,75.5z"
              />
              <path
                fill="#1B1A1C"
                d="M86.9,52.3L86.9,52.3c2.7,2.7,2.7,7.2,0,9.9L61.5,87.6c-2.7,2.7-7.2,2.7-9.9,0l0,0c-2.7-2.7-2.7-7.2,0-9.9
	L77,52.3C79.8,49.5,84.2,49.5,86.9,52.3z"
              />
            </g>
          </svg>
        </div>
      </div>
    );
  }
}

export default Loader;


```

{% endcode %}

{% code title="loader.scss" %}

```css
.modal {
  position: fixed; /* Stay in place */
  z-index: 1; /* Sit on top */
  left: 0;
  top: 0;
  width: 100%; /* Full width */
  height: 100%; /* Full height */
  overflow: auto; /* Enable scroll if needed */
  background-color: hsla(0, 0%, 0%, 0.502);

  .modal-card {
    position: static;

    @media screen and (max-width: 768px) {
      width: 300px; /* Could be more or less, depending on screen size */
    }
    @media screen and (min-width: 769px) and (max-width: 1119px) {
      width: 360px;
    }
    @media screen and (min-width: 1120px) {
      width: 420px; /* Could be more or less, depending on screen size */
    }

    margin: 15% auto; /* 15% from the top and centered */
    padding: 20px;

    .modal-header {
      display: flex;
      justify-content: space-between;
    }

    .modal-footer {
      display: flex;
      justify-content: flex-end;
    }

    > div {
      margin-top: 0.5rem;
      margin-bottom: 0.5rem;
    }

    > div:nth-child(1) {
      margin-top: 0.25rem;
      margin-bottom: 1.5rem;
    }

    > div:nth-last-child(1) {
      margin-top: 2rem;
      margin-bottom: 0.25rem;
    }
  }
}

.loading-container {
  display: flex;
  justify-content: center;
}

@for $i from 1 through 8 {
  .load6 path:nth-of-type(#{$i}) {
    -webkit-animation: spin_single 1s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
    animation: spin_single 1s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
  }
}
.pre path {
  -webkit-transform-origin: 50% 50%;
  transform-origin: 50% 50%;

  -webkit-transform: translate3d(0, 0, 0);
  transform: translate3d(0, 0, 0);

  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
@-webkit-keyframes spin_half {
  0% {
    -webkit-transform: rotate(0deg);
  }
  50% {
    -webkit-transform: rotate(360deg);
  }
}
@keyframes spin_half {
  0% {
    transform: rotate(0deg);
  }
  50% {
    transform: rotate(360deg);
  }
}
@-webkit-keyframes spin_full {
  0% {
    -webkit-transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
  }
}
@keyframes spin_full {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
@-webkit-keyframes spin_single_neg {
  0% {
    -webkit-transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(-180deg);
  }
}
@keyframes spin_single_neg {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(-180deg);
  }
}
@-webkit-keyframes spin_single {
  0% {
    -webkit-transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(180deg);
  }
}
@keyframes spin_single {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(180deg);
  }
}

```

{% endcode %}

이제 이 파일들을 API 로딩이 시작했을 때와 끝나기 전 까지 보여주어야 합니다. 그 부분을 제작해 보도록 하겠습니다. App.js파일을 다음과 같이 수정해 주세요. state를 하나 추가하고, getImages 부분을 수정할 것 입니다.

![state를 추가했습니다. ](/files/-LYg35MVQVGsWFdFN_i8)

메소드 시작과 동시에 isPending이라는 state가 true로 변경되고, API호출이 끝남과 동시에 다시 isPending이라는 변수가 false로 변경됩니다. 이제 이 변수에 맞추어서 Loader 컴포넌트를 보여주면 됩니다. render부분에 Loader 컴포넌트를 불러와 주세요. 단 isPending이 true일때만 불러와야 합니다. App.js의 render부분을 사진과 같이 수정해 주세요.

![Loader 컴포넌트를 불러왔습니다.](/files/-LYg3ZQGu64ZitPsyUGh)

isPending이라는 state가 true인 조건을 걸어, 그 조건이 활성화 되었을때만 *(&&, and연산과 같음)* Loader컴포넌트가 보여지도록 했습니다.

### 에러 핸들링

만약 axios를 통한 API요청이 실패했을 때는 어떻게 처리를 해 주어야 할까요? 현재 상태에서는 에러가 나면, 이전에 걸어두었던 isPending: true인 상태가 해제 되지도 않을 뿐더러 어디에서도 에러를 처리해주는 부분이 없어서 에러가 나게 됩니다. 이 부분을 해결해 보도록 하겠습니다. App.js를 사진과 같이 수정해 주세요.

![catch를 통한 에러 핸들링](/files/-LYgfdNNPrW3wMDtlxUW)

catch는 Promise로 반환되는 값에서 모두 사용할 수 있습니다. 특정한 코드가 실행되는 도중에 에러가 발생하게 되면 catch에 작성되어 있는 함수로 넘어갑니다. 이를 통해서 axios를 통한 API요청이 실패해도 정상적으로 재 로딩을 시도할 수 있습니다.
