스터디에서 한 React에서 Pagination 구현하기 과제를 리팩토링해보았다. 스터디 팀장님의 피드백과 팀원들의 코드를 살펴보며 추가하면 좋을 점을 정리해서 issue에 작성해서 올렸다.
|
이제 리팩토링을 해보자 🐱
//App.js
function Nav() {
return (
<Routes>
<Route path="/" element={<Layout />} />
<Route path=":page" element={<PictureList />} />
<Route index element={<Layout />} />
</Routes>
);
}
Route index란 default child routes로, 네스팅이 되기 때문에 현재까지의 경로 + '/' 를 설정할 수 있다.
App.js에서 라우팅을 설정한다. 처음에는 Layout 안에 PictureList를 중첩시키고 Nav()에도 포함시켰더니 두번 렌더링이 되서 Nav()에서 PictureLsit만 라우팅해주고, Layout에 Nav()를 중첩시켰다.
그리고 꼭 index.js에 BrowserRouter로 감싸주자.. 꼭.. 이거 발견 못 했다가 시간 쪼끔 날렸다 휴....
//App.js
function Nav() {
return (
<Routes>
<Route path="/" element={<PictureList />} />
<Route path=":/page" element={<PictureList />} />
</Routes>
);
}
function App() {
return (
<>
<ListProvider>
<Layout>
<Nav />
</Layout>
</ListProvider>
</>
);
}
export default App;
그리고 Pagination에 코드가 길어질 것 같아서 Pagination.jsx 컴포넌트를 따로 만들었다. 여기서 페이징을 하고 알맞은 라우트로 navigate을 이용해 이동하게 된다.
const Pagination = ({ currentPageGroup }) => {
const navigate = useNavigate();
const pageNumbers = useMemo(
() => [...Array(5)].map((_, i) => (currentPageGroup - 1) * 5 + i + 1),
[currentPageGroup],
);
const moveNextPagesGroup = () => {
navigate(`/${currentPageGroup * 5 + 1}`);
};
const movePreviousPageGroup = () => {
navigate(`/${(currentPageGroup - 1) * 5}`);
};
return (
<PaginationBlock>
{currentPageGroup === 1 ? null : (
<button onClick={movePreviousPageGroup}>{`<`}</button>
)}
{pageNumbers.map((_, idx) => {
return (
<NavLink
key={idx}
className={({ isActive }) => (isActive ? "active" : null)}
to={`/${(currentPageGroup - 1) * 5 + idx + 1}`}>
<button>{(currentPageGroup - 1) * 5 + idx + 1}</button>
</NavLink>
);
})}
<button onClick={moveNextPagesGroup}>{`>`}</button>
</PaginationBlock>
);
};
PictureList.jsx 여기서는 정해진 수 만큼 데이터를 fetch 해 와야 한다.
const PictureList = () => {
let { page } = useParams();
page = parseInt(page, 10);
if (isNaN(page) || page < 0) {
page = 1;
}
const [loading, data, error] = usePromise(page); //1page, 45개씩 fetch
const currentPageGroup = useMemo(() => Math.ceil(page / 5), [page]);
//...
}
usePromise는 커스텀 훅으로 데이터를 가져오는 역할을 한다.
loading, resolved, error을 하나의 배열로 return 하며, 이 훅을 호출한 곳에서 destructuring하면 된다
usePromise.js
//usePromise.js
function usePromise(page, limit = 9) {
const URL = "https://api.thecatapi.com/v1/images/search";
const API_KEY =
"live_TMHkfzpN281MrIv3tbYggwCuoviA3a5CjNGvVIbY9bPIVbeSbTZ6rY5Ndnc2BbdP";
const [loading, setLoading] = useState(false);
const [resolved, setResolved] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.get(URL, {
headers: {
"x-api-key": API_KEY,
},
params: {
page: page,
limit: limit,
order: "ASC",
},
});
setResolved(response.data);
setLoading(false);
} catch (e) {
setLoading(false);
setError(e);
}
};
fetchData();
}, [page, limit]);
return [loading, resolved, error];
}
마지막으로 Picture Item 컴포넌트에서는
1. context API를 통해 listed view === true 인지 데이터를 읽어온다. 만약 true라면 각 아이템의 width를 넓혀 자리를 길게 차지해 listed view가 되게 한다.
2. imgURL을 아직 못 가져왔다면 onLoad속성을 주어 ImgLoading(false)를 한다. 여전히 true일 경우 loading... 이라는 글자를 각 아이템마다 표시한다.
const PictureItem = ({ id, imgURL }) => {
const [imgLoading, setImgLoading] = useState(true);
const { listed } = useContext(ListContext).state;
const [listview, setListview] = useState(listed);
useEffect(() => {
setListview(listed);
}, [listed]);
return (
<PictureItemBlock listview={listview}>
{imgLoading && <div className="loading">Loading...</div>}
{<img src={imgURL} alt="item" onLoad={() => setImgLoading(false)} />}
<p>Name: {id}</p>
</PictureItemBlock>
);
};
export default PictureItem;
listed === true면 styled-component에서 props를 이용해 스타일을 바꾼다.
${({ listview }) =>
listview &&
css`
width: 500px;
border-bottom: 0.5px solid gray;
text-align: start;
`}
listed의 데이터를 관리하는 context API는 요기
/* eslint-disable react/prop-types */
import { createContext, useState } from "react";
const ListContext = createContext({
state: { listed: false },
actions: {
setListed: () => {},
},
});
const ListProvider = ({ children }) => {
const [listed, setListed] = useState(false);
const value = {
state: { listed: listed },
actions: { setListed: setListed },
};
return <ListContext.Provider value={value}>{children}</ListContext.Provider>;
};
const ListConsumer = ListContext.Consumer;
export { ListProvider, ListConsumer };
export default ListContext;
App 어디에서나 사용 가능하다.
routing 개념이 익숙하지 않아서 어디서 어디로 가는지 헷갈렸었다.. 연습을 더 많이 해봐야할듯 🥹 아직 공부할게 산더미같이 쌓였다는 느낌을 받았다. 좋은 경험이었따 🌊
'프로젝트 회고 🌊' 카테고리의 다른 글
React 기능 구현 스터디 - 2. 검색 기능 (리팩토링 회고) ☀️ (0) | 2023.07.03 |
---|---|
React 리액트 투두리스트 앱 (+ Redux, Styled-component) (0) | 2023.06.29 |
WIL - Virtual DOM (0) | 2023.06.26 |
스터디 발표 회고 2023.06.21 (0) | 2023.06.24 |
CS 스터디 발표 회고 1 - 비동기 처리, RESTful API (0) | 2023.06.19 |