새로고침시에도 전역 state값 유지

DONNIE·2023년 1월 2일
0

React

목록 보기
11/26

⚙ 로직

  1. App.js 단에서 token 확인
  2. token값 없을시 login page로 이동,passport js를 통해 token 발급 => 쿠키에 저장
  3. App.js단에서 쿠키값을 읽어오고, global state로 관리하여 전 페이지와 공유
  4. route로 해당 페이지에 접근시 state값에 저장된 토큰을 이용하여 API에 접근

❗ 문제

  • route로 이동하는 페이지에서 새로고침시에, state값 초기화로 token 값 읽어오지못함

💡 해결법

  • 초기화 함수를 통해서 새로고침이나 리다이렉션되는 상황에도 상태값이 유지될 수있도록 한다.

참고 블로그 1
참고 블로그 2

🔎 소스코드

  • App.js (./src/App.js)
import './App.css';
import React, {createContext, useEffect,useLayoutEffect,useState} from 'react';
import {BrowserRouter, Routes, Route } from 'react-router-dom';
import { Cookies } from 'react-cookie';
// components
import Main from './views/Main'
import Edit from './views/Edit';
import Post from './views/Post';
import Login from './views/Login';

export const AppContext = createContext();

function App() {
  
  const cookies = new Cookies();
  const [login, setLogin] = useState({
    id:null,
    password:null,
  })
  const [isLogin, setIsLogin] = useState(false)
  const [token, setToken] = useState(cookies.get('token'))
  async function checkAuth() {
    let userInfo = {
        userId: login.id,
        userPw:login.password
    }
    await fetch(process.env.REACT_APP_SERVER_ADDRESS+process.env.REACT_APP_AUTH, {
        method:'POST',
        mode:'cors',
        credentials: 'include',
        headers:{
            'Content-Type' : 'application/json',
            'Accept' : 'application/json',
            'Access-Control-Allow-Origin':'*',
        },
        body: JSON.stringify(userInfo)
    })
    .then(res=>{
        return res.json();
    })
    .then(data=>{
      console.log('data',data)
      setToken(data.accessToken)
    })
    .catch(err=>{
      setToken(null)
    })
}
const onSaveInfo=(e)=>{

  console.log(e.target.value)
  let target = e.target.className;
  let values = e.target.value;

  if(target==='login_id'){
      setLogin({
          ...login,
          id:values
      })
  }else {
      setLogin({
          ...login,
          password:values
      })
  }
}

function getCookie() {
  return cookies.get('token')
}

useEffect(()=>{
  if(token!==null){
    setIsLogin(true)
  }
},[token])

useLayoutEffect(()=>{
  let temp = getCookie();
  if(temp!==undefined) {
    setToken(temp)
  }
},[])

  return (
    <AppContext.Provider value={token}>
        <BrowserRouter>
        <div className="App">
          <div className='content'>
            <Routes>
            {!isLogin
              ?
              <Route excact path="/" element={<Login onChange={onSaveInfo} onClick={checkAuth}/>}></Route>
              :
              <Route excact path="/" element={<Main/>}></Route>
              }
              <Route excact path="/" element={<Main/>}></Route>
              <Route excact path="/login" element={<Login onChange={onSaveInfo} onClick={checkAuth}/>}></Route>
              <Route path="/category/:id" element={<Main/>}></Route>
              <Route path="/read/:id" element={<Post/>}></Route>
              <Route path="/write" element={<Edit/>}></Route>
              <Route path="/write/:id" element={<Edit/>}></Route>
          </Routes>
          </div>
        </div> 
        </BrowserRouter>
    </AppContext.Provider>
  );
}
export default App;
  • Main.js (./src/views/Main.js)
import React,{useState, useEffect, useLayoutEffect, useContext} from "react";
import { useNavigate ,useParams,useLocation, redirect } from "react-router-dom";
import styled from "styled-components";
import moment from "moment/moment";
import { Cookies } from 'react-cookie';
// components
import Button from "../components/Button";
import Header from "../components/Header";
// svgs
import Edit from "../public/images/edit.png"
import Delete from "../public/images/delete.png"
import New from '../public/images/new.png'
import { AppContext } from "../App";

export default function Main(props){

    let location = useLocation();
    let navigate = useNavigate();
    const token = useContext(AppContext);

    const [isLogin, setIsLogin] = useState(false)
    const [posts, setPosts] = useState([])
    const [paramId, setParamId] = useState()
    const [btnDetail, setBtnDetail] = useState({
        title:'',
        url:''
    })
    
    const onClickPost=(e,num)=>{
        let url = num;
        navigate(`/read/${url}`, {
            state: {
                post_num: url
            }
        })
    }
    const onDeletePost=(e,num)=>{

        fetch(process.env.REACT_APP_SERVER_ADDRESS+process.env.REACT_APP_ACCESS_DEL+`/${num}`, {
            mode:'cors',
            method:'PATCH',
            headers:{
                'Content-Type' : 'application/json',
                'Accept' : 'application/json',
                'Access-Control-Allow-Origin':'*',
                'Authorization':'Bearer ' + token,
            }
        })
        .then(res=> {
            console.log(res,'resresee')
            return res.json();
        })
        .then((data)=>{
            if(data.code===200) {
                alert('삭제 완료')
            }
        })
        .catch((err)=> 
            console.log(err)
        );
    }

    function checkLogin() {
        console.log(token)
        if(token!==null) {
            // if(token==null) {
            setBtnDetail({
                ...btnDetail,
                title:'글 작성하기',
                url:'/write'
            })
            setIsLogin(true)
        }else{
            setBtnDetail({
                ...btnDetail,
                title:'로그인하기',
                url:'/login'
            })
        }
    }

    function getLists() {
        fetch(paramId === undefined ? process.env.REACT_APP_SERVER_ADDRESS+process.env.REACT_APP_ACCESS_BOARD : process.env.REACT_APP_SERVER_ADDRESS+process.env.REACT_APP_CATEGORY+'/'+paramId, {
            mode:'cors',
            headers:{
                'Accept' : 'application/json',
                'Access-Control-Allow-Origin':'*',
                'Authorization':'Bearer ' + token,
            }
        })
        .then(res=> {
            console.log(res)
            if(res.statusText==='Unauthorized') {
                alert('인증이 필요합니다.')
                window.location.replace('/')
            }
            return res.json();
        })
        .then(data=>{
            setPosts(data)
        })
        .catch((err)=> 
            console.log(err)
        );
    }

         useLayoutEffect(()=>{
            if(location.pathname==='/') {
                setParamId(undefined)
            }else {
                setParamId(location.state.category)
            }
            checkLogin()
            getLists()
        },[])

        useEffect(()=>{
            getLists()
        },[paramId])

    
    return(
        <MainStyle>
        <div className="mainWrapper">
            {/* <div className="mainTop">게시판</div> */}
            <div className="contents">
                <Header isShown={true} title={btnDetail.title} url={btnDetail.url} onReadUrl={setParamId}/>
                <div className="categoryTitle">{paramId === undefined ? '전체보기': paramId}</div>
                 <table className="lists">
                    <tr >
                        <th>번호</th>
                        <th>제목</th>
                        <th>글쓴이</th>
                        <th>작성시간</th>
                        <th>조회수</th>
                    </tr>
                    {
                        posts.map((post,index)=>{
                            return(
                            <tr key={`list`+index} name={post.num} onClick={(e)=>onClickPost(e,post.num)}>
                                <td>{(posts.length)-(index)}</td>
                                <td>{post.title}</td>
                                <td>{post.writer}</td>
                                <td>{moment(post.insert_date).format('YYYY-MM-DD HH:mm:ss')}</td>
                                <td>{post.hits}</td>
                            </tr>
                            )
                        })
                    }
                    
                </table>
                <input type='button' onClick={(e)=>{logout(e)}} value='로그아웃'></input>
            </div>
        </div>
        </MainStyle>
    )
}
profile
후론트엔드 개발자

0개의 댓글