-
[고양이 사진 검색 사이트] 검색 페이지 - autofocus, Loading, api 연결, try & catch, async, await,img titleReact/과제 테스트 2023. 8. 7. 13:21
프로그래머스 과제 테스트 준비
프로그래머스는 html,css, javaScript이기 때문에 실질적으로 도움이 될 만한 React로 준비했다.
테스팅에 목적이 아닌 학습을 위한 구현자료로써, 디자인은 하지 않았고 기능에 초점을 두었다.
🐇 검색 페이지
🥕 페이지 진입 시 검색창(input)에 focus처리
※ autofocus
autoFocus속성은 일반 HTML에서와 같은 방식으로 작동합니다.
css에는 input autofocus가 있다. 하지만, 입력이 여러 개인 경우autoFocus한 페이지에autoFocus무시됩니다.
그래서 useRef와 useEffect를 사용하여 구현했다.
1. input 검색창에 ref를 이용한다.
블로그 참고(https://developer-talk.tistory.com/129)
const inputFocus = useRef(null);<inputtype="search"placeholder="고양이를 검색해주세요"ref={inputFocus}value={keyword}onClick={clearKeyword}onChange={displayKeyword}onKeyDown={checkEnterKey}onFocus={() => setIsVisibleHistory(true)}onBlur={() => setIsVisibleHistory(false)}/>2. useEffect 사용하여 페이지 진입 시 포커스
useEffect(() => {inputFocus.current.focus();}, []);🥕 키워드가 입력된 상태에서 input을 클릭 시 기존 키워드 삭제
input 태그에 클릭 이벤트를 구현하면 된다.
1.useState를 사용하여 input value의 상태를 관리한다.
// input value로 useState를 사용하여 상태관리, 양반향 데이터 바인딩, 재렌더링 최소화를 한다.const [keyword, setKeyword] = useState('');2. input 태그의 value를 useState로 사용자의 입력 값을 저장한다.
<inputtype="search"placeholder="고양이를 검색해주세요"ref={inputFocus}value={keyword}onClick={clearKeyword}onChange={displayKeyword}/>3. input 태그 함수 구현한다.
// 사용자가 값을 입력하면 보여준다.const displayKeyword = (e) => {setKeyword(e.target.value);};
//사용자가 input 클릭시 초기화한다.const clearKeyword = () => {setKeyword('');};🥕 사용자의 검색 요청
Get : /cats/search | query ⇒ q=””
HTTP/1.1 200 OK { "data": [{ id: string url: string name: string }] }
※ API 연결 (async, await, try, catch)
API fetch 코드를 async , await 문을 이용하여 수정해주세요. 해당 코드들은 에러가 났을 경우를 대비해서 적절히 처리가 되어있어야 합니다.필수 API 의 status code 에 따라 에러 메시지를 분리하여 작성해야 합니다. 아래는 예시입니다.
const request = async (url: string) => { try { const result = await fetch(url); return resu1. ~./api.js 함수 생성
2. api endpoint 상수 생성
const API_ENDPOINT =3. 공통 함수 생성
const fetchData = async (url) => {try {const response = await fetch(url);if (!response.ok) {throw new Error('Network response was not ok');}return response.json();} catch (error) {console.error('Error while fetching data:', error);return [];}};4. 각각의 함수 작성
export const getRandomCatData = async () => {return fetchData(`${API_ENDPOINT}/api/cats/random50`);};
export const getSearchCatData = async (keyword, pages) => {return fetchData(`${API_ENDPOINT}/api/cats/search?q=${keyword}&page=${pages}`);};
export const getDetailCatData = async (id) => {return fetchData(`${API_ENDPOINT}/api/cats/${id}`);};5. 검색 작동 구현
※ event.key === 'Enter'을 사용해서 검색 동작 구별했다.
// 사용자가 고양이를 검색한다.const checkEnterKey = async (event) => {if (event.key === 'Enter') {await searchCats(keyword);}};const searchCats = async (keyword) => {setIsLoading(true);const catsList = await getSearchCatData(keyword, currentPages);console.log(catsList.data);setCatsList(catsList.data);
if (keyword !== '') saveSearchResultToLocalStorage(keyword);
// 검색어 중복 제거 및 최대 5개까지 저장const historySet = new Set([keyword, ...searchHistory]);setSearchHistory([...historySet].slice(0, 5));setIsLoading(false);};<inputtype="search"placeholder="고양이를 검색해주세요"ref={inputFocus}value={keyword}onClick={clearKeyword}onChange={displayKeyword}onKeyDown={checkEnterKey}onFocus={() => setIsVisibleHistory(true)}onBlur={() => setIsVisibleHistory(false)}/>🥕 데이터 요청 시 Loading UI 추가 필요
※ API 연결 (async, await, try, catch)
fetch는 async await를 활용하여 진행하고 에러가 나는 경우 적절히 처리한다
status code에 따라 에러메세지를 분리하여 작성한다
1. loading 상태관리
const [isLoading, setIsLoading] = useState(false);2. loading 컴포넌트 제작
- 이때, user의 theme 파악
- spinner로 구현
import React from 'react';import styled, { keyframes } from 'styled-components';
const Loading = ({ isDarkMode }) => {console.log(isDarkMode);return (<S.SpinnerWrapper isDarkMode={isDarkMode}><S.Spinner /></S.SpinnerWrapper>);};
const spin = keyframes`to { transform: rotate(360deg); }`;const size = 100;
const S = {SpinnerWrapper: styled.div`width: 100vw;height: 100vh;display: flex;justify-content: center;align-items: center;position: absolute;z-index: 3;background-color: ${({ isDarkMode }) =>isDarkMode ? '#0d0d0d' : '#f5f7f7'};`,
Spinner: styled.div`width: ${size}px;height: ${size}px;border-width: ${size / 12}px;border-style: solid;border-color: #e6f7fa;border-top-color: #cff4fa;border-radius: 50%;animation: ${spin} 0.8s linear infinite;box-sizing: border-box;`,};export default Loading;3. loading 상태일 때 loading 컴포넌트 리턴
if (isLoading) return <Loading isDarkMode={isDarkMode} />;🥕 검색결과가 없는 경우 유저가 파악 할 수 있도록 UI 처리 필요
1. fail 컴포넌트 제작
2. 검색 결과 없을 시 fail 컴포넌트 리턴
{catsList?.length ? (<S.ImgContainer>{catsList.map((v, i) => (<S.Imgloading="lazy"src={v.url}key={i}alt={v.id}title={v.name}onClick={() => displayModal(v.id)}/>))}🥕 최근 5개의 검색 키워드를 SearchInput아래 표시되도록 생성. 해당 키워드를 선택할 시 검색요청 발생
1. 최근 검색 기록을 useState로 저장
const [searchHistory, setSearchHistory] = useState([]);2. 검색시 history 저장
- 단, 중복 제거
const searchCats = async (keyword) => {setIsLoading(true);const catsList = await getSearchCatData(keyword, currentPages);console.log(catsList.data);setCatsList(catsList.data);
if (keyword !== '') saveSearchResultToLocalStorage(keyword);
// 검색어 중복 제거 및 최대 5개까지 저장const historySet = new Set([keyword, ...searchHistory]);setSearchHistory([...historySet].slice(0, 5));setIsLoading(false);};3. history 가시성 여부 state로 관리하기
const [isVisibleHistory, setIsVisibleHistory] = useState(true);4. input focus / blur 상태 확인하여 history 보여주기
<inputtype="search"placeholder="고양이를 검색해주세요"ref={inputFocus}value={keyword}onClick={clearKeyword}onChange={displayKeyword}onKeyDown={checkEnterKey}onFocus={() => setIsVisibleHistory(true)}onBlur={() => setIsVisibleHistory(false)}/>{searchHistory.length > 0 && isVisibleHistory && (<ul>{searchHistory.map((keyword, i) => (<li key={i}>{keyword}</li>))}</ul>)}🥕 페이지 새로고침 시 마지막 검색결과 화면 유지
최근 검색 기록 localStorge에 저장하여, 페이지 진입시 마지막 검색결과 보여준다.
1. 페이지 진입시 localStorage에서 검색 결과 불러오기
useEffect(() => {// Local Storage에서 검색 결과를 불러오는 함수const loadSearchResultFromLocalStorage = () => {const savedResult = localStorage.getItem('lastSearchResult') || '';if (savedResult) {console.log('A', JSON.parse(savedResult));setKeyword(JSON.parse(savedResult));searchCats(JSON.parse(savedResult));}};loadSearchResultFromLocalStorage();}, []);2. 검색시 localStorage 검색 키워드 저장
// 검색 결과를 Local Storage에 저장하는 함수const saveSearchResultToLocalStorage = (data) => {localStorage.setItem('lastSearchResult', JSON.stringify(data));};// 사용자가 고양이를 검색한다.const searchCats = async (keyword) => {setIsLoading(true);const catsList = await getSearchCatData(keyword, currentPages);console.log(catsList.data);setCatsList(catsList.data);
if (keyword !== '') saveSearchResultToLocalStorage(keyword);
// 검색어 중복 제거 및 최대 5개까지 저장const historySet = new Set([keyword, ...searchHistory]);setSearchHistory([...historySet].slice(0, 5));setIsLoading(false);};🥕 SearchInput옆 자유롭게 버튼을 추가하여 /api/cats/random50 을 호출하여 화면에 렌더링.
1. random 버튼 추가
<S.Button onClick={randomCats}>random</S.Button>// S.styledButton: styled.button`padding: 10px 20px;font-size: 16px;background-color: #007bff;color: #fff;border: none;border-radius: 5px;cursor: pointer;
&:hover {background-color: #0056b3;}
&:focus {outline: none;box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);}
&:active {background-color: #0056b3;box-shadow: none;}`,2. /cats/random api 호출
//랜덤으로 고양이를 보여준다.const randomCats = async () => {setIsLoading(true);const catsList = await getRandomCatData();console.log(catsList.data);setCatsList(catsList.data);setIsLoading(false);};🥕 Lazy load를 이해하고 이미지가 화면에 보일 시점에 loading되도록 처리
- html에서 lazy loading을 구현 방법
*<image loading = 'lazy'> 사용
- Lazy Loading
React.lazy는 컴포넌트에 대한 코드분할을 하기위해 리액트에서 제공하는 내장 기능입니다.
일반적으로 리액트로 만들어진 앱을 빌드하게 되면, 보통 js파일이 index어쩌구 파일 하나로 통합빌드가 이루어집니다.
그 말인 즉슨, 최초 앱 렌더링시에 당장 보여지지 않는 컴포넌트라 할지라도 모두 미리 불러오기 때문에 최초 로딩 속도를 지연시킬 가능성이 높습니다.
그리고 이런 문제점을 해결하기 위해 React.lazy가 필요하게 되고, lazy는 필요에 따라 컴포넌트를 불러오도록 동작해주기 때문에 당연히 성능적인 향상을 기대할 수 있습니다
0. lazy, suspense 가져온다
import { useEffect, useRef, useState, Suspense, lazy } from 'react';1. 컴포넌트를 변수로 import 한다.
const Modal = lazy(() => import('./component/Modal'));2. suspense로 감싼다.
<Suspense fallback={<div>Loading...</div>}><ModalisOpen={isModalOpen}onClose={() => setIsModalOpen(false)}id={currentCat}/></Suspense>🥕 검색결과 각 아이템에 고양이 이름을 노출한다.
img title 추가
<S.Imgloading="lazy"src={v.url}key={i}alt={v.id}title={v.name}onClick={() => displayModal(v.id)}/>'React > 과제 테스트' 카테고리의 다른 글
[Input 이벤트] input readonly, 백스페이스 기능 (0) 2023.08.08 [Input 이벤트] 포커스 이벤트, 영역 외부 클릭시 상태 변화, document.addEventListener (0) 2023.08.08 [고양이 사진 검색 사이트] 스크롤 페이지 구현, 랜덤 고양이 배너 section,EventDelegation (0) 2023.08.07 [고양이 사진 검색 사이트] - 이미지 상세 보기 모달 : 모달, keypress, fade in/out (0) 2023.08.07 [고양이 사진 검색 사이트] HTML, CSS - 시맨틱, media query, 다크모드(dark mode) (0) 2023.08.07