Next.js 13과 redux/toolkit, styled-components 등으로 북마크 기능이 들어있는 쇼핑몰 앱을 구현한 프로젝트이며, 설명에 필요한 코드만 최소한으로 서술되었습니다. :
(1) gitHub - README 및 전체 코드 보러 가기
(2) 프로젝트 요구 명세서 보기 (해당 프로젝트 첫 포스팅)
Next.js 13 번역 프로젝트 참여 이후, 처음으로 프로젝트에 제대로 적용해본 지라 아직 개념 자체가 막연한 부분이 많았다. 다음은 그 중 하나로 프로젝트를 하면서 많이 고민해보고, StackoverFlow에 질문글을 올리면서 비로소 문제를 해결하게 된 부분을 간단하게 정리해보았다.
폴더 기반 라우팅
Next.js은 폴더 기반 라우팅으로 폴더 구조 자체가 라우팅을 정의하는 방식으로 동작한다.
- 처음 접했을 때 과히 혁신적이다..!! 라고 생각할 정도로 편리한 기능이었다.
실제 폴더 구조
layout.js 파일은 여러 페이지에 공통으로 적용되는 구성 요소 및 스타일을 포함한다.
- Next.js 13 버전의 폴더링 구조를 활용하여, '상품 전체 페이지'와 '북마크 페이지' 위의 공통된 카테고리를 layout.js 파일로 구현했다.
- 이렇게 한 이유는 bookmark/page.js 와 product/list/page.js 가 공통으로 받고있는 카테고리 컴포넌트를 폴더링을 통해 간단하게 구현하기 위해서였다.
app
├─ (DropDowns)
│ ├─ layout.js // bookmark와 product를 children으로 렌더링하는 파일 (여기에 카테고리 컴포넌트를 만들었다.)
│ ├─ bookmark
│ │ └─ page.js // 북마크 페이지
│ ├─ product
│ │ └─ list
│ │ └─ page.js // 상품 페이지
.
.
.
공통된 컴포넌트인 카테고리를 layout.js에 구현한 모습
문제 : layout.js에서 page.js로 props를 내려 보내는 방법
문제는 layout.js에서 특정 카테고리 버튼을 클릭하면, '상품 전체 페이지'와 '북마크 페이지'에서 데이터 필터링 로직이 구현되어야 하는데, layout.js에서 각각의 페이지인 {children}에 해당하는 page.js로 props를 내려줘야 한다는 것이다.
- 다시 말해 내가 구현하고 싶었던 것은 layout.js에 있는 카테고리를 클릭하면 (예: '브랜드' 카테고리를 클릭하면) layout.js의 children인 상품 페이지와 북마크 페이지에 해당되는 컴포넌트에서 props를 내려받아서 해당 카테고리에 속하는 아이템만 보여주는 로직이었다.
- 당연히 부모, 자식 컴포넌트로 이해했기 때문에 자식 컴포넌트로 props를 내려주면 (여기서는 무슨 카테고리를 클릭했는 가의 상태) 간단히 문제가 해결될 것으로 생각했다.
초기 작성했던 layout.js
.
.
.
export default function Category({ children }) {
const [selectedMenu, setSelectedMenu] = useState(null);
const handleTitleClick = (menu) => {
setSelectedMenu(menu);
}
return (
<Section>
{CategoryList.map((cate) => (
.
.
. <Title
onMouseEnter={() => handleTitleHover(cate.en)}
onMouseLeave={handleTitleLeave}
className={selectedMenu === cate.en ? "selected" : ""}
>
{cate.ko}
</Title>
.
.
<Main>
{
children <!-- 여기에 상품 페이지, 북마크 페이지가 렌더링된다. -->
}
</Main>
</Section>
)
}
정말 많은 시도를 해봤지만 핵심적인 것들만 정리해보았다. 시간이 없는 분들은 '문제 해결' 챕터만 봐도 충분하다.
해결하고자 한 시도들
- 물론, 컴포넌트로 만들어 페이지 컴포넌트에 import 하는 방법과 redux를 활용한 데이터 전역 관리 등 간단하게 해결할 수는 있다.
- 하지만, Next.js의 폴더링 구조를 활용한 문제 해결 방법이 있는 지에 대해 좀 더 알아보고 싶었다.
- 문제 해결에 참고한 StackoverFlow 질문
- 특히 StackoverFlow 글을 참고하여 위의 방법으로 해결하고자 다양하게 응용된 시도를 해보았다.
- 결국, 해결되지 않았다.
stackoverFlow 글을 참고하여 해결하고자 수정한 코드 중 하나
// layout.js에서 children에 props를 내려보내주는 코드
.
.
.
{
React.cloneElement(children, {selectedMenu: selectedMenu}, null)
}
.
.
.
StackoverFlow에 질문글을 올리다
글을 간단하게 작성했지만 카테고리 컴포넌트는 제일 마지막에 구현한 만큼 고민한 시간과 과정은 길었다.
- 엄청난 고민 끝에 전 세계 코딩 선배님들께 질문을 드려보기로 했다.
- 접은 글에는 내가 쓴 글이 들어있다.
StackoverFlow 질문글
How to pass props from layout.js to page.js in Next.js 13 folder-based routing?
Next.js 13 operates on a folder-based routing system, where the folder structure itself defines the routing.
- For example, the layout.js file, located directly under the app folder, includes components and styles that are shared across multiple pages.
Here's a folder structure on my project (The parent folder containing package.json etc and unnecessary files are omitted.) :
app/
├── (DropDownPages)/
│ ├── layout.js ------ (2) \* problem (parent component)
│ ├── bookmark/
│ │ └── page.js --- (3) (children component)
│ └── product/
│ └── page.js --- (3) (children component)
│
│
├── layout.js ---- (1)
└── page.js
(1) app/layout.js
In the layout.js file --- (1) just under app folder, I have added a Header and Footer and pages within
tags.
import './globals.css'
import Header from './components/Header'
import Footer from './components/Footer'
import { Providers } from './redux/provider'
export default function RootLayout({ children }) {
return (
<html lang="ko">
<Providers>
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</Providers>
</html>
)
}
(2) app/(DropDownPages)/layout.js
And within it, I have created a fixed category section at the top of the main page using another layout.js --- (2) in (DropDownPages) folder which is a parent folder for bookmark/ and product/.
layout.js : A category component
"use client"
import styled from "styled-components";
import { useState } from "react";
const 전체 = 'all'
const 상품 = 'product'
const 카테고리 = 'category'
const 기획전 = 'exhibition'
const 브랜드 = 'brand'
.
.
.
export default function Category({ children }) {
const [selectedMenu, setSelectedMenu] = useState(null);
const handleTitleClick = (menu) => {
setSelectedMenu(menu);
}
return (
<Section>
<nav className="flex justify-center items-center mt-6 mb-6">
<MenuBox onClick={() => handleTitleClick(전체)}} >
<ImageWrapper><StyledImage src="/전체.png" /></ImageWrapper>
<Title>전체</Title>
</MenuBox>
<MenuBox onClick={() => handleTitleClick(상품)}} >
<ImageWrapper><StyledImage src="/상품.png" /></ImageWrapper>
<Title>전체</Title>
</MenuBox>
<MenuBox onClick={() => handleTitleClick(카테고리)}} >
<ImageWrapper><StyledImage src="/카테고리.png" /></ImageWrapper>
<Title>전체</Title>
</MenuBox>
<MenuBox onClick={() => handleTitleClick(기획전)}} >
<ImageWrapper><StyledImage src="/기획전.png" /></ImageWrapper>
<Title>전체</Title>
</MenuBox>
<MenuBox onClick={() => handleTitleClick(브랜드)}} >
<ImageWrapper><StyledImage src="/브랜드.png" /></ImageWrapper>
<Title>전체</Title>
</MenuBox>
</nav>
<Main>
{
children
}
</Main>
</Section>
)
}
And here's the problem I'm struggling with :
The problem arises I tried to pass the prop -selectedMenu- to children (page.js --- (3) ) when clicking on a specific category button in the layout.js file. ---(2)
So below is one of the child component in app/(DropDowns)/product/page.js
"use client";
import styled from "styled-components";
import { useState, useEffect, useRef, useCallback } from "react";
// I want to get the props -selectedMenu- from its parent -layout.js ---(2) -
export default function Product({selectedMenu}) {
console.log(selectedMenu) // undefined
While there are alternative solutions such as creating components and importing them into the page components or using Redux for global data management as usual, but I'm digging in exploring a solution that leverages the folder-based routing structure.
I have already attempted a solution mentioned in a Stack Overflow post [(link)](https://stackoverflow.com/questions/75644509/passing-props-to-nextjs-layout-component) where I tried to make a clone children with some props to pass, but it didn't work. (Maybe the way I tried could be wrong.)
// layout.js ---(2)
.
.
.
{
React.cloneElement(children, {selectedMenu: selectedMenu}, null)
}
.
.
.
<br>
> For more information, here's my code on my [Github account](https://github.com/Doyu-Lee/fe-sprint-coz-shopping/tree/seona/readme_add).
그리고 하루 뒤, 내 질문은 관리자에 의해 닫혔다.
- 알고보니 이미 나와 비슷한 고민을 한 사람이 동일한 질문 글을 올린 것이다!!!
- 내가 올린 질문 제목에도 나오지만 나는 'how to pass props'로 질문을 올리고, 검색을 했다.
- 동일한 질문 글의 내용은 'how to pass data'였다....!
- 내가 한 고민은 웬만하면 StackoverFlow에 있다! 검색을 다양한 단어를 써서 해보자는 중요한 교훈을 얻었다!
- 따라서 내가 질문 글을 올리자 하루 뒤에 관리자가 '너가 올린 질문 글과 비슷한 글이 있어서 우선은 네 질문 글을 닫았어. 여기 질문 글 링크를 참고해보고 이 질문과 다른 의미면 다시 열어줄게!' 취지의 댓글을 달았다.
- 해당 질문 글
- 나와 동일한 고민, 질문을 한 글이었고 따라서 나는 고민을 해결할 수 있었다.
📌 문제 해결 (간단....)
정리하자면 Layout을 통해 데이터를 내려보내는 것을 불가능하다.
StackoverFlow 답변에서 발췌
It's supposed to be just an "UI that is shared between multiple pages" of a segment (page.js, loading.js, etc).
Next.js Docs 발췌
아는 만큼 보이는 군요..
Passing data between a parent layout and its children is not possible. However, you can fetch the same data in a route more than once, and React will automatically dedupe the requests without affecting performance.
따라서, 간단히 기존 방식인 카테고리 컴포넌트를 생성하여 props를 내려주었다.
잊지 말자!
내가 한 고민은 웬만하면 StackoverFlow에 있으니,
검색을 다양한 단어를 써서 해보자!
'📌 TOY-PROJECT > 2305 쇼핑몰 웹앱 (북마크 기능)' 카테고리의 다른 글
redux/toolkit(RTK) / 토스트 애니메이션 자연스럽게 구현하기 (0) | 2023.05.16 |
---|---|
RTK Query (0) | 2023.05.15 |
헤더 컴포넌트의 DropDown 메뉴 만들기 (0) | 2023.05.14 |
git 전략 : 작업 브랜치 생성 (0) | 2023.05.12 |
솔로 프로젝트 시작 : 스크럼을 통한 프로젝트 계획 (0) | 2023.05.12 |