
🍀 What I Learned this week
📌 이미지 오픈 API를 통해 이미지 정보 조회 사이트 만들기
📌 Next.js와 Supabase를 이용한 TO-DO List 개발
이번주는 총 2개의 프로젝트를 진행하면서 React와 Next, 그리고 Typescript에 대해 자세히 배울 수 있었다.
일주일 만에 2개나 프로젝트를 만들어서 어려웠지만 정말 배운 점이 많았다.
📌 이미지 오픈 API를 통해 이미지 정보 조회 사이트 만들기
구현한 기능
🥯 Jotai 라이브러리의 store를 이용하여 API fetch
- Jotai 라이브러리란 ?
- 중앙집중식 상태관리 라이브러리
- props에 대한 depth가 너무 깊어지는 걸 막기 위해 사용한다.(데이터 추적 어려움)
- atom (=state) : 전역에서 쓰이는 상태값
// src/store/index.ts
import { atom } from "jotai";
import axios from "axios";
export const searchValueAtom = atom<string>('korea');
export const pageAtom = atom<number>(1);
export const fetchAPI = async (searchValue: string, page: number) => {
const API_KEY = 'gcyfsAL2xYOU7tSWNxnPBikSgoeze88F9cdW2zNwjNM';
const BASE_URL = 'https://api.unsplash.com/search/photos';
try {
const res = await axios.get(
`${BASE_URL}/?query=${searchValue}&page=${page}&per_page=30&client_id=${API_KEY}`
);
return res;
} catch (error) {
console.error('API 호출 중 오류 발생');
throw error;
}
}; //store에 모듈화시켜서 코드 재사용성 높임
이를 Hompage에서, 서버API로부터 이미지 데이터들을 불러올 때 이용했다.
//src/views/HomePage.tsx
/*...*/
import { useAtom } from 'jotai';
import { fetchAPI, pageAtom, searchValueAtom } from '@/store';
function HomePage() {
const { toast } = useToast();
const [searchValue] = useAtom(searchValueAtom);
const [page] = useAtom(pageAtom);
const [images, setImages] = useState([]);
const fetchImages = useCallback(async () => {
try {
const res = await fetchAPI(searchValue, page);
if (res.status === 200 && res.data) {
setImages(res.data.results);
console.log(res.data);
} else {
toast({
variant: 'destructive',
title: 'API 호출 실패',
description: 'API 호출을 위한 필수 파라미터 값을 체크해보세요!',
});
}
} catch(error) {
console.log(error);
}
}, [searchValue, page, toast]); //필요한 의존성들만 주입
/*const fetchAPI = async () => {
const API_KEY = 'gcyfsAL2xYOU7tSWNxnPBikSgoeze88F9cdW2zNwjNM';
const BASE_URL = 'https://api.unsplash.com/search/photos';
@@ -36,11 +60,11 @@ function HomePage() {
} catch (error) {
console.log(error);
}
};*/
useEffect(() => {
fetchImages();
}, [fetchImages]); //fetchImages가 변경될 때만 실행
return (
<div className="page">
/*...*/
🥯 navigation bar
동적 라우팅을 위해 경로에 ':id'를 지정했다.
//src/App.tsx
/*...*/
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage/>}></Route>
{/* 동적 라우팅 */}
<Route path="/search/:id" element={<HomePage />}></Route>
<Route path="/bookmark" element={<Bookmark/>}></Route>
</Routes>
<Toaster/>
/*...*/
Homepage에 Nav컴포넌트가 추가되어있으므로, 이 컴포넌트의 값에 따라 다른 주소로 접근할 수 있게 된다.
Nav컴포넌트에서 useLocation()으로 현재 URL 정보를 가져왔다.
// src/components/common/nav/Nav.tsx
import { useEffect, useState } from 'react';
import { useAtom } from 'jotai';
import navJson from './nav.json';
import { Link, useLocation } from 'react-router-dom';
import styles from './nav.module.scss';
import { searchValueAtom } from '@/store';
interface Nav {
index: number;
path: string;
label: string;
searchValue: string;
isActive: boolean;
}
function Nav() {
const location = useLocation();
const [searchValue, setSearchValue] = useAtom(searchValueAtom);
const [navItem, setNavItem] = useState<Nav[]>(navJson);
useEffect(()=> {
navItem.forEach((nav: Nav)=> {
nav.isActive = false;
if(nav.path === location.pathname || location.pathname.includes(nav.path)){
nav.isActive = true;
setSearchValue(nav.searchValue);
}
});
setNavItem([...navItem]);
},[location.pathname])
/* ... */
🥯 localStorage를 이용해 북마크 데이터 저장
- localStorage란?
- 웹 스토리지(web storage)에는 로컬 스토리지(localStorage)와 세션 스토리지(sessionStorage)가 있다.
- 세션 스토리지는 웹페이지의 세션이 끝날 때 저장된 데이터가 지워지는 반면에, 로컬 스토리지는 웹페이지의 세션이 끝나더라도 데이터가 지워지지 않는다.
- 다시 말해, 브라우저에서 같은 웹사이트를 여러 탭이나 창에 띄우면, 여러 개의 세션 스토리지에 데이터가 서로 격리되어 저장되며, 각 탭이나 창이 닫힐 때 저장해 둔 데이터도 함께 소멸한다. 반면에, 로컬 스토리지의 경우 여러 탭이나 창 간에 데이터가 서로 공유되며 탭이나 창을 닫아도 데이터는 브라우저에 그대로 남아 있다.
로그인 기능을 구현하지 않았기에, 한 유저에 대한 북마크 저장 목록을 유지하기 위해 로컬 스토리지를 사용했다.
트러블 슈팅
localStorage를 이용해서 북마크 데이터를 저장할 때, 처음에 그냥 findIndex썼더니 오류가 났다.
const addBookmark = (imageData: ImageDataType) => {
console.log(imageData);
const getLocalStorage = localStorage.getItem("bookmark");
let bookmarks: ImageDataType[] = [];
if (getLocalStorage) {
try {
bookmarks = JSON.parse(getLocalStorage); //parsing을 할 땐 try-catch문으로 에러를 방지해주는 것이 좋다.
} catch (error) {
console.error("Error parsing localStorage:", error);
bookmarks = [];
}
}
if (bookmarks.length === 0) {
localStorage.setItem("bookmark", JSON.stringify([imageData]));
toast({
title: "로컬스토리지에 올바르게 저장되었습니다.",
});
} else {
const imageExists = bookmarks.findIndex((item: ImageDataType) => item.id === imageData.id) > -1;
if (imageExists) {
toast({
variant: "destructive",
title: "로컬스토리지에 해당 데이터가 이미 저장되어 있습니다.",
});
} else {
bookmarks.push(imageData);
localStorage.setItem("bookmark", JSON.stringify(bookmarks));
toast({
title: "로컬스토리지에 올바르게 저장되었습니다.",
});
}
}
};
=> Uncaught TypeError: bookmarks.findIndex is not a function at addBookmark
타입스크립트는 자바스크립트와 달리, 타입을 정확하게 지정해줘야한다! 따라서 Array인 경우 로직을 수행하도록 명확하게 경우를 구분지어주었다.
const addBookmark = (imageCard: ImageCardType) => {
console.log(imageCard);
let bookmarks: ImageCardType[] = [];
const getLocalStorage = localStorage.getItem('bookmark'); //null || string
if (getLocalStorage) {
try {
const parsedBookmarks = JSON.parse(getLocalStorage);
if (Array.isArray(parsedBookmarks)) {
bookmarks = parsedBookmarks;
} else {
console.error("Stored data is not an array");
}
} catch (error) {
console.error("Data parsing error: ", error);
}
}
const imageExists = bookmarks.findIndex((item: ImageCardType) => item.id === imageCard.id);
if (imageExists > -1) {
console.log('Data is already saved.');
} else {
bookmarks.push(imageCard);
localStorage.setItem('bookmark', JSON.stringify(bookmarks));
console.log('Data has been saved.');
}
};
소스코드 : https://github.com/mal0070/react-album
이 프로젝트는 지난주와 이번주에 걸쳐서 진행되었는데, 복습 겸 구현했던 것을 정리해보았다.
이 주에 진행한 TO-DO List 개발은 다음주에 마무리지으면서 정리해보겠다.
——————————————————————————
본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다.
'교육, 대외활동 > 유데미 프론트엔드 캠프' 카테고리의 다른 글
[유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 - KT wiz 홈페이지 개선 프로젝트 1주차 회고 (0) | 2024.12.17 |
---|---|
[유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 - 사전직무교육 3주차 학습 회고 (3) | 2024.12.10 |
[유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 - 사전직무교육 1주차 학습 회고 -(2) React 프로젝트, Typescript 개요 (1) | 2024.11.17 |
[유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 - 사전직무교육 1주차 학습 회고 -(1) 자바스크립트 문법 (0) | 2024.11.17 |
[유데미X웅진씽크빅X스팩] 프론트엔드 프로젝트 캠프 : 리액트 과정 지원기 (3) | 2024.11.06 |