2026-03-13 react 과제

박건일·2026년 3월 13일

IBM X RED HAT AI 과정 6기 박건일

  1. Main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
    <App />
)

React 애플리케이션의 시작 파일(Entry Point)

  • React 앱을 HTML의 root에 렌더링

  1. App.jsx
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import BoardList from './components/BoardList';
import Home from './components/Home';
import NaviBar from './components/NaviBar';
import CreateBoard from './components/CreateBoard';
import AuthContextPro from './components/AuthContextPro';
import Login from "./components/Login";
import SignUp from "./components/SignUp";
import MemberList from "./components/MemberList";
import EditBoard from "./components/EditBoard";

const App = () => {
  return (
    <AuthContextPro>
      <BrowserRouter>
        <NaviBar />
        <Routes>
          <Route path='/boardList' element={<BoardList />}></Route>
          <Route path='/' element={<Home />}></Route>
          <Route path='/login' element={<Login />}></Route>
          <Route path='/join' element={<SignUp />}></Route>
          <Route path='/memberList' element={<MemberList />}></Route>
          <Route path='/board/create' element={<CreateBoard />}></Route>
          <Route path='/board/edit/:id' element={<EditBoard />}></Route>
        </Routes>
      </BrowserRouter>
    </AuthContextPro>
  );
};

export default App;

전체 페이지 라우팅 관리

  • < AuthContextPro> 전역 상태 관리
  • BrowserRouter, Router 설정
  • < NaviBar> Routes 밖에 위치하여, 상단 고정

  1. AuthContextPro.jsx
import React, { createContext, useContext,useState } from 'react';

const AuthContext=createContext();
 //- 로그인 사용자 저장
    //- 로그아웃 기능
    //- 모든 컴포넌트에서 로그인 상태 사용 가능

const AuthContextPro = ({children}) => {

    //로그인버튼 누르면 로그인한 사용자들 로컬스토리지에 저장했다(Login.jsx)
    //사용자들 가져오기
    const [currentUser, setCurrentUser]=useState(
        JSON.parse(localStorage.getItem("currentUser")) || null,
    );

    const logout=()=>{
       setCurrentUser(null);
       localStorage.removeItem("currentUser");
    };

    //로그인한 사용자 정보, 로그인 상태 (로그인/로그아웃)=> 모든 컴포넌트에서 사용 가능함
    return (
        <AuthContext.Provider value={ {currentUser,setCurrentUser,logout }} >
            {children}
        </AuthContext.Provider>
    );
};

export const useAuth=()=>useContext(AuthContext);

export default AuthContextPro;

로그인 상태를 전역으로 관리

  • createContext() 이용해 인증상태를 담는다
  • useState 값들로 localStorage확인 -> localStorage.getItem("currentUser") 저장된 유저 데이터 가져와서 객체 형태 변환
    , 저장된 값 없다면 null
  • AuthContext.Provider의 value에 데이터와 함수 넣어 전역 데이터 공유
  • logout -> setCurrentUser(null)호출하여 앱 내 상태 비움 -> localStorage 에서 데이터 삭제
  • useAuth 커스텀 훅으로 다른 컴포넌트에서 Context 사용할 수 있게 한다.

  1. Navibar.jsx
import React from 'react';
import {Link} from "react-router-dom";
import { useAuth } from './AuthContextPro';
import { useNavigate } from 'react-router-dom';

const NaviBar = () => {

    const {currentUser, logout} = useAuth();
    const navigator=useNavigate();

    //로그아웃함수..logout함수호출하고 기본경로로 페이지이동
    const logout1=()=>{
        logout();
        navigator("/");
    }

    return (
        <>
            <Link to="/"></Link>
            <Link to="/memberList">회원목록</Link>
            <Link to="/boardList">게시글목록</Link>

           {/* currentUser가 false이면 로그인, 회원가입 보이게 */}
            {!currentUser && (
                <>
                    <Link to="/login">로그인</Link>
                    <Link to="/join">회원가입</Link>
                </>
            )}
            {/* 로그인이 되어있는 상태면 userId 와 로그아웃 버튼 보이게 */}
            :{currentUser && (
                <>
                    <span>{currentUser.userId}</span>
                    <button onClick={logout1}>로그아웃</button>
                </>


            )}
        </>

    );
};

export default NaviBar;

상단 메뉴 UI 컴포넌트

  • useAuth를 호출하여 AuthContextPro에서 관리하는 데이터에 접근한다.(currentUser, logout) 가져옴

  • useNavigate : 로그아웃 후 홈을 돌아가기 위함 -> 로그아웃함수 호출 -> "/"경로 이동

  • !currentUser(비로그인 상태) : currentUser가 null 인 경우 로그인 회원가입 보임

  • currentUser : 로그인 상태면 userId, 로그아웃 버튼 보임


Home.jsx

import React from 'react';

const Home = () => {
    return (
        <div>
            Home
        </div>
    );
};

export default Home;

웹사이트 메인 페이지


  1. Login.jsx
import React from 'react';
import { useAuth } from './AuthContextPro';
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';

const Login = () => {

    const [userId, setUserId]=useState('');
    const [password, setPassword]=useState('');
    const navigator=useNavigate();

    //useAuth함수호출
    const {setCurrentUser} = useAuth();


    const onSubmit2=(e)=>{
        e.preventDefault();
        
        let users=JSON.parse(localStorage.getItem("users")) || [] ;

        const loginUser= users.find((user)=> user.userId === userId && user.password === password);

        //로그인 성공 시 사용자 정보 로컬스토리지에 저장
        if(loginUser){
            setCurrentUser(loginUser);

            localStorage.setItem("currentUser", JSON.stringify(loginUser))

            setUserId("");
            setPassword("");

            navigator('/boardList');
        }
        else{
            alert('아이디 또는 비밀번호 오류');
        }
    }

    return (
         <div>
            <form onSubmit={onSubmit2}>
                <h1>로그인</h1>
                아이디 :<input type='text' value={userId} onChange={(e)=>setUserId(e.target.value)}></input>
                비밀번호 : <input type='password' value={password} onChange={(e)=>setPassword(e.target.value)}></input>
            
                <button type='submit'>로그인</button>
            </form>
    
        </div>
    );
};

export default Login;

저장된 회원 정보와 입력값 비교하여 로그인 처리

  • useState통해 아이디, 비번 관리 -> onChange 이벤트 통해 userId와 password 업데이트
  • useNavigate(), useAuth() 함수 호출
  • form 태그 안에서 submit 누르면 이벤트 통해 새로고침 동작을 방지한다.
  • localStorage에 저장된 users를 불러와 가입안되어 있으면 빈 배열로 가져온다.
  • users.find를 통해 아이디와 비번 일치 여부 확인하여, 배열을 순회하며 찾는다.
    -> if 문을 통해 로그인 성공 시 사용자 정보를 localStorage에 저장한다.
    -> else : 로그인 실패 시 alert 처리

  1. SignUp.jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const SignUp = () => {

    const [userId, setUserId]=useState('');
    const [password, setPassword]=useState('');
    const navigator=useNavigate();

    const onSubmit1=(e)=>{
        e.preventDefault();

        const user={userId, password}; //입력한 값을 객체로 담음(로컬스토리지에 저장하려고)

        let users=JSON.parse(localStorage.getItem("users")) || [] ;
        users.push(user); //처음에는 빈배열이므로 빈배열에 푸쉬(삽입)

        localStorage.setItem("users",JSON.stringify(users));
        //빈배열에 추가한 userId, password를 로컬스토리지에 저장

        setUserId("");
        setPassword("");

        navigator('/login');
    }

    return (
        <div>
            <form onSubmit={onSubmit1}>
                <h1>회원 가입</h1>
                아이디 :<input type='text' value={userId} onChange={(e)=>setUserId(e.target.value)}></input>
                비밀번호 : <input type='password' value={password} onChange={(e)=>setPassword(e.target.value)}></input>
            
                <button type='submit'>회원가입</button>
            </form>
    
        </div>
    );
};

export default SignUp;

LocalStorage에 저장하여 회원가입

  • form 안에 아이디, 비번 입력하고, useState() 통해 상태관리-> 이벤트 발생 시 페이지 새로고침 방지 -> const user={userId, password} 입력한 값을 user 객체로 담음
  • getItem 이전 가입했던 user 목록 가져옴.(있으면 객체 배열 전환하여 가져오고, 없으면 빈 배열 생성) -> push통해 빈 배열에 삽입
  • localStorage에 JSON 문자열로 직렬화하여 저장
  • 가입 완료 후 상태 값 비우고("") 로그인 페이지('/login') 이동

  1. MemberList.jsx
import React, { useEffect, useState } from 'react';

const MemberList = () => {
    //1. 로컬스토리지에서 회원가입했을 때 저장했던 회원정보들 다 가져오기
    const users= JSON.parse(localStorage.getItem("users"));

    //관리자로 로그인하면 회원목록 보이고, 관리자가 아니면 안보이게 할거임
    //2. 로그인한 사용자 상태 초기화
    const [currentUser, setCurrentUser]= useState(null); //currentUser에 id, password속성

    //3. 로컬스토리지에서 로그인했을 때 저장한 사용자 정보 가져온다.
    useEffect(()=>{
        const storedUser=JSON.parse(localStorage.getItem("currentUser"));
        setCurrentUser(storedUser);

    },[]);
	

    return (
        <div>
            <h1>회원 목록</h1>
             {/* 관리자로 로그인하면 회원목록 보이고 */}
            {currentUser && currentUser.userId==="admin" && 
            currentUser.password==="admin" ? (
                <ul>
                    {users.length > 0 ? (
                        users.map((user, index) => <li key={index}>{user.userId}</li>)
                    ) : (
                        <li>회원 없다</li>
                    )
                }
                </ul>

            )  :( //관리자가 아니면 회원목록 안보이게 할거임
                <div>
                    <div>회원목록은 관리자만 볼 수 있습니다</div>
                </div>
            )       
        }     
        </div>
    ); 
};

export default MemberList;

로그인된 회원들 목록

  • 중첩 조건부 렌더링 이용하여 {currentUser && ... ? (A) : (B)} 관리자로 로그인 시 ul태그 이용하여 목록 출력하고, 관리자 아니면 div 태그 안 문구 처리 -> ul태그 안의 관리자라도 회원 데이터 가 없을 시를 대비하여 있으면 map 함수 실행, 없으면 회원 없다 처리 -> 리스트의 각 항목을 회원 아이디로 출력
  • 로컬스토리지에서 회원가입했을 때 저장했던 회원정보들 다 가져오기
  • useEffect 통하여 currentUser 가져와 state 에 저장

  1. BoardList.jsx
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';

const BoardList = () => {

    const [posts, setPosts]=useState([]);
    const [currentUser, setCurrentUser]=useState(null);

    useEffect(()=>{
        //posts 가져오고, currentUser도 가져온다..
        const storedPosts=JSON.parse(localStorage.getItem("posts")) || [];
        setPosts(storedPosts);

        const storedUser=JSON.parse(localStorage.getItem("currentUser")) || [];
        setCurrentUser(storedUser);

    },[])


    const handleDelete=(id)=>{
        const updated=posts.filter((post) => post.id !== id);
        setPosts(updated);

        //삭제 후 남겨진 데이터만 로컬스토리지에 저장 ..posts
        localStorage.setItem("posts", JSON.stringify(updated));

    }

    return (
      <div>
            <h1>게시글 목록</h1>
            <Link to="/board/create">글쓰기</Link>
      
        {posts.length > 0 ? (
            posts.map((post)=>(
                <div key={post.id}>
                    <div>{post.title}</div>
                
                {currentUser && currentUser.userId === post.writerId && (
                    <div>
                        <Link to={`/board/edit/${post.id}`}>수정</Link>

                  
                        <button onClick={()=> handleDelete(post.id)}>삭제</button>
                    </div>
                )}
            </div>
            ))
        ) : (
            <div>게시물 없다</div>
        )}
        </div>
    )
}
export default BoardList;

게시글 목록

  • currentUser 로그인이 되있고 로그인한 아이디와 게시글 아이디가 같을 때 수정, 삭제 화면이 렌더링된다.
  • 전체 게시글 posts, currentUser 로그인한 사용자도 가져온다.([ ])의존성 배열을 통해 한 번만 데이터를 불러온다.
  • posts.filter()를 사용하여 id 와 일치하지 않는 게시글 모아 새로운 배열(updated) 만든다. -> setPosets 통해 화면 즉시 업데이트 -> setItem 통해 남겨진 데이터 저장

  1. CreateBoard.jsx
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';

const CreateBoard = () => {
    //1. 제목 상태 초기화
    const [title, setTitle]=useState('');
    //2. 내용 상태 초기화
    const [content, setContent]=useState('');

    //3. 네비게이트(코드내에서 페이지 이동)
    const navigator=useNavigate();
    

    //login.jsx 에서 저장한 로그인한 사용자 정보 가져온다.
    const currentUser=JSON.parse(localStorage.getItem("currentUser"));

    //currentUser 가 false면 alert -> 로그인 필요해
    //                      /login
    useEffect(()=>{
        if(!currentUser){
            alert("로그인 필요하다");
            navigator('/login');
        }
    },[]);

    //4. onSubmit1 구현 -> 새로고침 방지
    const onSubmit1=(e)=>{
        e.preventDefault();

        //const post={id:Date.now(), title, content}

    //4.1 로컬스토리지에서 내가 쓴 제목과 내용을 읽어와서 posts에 저장 / 근데 만약에 내가 쓴 제목, 내용이 없으면 []
    //문자열 -> 객체로 변환
    let posts=JSON.parse(localStorage.getItem("posts")) || [];

    const newPost={
        id:Date.now(),
        title,
        content,
        writerId:currentUser.userId, //현재 로그인한 사용자 아이디 추가해서 배열에 삽입
    }
    //posts 에다 newPost추가
    posts.push(newPost);

     //4.2 로컬스토리지에 내가 쓴 제목과 내용을 저장 (키 이름: posts) 
     //어차피 getItem으로 처음에 꺼낼 데이터가 없기때문에 posts=[]
    localStorage.setItem("posts", JSON.stringify(posts));

    setTitle("");
    setContent("");

    navigator('/boardList');

    };

   
    return (
        <div>
            <h1>게시글 작성</h1>
            <form onSubmit={onSubmit1}>
                제목 : <input type='text' value={title} onChange={(e)=>setTitle(e.target.value)} />
                내용 : <textarea value={content} onChange={(e)=>setContent(e.target.value)} />
                
                <button type='submit'>작성 완료</button>
            </form>
        </div>
    );
};

export default CreateBoard;

게시글 작성

  • useState통해 제목, 내용 상태 초기화 -> 로그인한 사용자들 불러옴 -> useEffect가 가장 먼저 실행되며 currentUser 확인하여 값이 없으면 접근 거부
  • onSubmit1() : 새로고침 방지하고, localStorage에서 쓴 제목과 내용 불러와서 Post에 저장
  • newPost 에 id 값 현재 시간, UserId 추가해서 push 통해 배열에 삽입
  • 기존에 저장된 글들을 잃어버리지 않기 위해 새 글 추가하여 (stringify)문자열화 해서 저장

EditBoard.jsx

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';

const EditBoard = () => {

    //board/edit/${post.id} 해당하는 id가져옴
    const {id} =useParams();

    const [post, setPost]=useState({title:"", content:""});
    const navigator=useNavigate();

    useEffect(()=>{

        const posts=JSON.parse(localStorage.getItem("posts")) || [];

        const currentPost=posts.find((p)=> parseInt(id) === p.id);

        if(currentPost){
            setPost(currentPost);
        }

    },[id]);

    const onSubmit1=(e)=>{
        e.preventDefault();

        let posts=JSON.parse(localStorage.getItem("posts")) || [];

        posts=posts.map((p) => p.id === parseInt(id) ? {...post, writerId: p.writerId} : p);

        //수정된 값 다시 로컬스토리지에 저장
        localStorage.setItem("posts", JSON.stringify(posts));

        //boardList로 이동
        navigator('/boardList');

    }
   

    return (
        <div>
            <h1>게시글 수정</h1>
            <form onSubmit={onSubmit1}>
                <input value={post.title} onChange={(e)=>setPost({...post, title:e.target.value})}></input>
                <textarea value={post.content} onChange={(e)=>setPost({...post, content:e.target.value})}></textarea>

                <button>수정</button>

            </form>

            
        </div>
    );
};

export default EditBoard;

게시글 수정 페이지

  • const {id} =useParams(); : id 값을 가져옴
  • posts.find((p)를 통해 전체 게시글 중 id와 일치 하는 객체 찾아 수정된 값 저장
  • ...post를 사용하여 id, writerId 등은 그대로 복사하고, 변경된 제목과, 내용 덮어씌운다.
  • map 을 돌며 게시글 전체 검사하고, ID가 일치하면 새로운 post객체로 교체하고, 나머지는 그대로 둔다.
profile
ibm-redhat ai 과정

0개의 댓글