json 데이터를 context API 와 Axios로 세팅하고 공급 하기 위한 선행 작업
우리가 흔히 알고 있는 SNS 포스팅을 만들것입니다.
포스팅은 JSON 데이터를 사용할 것입니다.
이 데이터를 프로젝트에서 사용하기 위해서 아래의 몇가지 작업을 선행해야 합니다.
1. 데이터타입 포스트 인터페이스에 세팅하기
2. 포스트/로딩/에러 타입 세팅하고 포스트콘텍스트타입 인터페이스에 세팅하기
3. 데이터가 정의되지 않았을때 접근하는 경우 대비하고 그것을 포스트콘텍스트 변수에 세팅하기
4. 리턴받은 결과 포스트프로바이더 변수에 세팅하기
- 포스트/로딩/에러 스테이트 빌딩하기
- 데이터 렌더링하는 유즈이펙트 빌딩하기
-- 트라이로 데이터 가져오기
-- 캐치로 예외처리 하기
-- 파이널리로 무한로딩 막기
- 유저이펙트로 세팅된 스테이트 밸류 변수에 세팅하기
- 세팅한 변수를 외부에서 공급받을 수 있는 상태로 세팅해서 리턴하기
즉, 각 컴포넌트는 포스트프로바이더로 JSON 데이터를 공급받을 것입니다.
포스트데이터/포스트/로딩/에러 타입 세팅하고 인터페이스로 세팅하기
https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/text/engtext.json
사용할 data를 체크 합니다.
https://lshjju.tistory.com/289
Axios
Axios란 무엇인가요?Axios는 웹 브라우저와 Node.js 환경에서 HTTP 통신을 쉽게 할 수 있도록 도와주는 라이브러리입니다. 즉, 웹사이트나 애플리케이션이 다른 서버와 데이터를 주고받을 때 사용하는
lshjju.tistory.com
액시오스 설치 합니다.
'use client';
import React, {
createContext,
useContext,
useState,
useEffect,
ReactNode,
} from 'react';
import axios from 'axios';
context/PostContext.tsx
메인데이터 바인딩은 단순하기 때문에 콘텍스트에이피아이로 공급하도록 하겠습니다.
루트에 context 디렉토리를 생성하고 내부에 파일을 빌딩 합니다.
Imports 및 초기 설정 합니다.
앞으로 진행될 세팅은 콘텍스트에이피아이를 사용하기 위한 준비 입니다.
'use client'; // Next.js에서 클라이언트 컴포넌트임을 나타내는 지시어입니다.
import React, {
createContext, // React Context API를 생성하기 위한 함수입니다.
useContext, // 생성된 Context를 사용하기 위한 훅입니다.
useState, // 상태 관리를 위한 React 훅입니다.
useEffect, // 부수 효과를 처리하기 위한 React 훅입니다.
ReactNode, // 타입스크립트에서 React 자식 요소의 타입을 정의합니다.
} from 'react';
import axios from 'axios'; // HTTP 요청을 보내기 위한 라이브러리입니다.
interface Post {
id: number;
title: string;
summary: string;
content: string;
date: string;
likeIt: string;
}
interface PostContextType {
posts: Post[];
loading: boolean;
error: string | null;
}
임포트 아래에 데이터 타입을 정의 합니다.
액시오스로 가져올 포스트 데이터타입을 포스트 인터페이스로 세팅 합니다.
데이트 타입은 스트링으로 세팅했습니다.
왜냐하면 데이트 데이터는 프로그랭밍 언어별로 나름의 포맷이 있으니 넘버는 위험 합니다.
라이크잇은 넘버로 세팅해야 할것 같지만 다시 생각할 필요가 있습니다.
라이크잇은 넘버뿐 아니라 상태나 정보를 표현 할 수도 있습니다.
혹은 우리가 유튜브에서 보는것처럼 1.8k 이런 식으로 표현될 수도 있습니다.
그래서 스트링이 좀 더 안전 합니다.
일반적으로 반론의 여지가 없는 경우만 넘버로 세팅 합니다.
데이터를 좀 더 안전하게 설계하겠다는 의지로 받아들이기 바랍니다.
아래 세가지 타입을 포스트콘텍스트타입 인터페이스로 세팅 합니다.
포스트가 어레이임을 보증하고 포스트스 데이터로 데퍼니션 합니다.
데이터로딩/에러 타입을 지정 합니다.
이 로딩과 에러는 아래에서 액시오스 로드할 때 예외처리용으로 사용할 것입니다.
상세설명은 나중에 하겠습니다.
// 1. 데이터 타입 정의 (Main 컴포넌트의 Post 인터페이스와 동일)
interface Post {
id: number; // 게시물의 고유 식별자입니다.
title: string; // 게시물의 제목입니다.
summary: string; // 게시물의 요약 내용입니다.
content: string; // 게시물의 전체 내용입니다.
date: string; // 게시물의 작성 날짜입니다.
likeIt: string; // 게시물의 좋아요 정보입니다.
}
// 2. Context가 제공할 값의 타입 정의
interface PostContextType {
posts: Post[]; // 게시물 목록을 저장하는 배열입니다.
loading: boolean; // 데이터 로딩 상태를 나타냅니다.
error: string | null; // 오류 발생 시 오류 메시지를 저장합니다.
}
Completion
'use client';
import React, {
createContext,
useContext,
useState,
useEffect,
ReactNode,
} from 'react';
import axios from 'axios';
interface Post {
id: number;
title: string;
summary: string;
content: string;
date: string;
likeIt: string;
}
interface PostContextType {
posts: Post[];
loading: boolean;
error: string | null;
}
포스트콘텍스트와 포스트프로바이더 세팅하기

const PostContext = createContext<PostContextType | undefined>(undefined);
넥스트 머리는 지금 걱정으로 가득 차 있습니다.
"액시오스로 데이터를 가져온다는데 믿어도 되나?"
"가져오긴 했는데 이상한 애들이면 어떡하지?"
"데이터가 아직 공급되지 않은 상태에서 누군가 데이터에 접근한다면 어떻하지?"
걱정인형의 스트레스를 해결해 줍시다.
인터페이스 아래에 언디파인드로 초기화 된 Context를 생성 합니다.
그러기 위해 포스트콘텍스트 변수를 세팅 합니다.
이것은 이 변수가 어떤 상태의 밸류를 담을 수 있는지를 정의 합니다.
또한, JSON 데이터는 이 변수에 세팅 됩니다.
<PostContextType | undefined>
예상할 수 있는 상태는 위 두가지 입니다.
즉, 만약 누군가 데이터가 비어있는 상태에서 데이터에 접근한다면 언디파인드를 발생시킵니다.
왜냐하면 포스트콘텍스트 상태는 데이터가 있을수도 없을수도 있기 때문입니다.
이런 부분도 고려할 필요가 있습니다.
넥스트는 이런 일을 잘 합니다.
파라미터는 언디파인드로 세팅 합니다.
// 3. Context 생성 (기본값 설정)
// undefined로 초기화된 Context를 생성합니다. 이는 Provider 없이 사용될 경우 에러를 발생시키기 위함입니다.
const PostContext = createContext<PostContextType | undefined>(undefined);
export const PostProvider = ({ children }: { children: ReactNode }) => {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchPosts() {
try {
const res = await axios.get(
'https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/text/engtext.json'
);
setPosts(res.data);
} catch (err) {
console.error('앗! 포스트를 가져오는 데 문제가 생겼어:', err);
setError('데이터를 불러오는 데 실패했습니다.');
} finally {
setLoading(false);
}
}
fetchPosts();
}, []);
const value = { posts, loading, error };
return <PostContext.Provider value={value}>{children}</PostContext.Provider>;
};
포스트콘텍스트 아래에 포스트프로바이더 변수 세팅 합니다.
변수에 세팅할 펑션 빌딩 합니다.
펑션 파라미터 세팅합니다.
칠드런은 컴포넌트 내부 모든 요소를 의미합니다.
또한, 칠드런 타입은 리액트노드라는 것도 분명히 선언 합니다.
리액트노드란 리액트에서 렌더링 할 수 있는 모든 것을 뜻합니다.
즉, 이 데이터는 리액트노드타입이고 컴포넌트 모든 요소를 파라미터로 사용할 것입니다.
사용할 포스트/로딩/에러 스테이트 세팅합니다.
유즈이펙트 빌딩 합니다.
일반적으로 리액트 프로젝트에서 액시오스는 겟-덴-캐치 3스텝의 타입으로 운영 합니다.
이 프로젝트에서는 유즈이펙트 빌딩하고 그 내부에서 엑시오스를 사용하겠습니다.
트라이캐치로 액시오스 데이터 로드하고 안전점검도 한번 해줍니다.
예외처리까지 끝났다면 유즈이펙트를 끝내도록 파이널리는 폴스로 설정 합니다.
이렇게 하면 데이터로딩 무한반복을 막을 수 있습니다.
유즈이펙트는 메모리관리를 위해 컴포넌트 마운트 할 때 딱 한번만 사용하게 하면 깔끔하겠네요.
그러므로 유즈이펙트 세가지 사용 패턴 중 빈어레이 세팅 합니다.
사용 패턴이 무엇인지 기억이 나지 않는다면 아래 유즈이펙트 관련 포스팅을 참고 합니다.
빌딩된 스테이트3개를 밸류 변수에 세팅 합니다.
포스트콘텍스트 변수를 자식컴포넌트에 프로바이더로 공급하고 그 결과를 리턴 합니다.
밸류 변수를 프로바이더로 단단하게 말아 줍니다.
이렇게 하면 프로바이더가 이 변수의 데이터를 안전하게 공급할 수 있습니다.
이렇게 해서 이 변수는 이 시스템을 품게 되었습니다.
이제 layout.tsx 파일에 포스트프로바이더 변수를 공급하겠습니다.
import { PostProvider } from './context/PostContext';
파일에서 위 코드로 임포트하면 됩니다.
이제 이 변수는 데이터를 안전하게 배송하는 대활약을 하게 될것입니다.
결론
포스트콘텍스트 변수는 JSON 데이터를 담는 역할입니다.
포스트프로바이더 변수는 다른 파일에 포스트콘텍스트 변수를 안전하게 공급하는 역할입니다.
// 4. Provider 컴포넌트 정의
// 이 컴포넌트는 하위 컴포넌트들에게 포스트 데이터를 제공합니다.
export const PostProvider = ({ children }: { children: ReactNode }) => {
// 상태 관리를 위한 useState 훅 사용
const [posts, setPosts] = useState<Post[]>([]); // 게시물 목록 상태
const [loading, setLoading] = useState<boolean>(true); // 로딩 상태
const [error, setError] = useState<string | null>(null); // 오류 상태
// 컴포넌트가 마운트될 때 데이터를 가져오는 효과
useEffect(() => {
// 비동기 함수로 포스트 데이터를 가져옵니다.
async function fetchPosts() {
try {
// axios를 사용하여 GitHub에서 JSON 데이터를 가져옵니다.
const res = await axios.get(
'https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/text/engtext.json'
);
setPosts(res.data); // 가져온 데이터로 posts 상태를 업데이트합니다.
} catch (err) {
// 오류 발생 시 콘솔에 오류를 기록하고 오류 상태를 설정합니다.
console.error('앗! 포스트를 가져오는 데 문제가 생겼어:', err);
setError('데이터를 불러오는 데 실패했습니다.');
} finally {
// 성공하든 실패하든 로딩 상태를 false로 설정합니다.
setLoading(false);
}
}
fetchPosts(); // 함수 호출하여 데이터 가져오기 시작
// 의존성 배열이 비어 있으므로 컴포넌트 마운트 시 한 번만 실행됩니다.
}, []);
// Context Provider에 제공할 값을 객체로 구성합니다.
const value = { posts, loading, error };
// Provider 컴포넌트로 자식 요소들을 감싸고 값을 제공합니다.
return <PostContext.Provider value={value}>{children}</PostContext.Provider>;
};
export const usePosts = () => {
const context = useContext(PostContext);
if (context === undefined) {
throw new Error('usePosts는 PostProvider 내부에서 사용해야 합니다.');
}
return context;
};
위에서 JSON 데이터를 포스트프로바이더 변수에 안전하게 세팅했습니다.
이제 해야 할 일은 이 변수를 넥스트프로젝트에서 무선으로 사용가능하게 개발하는 것입니다.
왜냐하면 데이터를 사용할 수 있게 준비하는 것과 이것을 무선으로 글로벌스코프로 공유하는 것은 다른 일이기 때문입니다.
그러기 위해 콘텍스트에이피아이를 사용하는 코드를 추가하겠습니다.
추가할 코드 목적은 아래와 같습니다.
app/page.tsx 에서 사용할 최종 데이터를 준비 합니다.
즉 유즈포스트스는 page.tsx 에서
import { usePosts } from './context/PostContext';
위 코드로 임포트 해서 활용될 것입니다.
가장 아래에 Custom Hook을 빌딩 합니다.
즉, 유즈콘텍스트 훅을 만들어 보겠습니다.
유즈포스트스 변수 세팅하고 익스포트 합니다.
펑션 빌딩 합니다.
포스트콘텍스트 변수를 파라미터로 사용해서 유즈콘텍스트로 초기화 하고 이것을 콘텍스트 변수에 세팅합니다.
이렇게 해서 포스트콘텍스트 변수는 무선연결 기능을 장착하였습니다.
곰곰히 생각해보니 이 무선 기능은 포스트프로바이더 변수 내부에서만 사용해야 합니다.
그러므로 스코프를 벗어나는 접근을 시도하는 경우를 대비한 예외처리를 합니다.
결론
포스트콘텍스트 변수는 JSON 데이터를 담는 역할입니다.
포스트프로바이더 변수는 다른 파일에 포스트콘텍스트 변수를 안전하게 공급하는 역할입니다.
유즈포스트스 변수는 글로벌스코프로 데이터를 무선으로 공급하는 역할입니다.
// 5. 데이터를 쉽게 사용할 수 있게 해주는 Custom Hook
// 이 훅을 사용하면 하위 컴포넌트에서 Context 값을 쉽게 가져올 수 있습니다.
export const usePosts = () => {
const context = useContext(PostContext); // Context에서 값을 가져옵니다.
// Provider 없이 훅이 사용될 경우 오류를 발생시킵니다.
if (context === undefined) {
throw new Error('usePosts는 PostProvider 내부에서 사용해야 합니다.');
}
return context; // Context 값을 반환합니다.
};
Completion
'use client';
import React, {
createContext,
useContext,
useState,
useEffect,
ReactNode,
} from 'react';
import axios from 'axios';
interface Post {
id: number;
title: string;
summary: string;
content: string;
date: string;
likeIt: string;
}
interface PostContextType {
posts: Post[];
loading: boolean;
error: string | null;
}
const PostContext = createContext<PostContextType | undefined>(undefined);
export const PostProvider = ({ children }: { children: ReactNode }) => {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchPosts() {
try {
const res = await axios.get(
'https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/text/engtext.json'
);
setPosts(res.data);
} catch (err) {
console.error('앗! 포스트를 가져오는 데 문제가 생겼어:', err);
setError('데이터를 불러오는 데 실패했습니다.');
} finally {
setLoading(false);
}
}
fetchPosts();
}, []);
const value = { posts, loading, error };
return <PostContext.Provider value={value}>{children}</PostContext.Provider>;
};
export const usePosts = () => {
const context = useContext(PostContext);
if (context === undefined) {
throw new Error('usePosts는 PostProvider 내부에서 사용해야 합니다.');
}
return context;
};
Comment ver
'use client'; // Next.js에서 클라이언트 컴포넌트임을 나타내는 지시어입니다.
import React, {
createContext, // React Context API를 생성하기 위한 함수입니다.
useContext, // 생성된 Context를 사용하기 위한 훅입니다.
useState, // 상태 관리를 위한 React 훅입니다.
useEffect, // 부수 효과를 처리하기 위한 React 훅입니다.
ReactNode, // 타입스크립트에서 React 자식 요소의 타입을 정의합니다.
} from 'react';
import axios from 'axios'; // HTTP 요청을 보내기 위한 라이브러리입니다.
// 1. 데이터 타입 정의 (Main 컴포넌트의 Post 인터페이스와 동일)
interface Post {
id: number; // 게시물의 고유 식별자입니다.
title: string; // 게시물의 제목입니다.
summary: string; // 게시물의 요약 내용입니다.
content: string; // 게시물의 전체 내용입니다.
date: string; // 게시물의 작성 날짜입니다.
likeIt: string; // 게시물의 좋아요 정보입니다.
}
// 2. Context가 제공할 값의 타입 정의
interface PostContextType {
posts: Post[]; // 게시물 목록을 저장하는 배열입니다.
loading: boolean; // 데이터 로딩 상태를 나타냅니다.
error: string | null; // 오류 발생 시 오류 메시지를 저장합니다.
}
// 3. Context 생성 (기본값 설정)
// undefined로 초기화된 Context를 생성합니다. 이는 Provider 없이 사용될 경우 에러를 발생시키기 위함입니다.
const PostContext = createContext<PostContextType | undefined>(undefined);
// 4. Provider 컴포넌트 정의
// 이 컴포넌트는 하위 컴포넌트들에게 포스트 데이터를 제공합니다.
export const PostProvider = ({ children }: { children: ReactNode }) => {
// 상태 관리를 위한 useState 훅 사용
const [posts, setPosts] = useState<Post[]>([]); // 게시물 목록 상태
const [loading, setLoading] = useState<boolean>(true); // 로딩 상태
const [error, setError] = useState<string | null>(null); // 오류 상태
// 컴포넌트가 마운트될 때 데이터를 가져오는 효과
useEffect(() => {
// 비동기 함수로 포스트 데이터를 가져옵니다.
async function fetchPosts() {
try {
// axios를 사용하여 GitHub에서 JSON 데이터를 가져옵니다.
const res = await axios.get(
'https://raw.githubusercontent.com/lshjju/cdn/refs/heads/main/text/engtext.json'
);
setPosts(res.data); // 가져온 데이터로 posts 상태를 업데이트합니다.
} catch (err) {
// 오류 발생 시 콘솔에 오류를 기록하고 오류 상태를 설정합니다.
console.error('앗! 포스트를 가져오는 데 문제가 생겼어:', err);
setError('데이터를 불러오는 데 실패했습니다.');
} finally {
// 성공하든 실패하든 로딩 상태를 false로 설정합니다.
setLoading(false);
}
}
fetchPosts(); // 함수 호출하여 데이터 가져오기 시작
// 의존성 배열이 비어 있으므로 컴포넌트 마운트 시 한 번만 실행됩니다.
}, []);
// Context Provider에 제공할 값을 객체로 구성합니다.
const value = { posts, loading, error };
// Provider 컴포넌트로 자식 요소들을 감싸고 값을 제공합니다.
return <PostContext.Provider value={value}>{children}</PostContext.Provider>;
};
// 5. 데이터를 쉽게 사용할 수 있게 해주는 Custom Hook
// 이 훅을 사용하면 하위 컴포넌트에서 Context 값을 쉽게 가져올 수 있습니다.
export const usePosts = () => {
const context = useContext(PostContext); // Context에서 값을 가져옵니다.
// Provider 없이 훅이 사용될 경우 오류를 발생시킵니다.
if (context === undefined) {
throw new Error('usePosts는 PostProvider 내부에서 사용해야 합니다.');
}
return context; // Context 값을 반환합니다.
};
https://lshjju.tistory.com/147
React.js - Context API - 리액트 콘텍스트 에이피아이
React.js - Context API - 리액트 콘텍스트 에이피아이 1. Context API는 왜 필요한가요? (Props Drilling 문제)React에서 컴포넌트 간에 데이터를 전달할 때 기본적으로 Props를 사용합니다. 부모 컴포넌트에서 자
lshjju.tistory.com
https://lshjju.tistory.com/365
Next.js/React/Typescript - Context API file naming rule
개발할 때 파일 이름과 관련된 규칙이 있습니다. 이 룰에 대해 파악하고 있으면 좋습니다.하지만 이는 필수적인 문법 규칙이라기보다는, **개발의 효율성과 협업을 위해 많은 개발자가 따르는
lshjju.tistory.com
https://lshjju.tistory.com/177
React.js useEffect
useEffect란 무엇인가요?useEffect는 React 함수형 컴포넌트에서 사이드 이펙트(부수 효과)를 처리하기 위한 Hook입니다. 사이드 이펙트란 컴포넌트의 렌더링 이외에 발생하는 모든 작업을 말해요. 예를
lshjju.tistory.com
컴포넌트에 json 데이터를 공급 합니다.
import { PostProvider } from './context/PostContext';
layout.tsx
이 파일은 프로젝트 전체 설계와 데이터공급을 담당합니다.
위에서 언급한대로 데이터를 공급하겠습니다.
공급을 위해 포스트프로바이더 임포트 합니다.
<body className={inter.className}>
<Nav />
<PostProvider>{children}</PostProvider>
<Footer />
</body>
칠드런은 컴포넌트 전체를 바라봅니다.
칠드런을 포스트프로바이더로 곱게 말아 줍니다.
이제부터 컴포넌트는 데이터를 공급받을 수 있습니다.
Completion
import './globals.css';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import Link from 'next/link';
import dataTexts from './data/dataText';
import './globals.css';
import { PostProvider } from './context/PostContext';
interface DataText {
main: string;
subCaps1: string;
sub1: string;
subCaps2: string;
sub2: string;
subCaps3: string;
sub3: string;
footer: string;
}
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<Nav />
<PostProvider>{children}</PostProvider>
<Footer />
</body>
</html>
);
}
function Nav() {
return (
<nav className="navbar navbar-expand-lg bg-body-tertiary m-3">
<div className="container-fluid">
<Link href="/" className="navbar-brand">
<img src="./next.svg" width="50" />
</Link>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNavAltMarkup">
<div className="navbar-nav">
<Link href={`/${dataTexts[0].sub1}`} className="nav-link">
{dataTexts[0].subCaps1}
</Link>
<Link href={`/${dataTexts[0].sub2}`} className="nav-link">
{dataTexts[0].subCaps2}
</Link>
<Link href={`/${dataTexts[0].sub3}`} className="nav-link">
{dataTexts[0].subCaps3}
</Link>
</div>
</div>
</div>
</nav>
);
}
function Footer() {
return (
<div className="card m-3">
<div className="card-header">Image Copyright</div>
<div className="card-body">
<h5 className="card-title">{dataTexts[0].footer}</h5>
<p className="card-text">
With supporting text below as a natural lead-in to additional content.
</p>
<a
href="https://unsplash.com/"
className="btn btn-outline-secondary fs-6"
>
Go {dataTexts[0].footer}
</a>
</div>
</div>
);
}
layout.tsx
