
9일차에는 멤버 탭에 연결 할 멤버 소개 페이지를 만들어 보겠습니다.
✍️ React Context 사용하기
새로운 멤버 페이지에서도 노션에 있는 데이터 베이스 데이터가 필요했습니다.
기존의 노션 데이터 베이스 데이터의 양이 꽤 많아서 여러 번 호출하는건 비효율적이라고 생각했습니다.
그래서 한 번의 호출로 받아온 데이터를 전역적으로 사용하는 방법에 대해 알아봤습니다.
그게 바로 React Context 였습니다.
React Context는 컴포넌트 트리 전체에 데이터를 전역적으로 공유할 수 있도록 도와주는 기능입니다.
이를 통해 부모-자식 관계에 의존하지 않고 데이터를 전달할 수 있어 props drilling(여러 컴포넌트를 통해 데이터를 계속 전달하는 과정)을 줄여줍니다.
작동 원리
- Context 생성 : createContext를 호출하여 Context 객체를 생성합니다. 이는 데이터를 전달하는 "통로" 역할을 합니다.
- Provider 설정 : Provider는 Context에서 데이터를 공급(supply)하는 역할을 합니다. Context 데이터를 사용하는 컴포넌트는 반드시 Provider 내부에 있어야 합니다.
- Consumer 사용 : Context 데이터를 읽는 컴포넌트는 useContext 훅을 사용하여 데이터를 가져옵니다.
app 디렉토리에 context 디렉토리를 만들고 js 파일을 생성 후 코드를 작성합니다.
"use client";
import React, { createContext, useContext, useState } from "react";
// context 생성
const MemberContext = createContext();
export function MemberProvider({ children }) {
const [memberData, setMemberData] = useState(null); // 데이터와 수정하는 함수 정의
// 공유 할 데이터를 value에 전달
// children이 context 자식으로 포함되어 children 컴포넌트들이 MemberContext에 접근 할 수 있음
return (
<MemberContext.Provider value={{ memberData, setMemberData }}>
{children}
</MemberContext.Provider>
);
}
export function useMemberContext() {
const context = useContext(MemberContext); // useContext 훅을 사용하여 저장된 데이터를 가져옴
if (!context) {
throw new Error("useMemberContext must be used within a MemberProvider");
}
return context;
}
그리고 앱 전체를 MemberProvider로 감싸줍니다.
// layout.js (root layout)
import { MemberProvider } from "../app/context/member-context";
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased bg-primary`}>
<ThemeProvider attribute="class">
<MemberProvider>
{children}
</MemberProvider>
</ThemeProvider>
</body>
</html>
);
}
서버 사이드에서 넘겨준 memberData를 기본값으로 context에 넣어 줍니다.
// layout.js (component)
'use client'
import { useMemberContext } from "../context/member-context";
export default function Layout({ children, memberData }) {
const { setMemberData } = useMemberContext();
useEffect(() => {
if(memberData){
setMemberData(memberData);
}
}, [memberData, setMemberData]);
// 생략
}
이제 멤버 페이지에서 context를 활용하여 데이터를 잘 가져오는지 확인해봅시다.
'use client'
import { useEffect, useState } from "react";
import { useMemberContext } from "../../context/member-context";
import { useParams } from "next/navigation";
export default function MemberPage() {
const { id } = useParams(); // 비동기로 풀어서 사용
const { memberData } = useMemberContext();
const [member, setMember] = useState(null);
useEffect(() => {
if (memberData && id) {
const foundMember = memberData.find(member => member.memberId === id);
setMember(foundMember);
}
}, [id, memberData]);
if (!member) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{member.memberName}님의 페이지</h1>
<p>{member.goal}</p>
<p className="text-gray-900">{member.position}</p>
<p className="text-gray-900">총 {Math.floor(member.totalTime)}시간</p>
<div className="w-40 h-40">
{member.iconUrl && (
<img src={member.iconUrl} alt={`${member.memberName}의 아이콘`} />
)}
</div>
</div>
);
}

Next.js 13이상에서는 params가 더 이상 바로 객체처럼 사용되지 않고 Promise로 반환된다는 경고가 뜹니다.
이를 해결하기 위해서 params를 직접 사용하는 대신 useParams 훅을 사용하여 비동기적으로 풀어줘야 합니다.
A param property was accessed directly with `params.id`. `params` is now a Promise and should be unwrapped with `React.use()` before accessing properties of the underlying params object. In this version of Next.js direct access to param properties is still supported to facilitate migration but in a future version you will be required to unwrap `params` with `React.use()`.
성공적으로 데이터를 가져왔다면 각 멤버 페이지마다 알맞게 출력되는 것을 확인할 수 있습니다!!!😎
⭐ 레이아웃 설정하기
새로 만든 멤버 소개 페이지도 기본적인 레이아웃을 설정해봅시다.
멤버 소개 페이지에 적용시킬 레이아웃은 member/[id] 디렉토리에 layout.js를 만들면 됩니다.
우선 멤버 소개 페이지 헤더는 기존의 헤더에서 제목과 테마 전환 버튼을 가져와 적용시켜보겠습니다.
'use client'
import Header from "../../components/member-header"
export default function MemberLayout({ children }) {
return (
<div className="bg-primary">
<Header />
<div>
{children}
</div>
</div>
);
}
import ThemeButton from "./theme-button";
export default function MemberHeader() {
return (
<header className="text-gray-600 body-font">
<div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
<a className="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" className="w-10 h-10 text-white p-2 bg-yellow-500 rounded-full" viewBox="0 0 24 24">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
</svg>
<span className="ml-3 text-xl">멤버 소개 페이지</span>
</a>
<nav className="md:ml-auto flex flex-wrap items-center text-base justify-center">
</nav>
<ThemeButton />
</div>
</header>
);
}
메인 레이아웃 부분은 https://tailblocks.cc/에서 아래의 이미지 컴포넌트 부분을 사용하겠습니다.
return (
<section className="text-gray-600 body-font">
<div className="container px-5 py-24 mx-auto flex flex-col">
<div className="lg:w-4/6 mx-auto">
<div className="flex flex-col sm:flex-row mt-10">
<div className="sm:w-1/3 text-center sm:pr-8 sm:py-8">
<div className="w-40 h-40 rounded-full inline-flex items-center justify-center">
{member.iconUrl && (
<img src={member.iconUrl} alt={`${member.memberName}의 아이콘`} />
)}
</div>
<div className="flex flex-col items-center text-center justify-center">
<h2 className="font-medium title-font mt-4 text-gray-900 text-lg">{member.memberName}</h2>
<div className="w-12 h-1 bg-yellow-500 rounded mt-2 mb-4"></div>
<p className="text-gray-900">{member.goal}</p>
<p className="text-gray-900">{member.position}</p>
<p className="text-gray-900">총 {Math.floor(member.totalTime)}시간</p>
</div>
</div>
<div className="sm:w-2/3 sm:pl-8 sm:py-8 sm:border-l border-gray-200 sm:border-t-0 border-t mt-4 pt-4 sm:mt-0 text-center sm:text-left">
<h2 className="text-xl font-semibold mt-4">소제목</h2>
<p className="text-gray-900">정보 1</p>
<h2 className="text-xl font-semibold mt-4">소제목</h2>
<p className="text-gray-900">정보 2</p>
<h2 className="text-xl font-semibold mt-4">소제목</h2>
<p className="text-gray-900">정보 3</p>
<h2 className="text-xl font-semibold mt-4">소제목</h2>
<p className="text-gray-900">정보 4</p>
</div>
</div>
</div>
</div>
</section>
);
그러면 미리보기 이미지처럼 나오는 페이지 모습을 확인 할 수 있습니다!
이제 홈페이지 모양새가 그럴싸해졌습니다.
앞으로는 팀원들과 의논 후 디테일한 부분을 수정해야겠습니다!

멤버 페이지 추가 by JoHyeonJi0408 · Pull Request #3 · JoHyeonJi0408/Study-Introduction-Website
github.com
'프로젝트 > 웹페이지' 카테고리의 다른 글
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 11일차 (1) | 2025.02.01 |
---|---|
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 10일차 (1) | 2025.01.20 |
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 8일차 (0) | 2025.01.18 |
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 7일차 (1) | 2025.01.16 |
웹 개발 1도 모르는 사람이 웹페이지 만들기 프로젝트 6일차 (1) | 2025.01.15 |