jwtToken

์ด๋™์–ธยท2024๋…„ 9์›” 27์ผ

new world

๋ชฉ๋ก ๋ณด๊ธฐ
51/62
post-thumbnail

9.27 (๊ธˆ)

1. extraReducers

export const postSigninThunk = createAsyncThunk('postSigninThunk', postSignin)


const signinSlice = createSlice({
    name: "signin",
    initialState: initialState,
    reducers: {
        signin: (state, action) => {
            console.log("signin state: "+state, action);
            const email = action.payload.username
            const result =  {email:email}

            cookies.set("member", JSON.stringify(result), {path:"/", maxAge: 60*60*24*7}) // stringify: ์ž๋ฐ”์˜ ๊ฐ์ฒด๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•ด

            return result
        },
        signout: (state, action) => {
            console.log("signout state: "+state, action);
            return {...initialState}
        },

    },
    extraReducers: builder => { // ์ƒํƒœ๋ฅผ ์ง์ ‘๊ด€๋ฆฌํ• ํ•„์š”๊ฐ€ ์—†์–ด์„œ extraReducers๋ฅผ ์‚ฌ์šฉ / ๋น„๋™๊ธฐ์ƒํƒœ๋ฅผ ์‹ ๊ฒฝ์“ธํ•„์š”๊ฐ€ ์—†๋‹ค.
        builder
            .addCase(postSigninThunk.fulfilled, (state,action) => { // fulfilled๋Š” ๋‹ค ๋˜์—ˆ๋‹ค๋ฉด ์ด๋ผ๋Š”๋œป
                console.log("postSigninThunk.fulfilled")


                const result = action.payload
                return result
            })
            .addCase(postSigninThunk.pending, (state,action) => { // pending์€ ๋กœ๋”ฉ์ค‘ ์ด๋ผ๋Š” ๋œป
                console.log("postSigninThunk.pending")
            })
    }
})

๐Ÿ‘‰ export๋ฅผ ๋ณด๋ฉด postSigninThunk๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ postSignin์„ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค๋Š” ๋‚ด์šฉ์ด ์žˆ๋Š”๋ฐ, postSignin์€ api์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋กœ๊ทธ์ธ๋ฉ”์†Œ๋“œ ์ด๋ฆ„์ด๋‹ค.

๐Ÿ‘‰ ๊ธฐ์กด์˜ reducers์•„๋ž˜์— ์ž‘์„ฑ๋˜๋Š” extraReucers๋Š” ํ˜„์žฌ ์ž‘์„ฑ๋œ reducers์™€ ๋‹ค๋ฅด๊ฒŒ API์„œ๋ฒ„๋ฅผ ํ†ตํ•œ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ง„ํ–‰ํ• ๋•Œ ์‚ฌ์šฉ๋˜๋Š”๊ฒƒ์œผ๋กœ ๋น„๋™๊ธฐ์ž‘์—… ํŠน์„ฑ์ƒ ์‹œ๊ฐ„์ฐจ๊ฐ€์žˆ์œผ๋ฏ€๋กœ ์š”์ฒญ์‘๋‹ต ๋ฐ ์š”์ฒญ๋Œ€๊ธฐ์™€ ๊ฐ™์€ ํ‘œํ˜„์„ ํ• ์ˆ˜์žˆ์œผ๋ฏ€๋กœ ์‹œ๊ฐ์ ์œผ๋กœ ์ข‹๋‹ค

๐Ÿ‘‰ ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” postSigninThunk๋Š” APIํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์„œ๋ฒ„์— ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋น„๋™๊ธฐ์ž‘์—…์ด๋ฏ€๋กœ extraReducers๋ฅผ ์‚ฌ์šฉ์ค‘์ด๋‹ค

๐Ÿ‘‰ fulfilled : ๋กœ๊ทธ์ธ ์š”์ฒญ์„ฑ๊ณต์‹œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ƒํƒœ์— ์ €์žฅ

const result = action.payload // ๊ฒฐ๊ณผ๊ฐ’์„ ์ƒํƒœ์—์ €์žฅ
                return result

๐Ÿ‘‰ pending : ์š”์ฒญ์ด ์ง„ํ–‰์ค‘์ผ๋•Œ ๋กœ๋”ฉ ์ƒํƒœ ์ฒ˜๋ฆฌ

console.log("postSigninThunk.pending") // ๋กœ๋”ฉ์ค‘

1-1. useSignin.ts

import {useAppDispatch, useAppSelector} from "./rtk.ts";
import {ISigninParam} from "../types/member.ts";
import {postSigninThunk, signin, signout} from "../slices/signinSlice.ts";
import {Cookies} from "react-cookie";


const cookies = new Cookies();

const loadCookie = () => {

    const memberCookie = cookies.get("member",{path:'/'}) // ์ฟ ํ‚ค๋ฅผ ๊ฐ€์ ธ์™€์„œ

    console.log("Cookie",memberCookie)

    return memberCookie; //์ฟ ํ‚ค๋ฅผ ๋ฆฌํ„ดํ•ด์ฃผ๋Š”๊ฒƒ
}

const useSignin = () => {

    const dispatch = useAppDispatch();

    let member = useAppSelector(state => state.signin) // email์˜ ์ƒํƒœ

    if(!member.email){ // ์ƒˆ๋กœ๊ณ ์นจํ•ด์„œ ํ˜„์žฌ member์˜ email๊ฐ’์ด ์—†์„๋•Œ ๋ฆฌํ„ด๋œ ์ฟ ํ‚ค๊ฐ’์ด member์— ๋ฐ›๊ฒŒ๋˜๋Š”๊ฒƒ.
        member = loadCookie()
    }

    const doSignin = (param:ISigninParam) => {

        dispatch(postSigninThunk(param)).unwrap().then( data => {
            console.log(data)
            cookies.set("member",data,{path:"/"})
        })
    }

    const doSignout = () => {

        dispatch(signout(null))
        cookies.remove("member",{path:'/'})
    }

    return{member, doSignin, doSignout}
}
export default useSignin

๐Ÿ‘‰ ์ด๊ณณ์—์„œ ์ค‘์ ์œผ๋กœ ๋ด์•ผํ• ์ ์€ doSignin์ธ๋ฐ, ๊ฒฐ๊ตญ api์„œ๋ฒ„์—์žˆ๋Š” ์ œ๋Œ€๋กœ๋œ username๊ณผ pw๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๋ง์ด ๋˜๋Š”๊ฒƒ์ด๊ณ , unwrap์„ ์ด์šฉํ•ด์„œ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ๋ฐ ์‹คํŒจ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.




2. jwtToken

2-1. ProductAPI.ts

import jwtAxios from "../util/jwtUtil.ts";


const host:string = 'http://localhost:8090/api/products'


export const postAdd = async (formData: FormData): Promise<number> => {

    const res = await jwtAxios.post(`${host}/`, formData)

    return Number(res.data.result)

}

export const getList = async ( page:number = 1, size:number = 10) => {

    const res = await jwtAxios.get(`${host}/list?page=${page}&size=${size}`)

    return res.data
}

๐Ÿ‘‰ ์ผ๋ฐ˜์ ์ธ axios๋ฅผ ์‚ฌ์šฉํ–ˆ์—ˆ์ง€๋งŒ, product์˜ˆ์ œ๋ถ€ํ„ฐ๋Š” token์„ ์ด์šฉํ•œ ์ธ์ฆ๊ด€๋ จ์„œ๋น„์Šค๊ฐ€ ๋“ค์–ด๊ฐ€๋ฏ€๋กœ jwt(token)์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•จ - ํ† ํฐ์ž๋™์ถ”๊ฐ€, ๊ฐฑ์‹ ์ฒ˜๋ฆฌ๋“ฑ




3. AccessToken / RefreshToken

๐Ÿ‘‰ ํ•ด๋‹น ํ† ํฐ๋“ค์— ๋Œ€ํ•œ ์ดํ•ด๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ ์„ค๋ช…๋ถ€ํ„ฐ ์ง„ํ–‰ํ•˜๊ฒ ๋‹ค. accessToken์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ์œ„ํ•ด ํ•„์š”ํ•œ ์ž…์žฅ๊ถŒ ๊ฐ™์€ ์—ญํ• ์„ ํ•œ๋‹ค. ํ•˜์ง€๋งŒ ์ด ํ† ํฐ์€ ๋งŒ๋ฃŒ์‹œ๊ฐ„์ด 10๋ถ„์œผ๋กœ ์งง๊ธฐ ๋•Œ๋ฌธ์— ๋งŒ๋ฃŒ๊ฐ€ ๋˜๋ฉด ์žฌ ๋ฐœ๊ธ‰์„ ๋ฐ›์•„์•ผํ•˜๋Š”๋ฐ, ๊ด€๋ จ๋œ ๋˜๋‹ค๋ฅธ ์ธ์ฆ์„ ๋ฐ›๋Š”๊ฒƒ์— ๋Œ€ํ•œ ๋ฒˆ๊ฑฐ๋กœ์›€ ๋•Œ๋ฌธ์— AccessToken์ด ๋ฐœ๊ธ‰์ด ๋˜๋Š”๋™์‹œ์— ๋งŒ๋ฃŒ๊ธฐ๊ฐ„์ด ๊ธด RefreshToken์„ ๊ฐ™์ด ๋ฐœ๊ธ‰ํ•ด์ค€๋‹ค

๐Ÿ‘‰ ๊ทธ๋ ‡๊ฒŒ AccessToken์ด ๋งŒ๋ฃŒ๋˜๊ฒŒ ๋˜๋ฉด AccessToken๊ณผ RefreshToken์„ ๊ฐ€์ ธ๊ฐ”์„๋•Œ, ์ƒˆ๋กœ์šด Access/Refresh Token์„ ์ฃผ๋Š” ์Šคํƒ€์ผ์ด๋‹ค.

3-1. memberAPI.ts

import {IMember, ISigninParam} from "../types/member.ts";
import axios from "axios";


const host:string = 'http://localhost:8090/api/member'

export const postSignin = async (param:ISigninParam):Promise<IMember> => { //id,pw๊ฐ’์„ ์ฃผ๋ฉด email๊ฐ’ return

    const res = await axios.post(`${host}/login`,
        param,
        {headers: {'Content-Type': 'application/x-www-form-urlencoded'}})

    return res.data
}

export const refreshRequest = async (accessToken:string, refreshToken:string):Promise<IMember> => { // access ๋งŒ๋ฃŒ๋ผ์„œ access,refresh์ค˜์•ผํ•จ


    const res = await axios.get(`${host}/refresh?refreshToken=${refreshToken}`, {
        headers: {'Authorization ': `Bearer ${accessToken}`},
    })

    console.log(res.data)

    return res.data
}

๐Ÿ‘‰ API์—์„œ postSignin์—์„œ๋Š” ํ•ด๋‹น๋˜๋Š” username, pw๋ฅผ ์ฃผ๊ฒŒ๋˜๋ฉด ๊ฐ token๋“ค์„ ๋ฐœ๊ธ‰ํ•ด์ฃผ๋Š”์—ญํ• ์„ํ•˜๊ณ 

๐Ÿ‘‰ refreshRequest์—์„œ๋Š” ๋งŒ๋ฃŒ๋œ accessToken๊ณผ ๋‹น์‹œ์— ๋ฐ›์€ refreshToken์„ ์ฃผ๊ฒŒ๋˜๋ฉด ์ƒˆ๋กœ์šด access,refresh Token์„ ๋ฐœ๊ธ‰ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

3-2. useSignin.ts

    const doSignin = (param:ISigninParam) => {

        dispatch(postSigninThunk(param)).unwrap().then( data => {

            cookies.set("member",data,{path:"/"})
        })
        console.log(cookies)
    }

๐Ÿ‘‰ ๊ทธ๋Ÿฌ๋ฉด useSignin์—์„œ api์„œ๋ฒ„์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ† ํฐ๋“ค์„ cookie์— ๋„ฃ์–ด์ค€๋‹ค.

3-3.jwtUtil.ts

import axios, {AxiosResponse, InternalAxiosRequestConfig} from "axios";
import {Cookies} from "react-cookie";
import {refreshRequest} from "../api/memberAPI.ts";

const cookies = new Cookies();

const jwtAxios = axios.create() // jwt๋กœ์ง์„ ์‚ฌ์šฉํ•œ๋‹ค.

const beforeReq = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => { // ์š”์ฒญ์ด ์„œ๋ฒ„์— ๋ณด๋‚ด์ง€๊ธฐ ์ „์— ํ˜ธ์ถœ

    console.log("beforeReq============")

    const memberCookie = cookies.get("member", {path:"/"}) // ์ฟ ํ‚ค์ฝ๊ธฐ

    if(!memberCookie) { // ์ฟ ํ‚ค๊ฐ€ ์—†๋‹ค๋ฉด ์š”์ฒญ์„ ๊ฑฐ๋ถ€ํ•œ๋‹ค.
        return Promise.reject("cookie is not found")
    }

    console.log(config)

    const accessToken = memberCookie.accessToken // signinSlice์—์„œ ๋ฐœ๊ธ‰๋œ ์ฟ ํ‚ค
    // ์ฟ ํ‚ค๊ฐ€ ์ด ๋‘๊ตฐ๋ฐ์—์„œ ๋ฐœ๊ธ‰์ด๋˜๋Š”๋ฐ signinSlice๋Š” ๋กœ๊ทธ์ธ์š”์ฒญํ›„์— ์‚ฌ์šฉ์ž ์ •๋ณด์ €์žฅ์šฉ - ํ•˜์ง€๋งŒ extrareducers๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ reducers์˜ cookie๋Š” ์‚ฌ์šฉ๋˜์ง€์•Š์Œ
    // useSignin์ฟ ํ‚ค๋Š” ์ƒˆ๋กœ๊ณ ์นจํ•ด์„œ ์ฟ ํ‚ค๊ฐ€ ์‚ฌ๋ผ์งˆ๋•Œ ์ฟ ํ‚ค์ •๋ณด ์ฝ์–ด์˜ค๋Š” ๋ณต์›์—ญํ•  ๊ทผ๋ฐ ๋‘๊ฐœ๋‹ค ๊ฐ™์€ member์ฟ ํ‚ค

    console.log("at",accessToken)

    if(accessToken){
        config.headers.Authorization = `Bearer ${accessToken}`
    }


    return config
}

const failReq = (error:any) => { // ์š”์ฒญ์—๋Ÿฌ ๋ฐœ์ƒ์‹œ
    console.log("failReq=============")

    return Promise.reject(error)

}
const beforeRes = async (res: AxiosResponse): Promise<AxiosResponse> => { // ์‘๋‹ต ๋ณด๋‚ด๊ธฐ์ „, 200ok
    console.log("beforeRes===============")


    const data = res.data // res์—๋Š” list๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ฐ’์ด ๋“ค์–ด๊ฐ€๋Š”๋ฐ, ํ† ํฐ์‹œ๊ฐ„์ด ๋งŒ๋ฃŒ๋˜๋ฉด error_access token์ด๋ผ๋Š” ๋ฉ”์„ธ์ง€๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ๋‹ค.

    if(data.error === 'ERROR_ACCESS_TOKEN') {
        console.log("access token์— ๋ฌธ์ œ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ refresh ํ•„์š”")

        const memberCookie = cookies.get("member", {path: "/"})
        const {accessToken, refreshToken} = memberCookie

        const refreshResult = await refreshRequest(accessToken, refreshToken) // api์„œ๋ฒ„์˜ refreshRequest๋ฅผ ํ†ตํ•ด์„œ ์ƒˆ๋กœ์šด ํ† ํฐ๋“ค์„ ๋ฐ›๊ธฐ

        console.log("refresh", refreshResult)

        memberCookie.accessToken = refreshResult.accessToken // ์ƒˆ๋กœ๋ฐ›์€ ์ฟ ํ‚ค๋“ค์„ ๋„ฃ๊ธฐ
        memberCookie.refreshToken = refreshResult.refreshToken

        cookies.set("member", memberCookie, {path: "/", maxAge: 60 * 60 * 24 * 7})

        const originalRequest = res.config
        originalRequest.headers.Authorization = `Bearer ${accessToken}`

        return await axios(originalRequest)


    }


    return res

}
const failRes = (error:any) => {
    console.log("failRes=============")

    return Promise.reject({response: {msg:error.message}})

}


jwtAxios.interceptors.request.use(beforeReq, failReq)

jwtAxios.interceptors.response.use(beforeRes, failRes)

export default jwtAxios

๐Ÿ‘‰ ํ•ด๋‹น์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด๋ณด์ž๋ฉด jwtUtil์€ ์ด 4๊ฐ€์ง€๋กœ ๋ถ„๋ฆฌ๋˜์–ด์žˆ๋‹ค.

  • beforeReq : ์š”์ฒญ์ด ์„œ๋ฒ„์— ๋ณด๋‚ด์ง€๊ธฐ ์ „์— ํ˜ธ์ถœ์ด๋œ๋‹ค
    ๐Ÿ‘‰ ์ฃผ๋กœ ์ฟ ํ‚ค๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์—ฌ ์ฟ ํ‚ค๊ฐ€ ์žˆ๋‹ค๋ฉด ์ฟ ํ‚ค๋‚ด๋ถ€์— accessToken์„ ํ™•์ธํ•ด์„œ, config.header๋‚ด๋ถ€์— accessToken์„ ๋„ฃ์–ด์ฃผ๋Š” ์—ญํ• 

  • failReq : ์š”์ฒญ์„ ํ•˜๋˜์ค‘์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ• ๋•Œ ํ˜ธ์ถœ์ด๋œ๋‹ค

  • beforeRes : ์‘๋‹ต์ด ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ด์ง€๊ธฐ ์ „์— ํ˜ธ์ถœ๋œ๋‹ค.
    ๐Ÿ‘‰ ์ฃผ๋กœ accessToken์ด ๋งŒ๋ฃŒ๋˜์–ด์„œ api์„œ๋ฒ„์— ์žˆ๋Š” refresh๊ด€๋ จ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ access/refreshToken์„ ์ „๋‹ฌํ•˜๋ฉด ์ƒˆ๋กœ์šด ํ† ํฐ๋“ค์„ ๋ฐ›๊ฒŒ๋˜๊ณ , ์ƒˆ๋กœ์šด ํ† ํฐ๋“ค์„ ์ฟ ํ‚ค์— ๋‹ค์‹œ ๋„ฃ์–ด์ฃผ๊ณ , Req์— ๋งˆ์ง€๋ง‰์— ํ–ˆ์—ˆ๋˜ config.header๋‚ด๋ถ€์— accesToken์„ ๋„ฃ์–ด์ฃผ๋Š” ์—ญํ• 

  • faliRes : ์‘๋‹ต ์ฒ˜๋ฆฌ ์ค‘์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋œ๋‹ค.




4. ReactQuery

๐Ÿ‘‰ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๐Ÿ“Œ npm i @tanstack/react-query
๐Ÿ“Œ npm i @tanstack/react-query-devtools

4-1. main.tsx

import { createRoot } from 'react-dom/client'
import './index.css'
import {RouterProvider} from "react-router-dom";
import mainRouter from "./router/mainRouter.tsx";
import {Provider} from "react-redux";
import projectStore from "./store.ts";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(

    <QueryClientProvider client={queryClient}>
        <Provider store={projectStore}>
            <RouterProvider router={mainRouter}></RouterProvider>
        </Provider>
        <ReactQueryDevtools initialIsOpen={true}></ReactQueryDevtools>
    </QueryClientProvider>
)

๐Ÿ‘‰ queryClient ๊ฐ์ฒด์ƒ์„ฑํ•ด์ฃผ๊ณ , ReactQueryDevtools ํƒœ๊ทธ์™€, QueryClientProvider ํƒœ๊ทธ๋ฅผ main.tsx์— ํฌํ•จ์‹œ์ผœ์ค€๋‹ค.




4-2. React-Query example

import {useQuery} from "@tanstack/react-query";
import {getList} from "../../api/ProductAPI.ts";

function ProductListPage() {

    const {} = useQuery({
        queryKey: ['product','list',1],
        queryFn: () => getList(1,10),
        staleTime: 1000*10
    })

    return (
        <div>
            <div>Product List page</div>
        </div>
    );
}

export default ProductListPage;

๐Ÿ‘‰ ํ•ด๋‹น ์ฝ”๋“œ๋ถ€ํ„ฐ ์„ค๋ช…์„ ํ•˜์ž๋ฉด, useQuery๋‚ด๋ถ€์—

  • queryKey : ๋ฐฐ์—ด๋กœ ๋ށ์Šค๋ฅผ ๋‚˜ํƒ€๋‚ธ๊ฒƒ์ด๋‹ค. ํ˜„์žฌ tsx๋Š” ProductListPage์œผ๋กœ product / list / 1๋ฒˆํŽ˜์ด์ง€ ๋ฅผ ๋‚˜ํƒ€๋ƒ„

  • queryFn : API์„œ๋ฒ„์˜ getList๋ฉ”์„œ๋“œ์— 1,10์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋„ฃ์€ ๊ฐ’์„ ํ˜ธ์ถœํ•œ๋‹ค

  • staleTime : ์‹ ์„ ๋„ ํŒŒ์•… 10์ดˆ๊นŒ์ง€๋Š” ์ด ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ ์„ ํ•˜๋‹ค๊ณ  ํŒ๋‹จ๋˜์–ด ํ•ด๋‹น ํŽ˜์ด์ง€ ๋ฐ apiํ˜ธ์ถœ์„ ํ•˜์ง€ ์•Š์ง€๋งŒ, 10์ดˆ๊ฐ€ ์ง€๋‚œ ์ดํ›„์—๋Š” ์ž๋™์œผ๋กœ ๋ฆฌ๋กœ๋“œ๋œ๋‹ค.




4-3. React-query another example

import {useQuery, useQueryClient} from "@tanstack/react-query";
import {getList} from "../../api/ProductAPI.ts";
import LoadingComponent from "../../common/LoadingComponent.tsx";
import {useSearchParams} from "react-router-dom";

function ProductListPage() {

    const [query,setQuery] = useSearchParams()

    const page: number = Number(query.get("page")) || 1

    const {isFetching} = useQuery({
        queryKey: ['product/list', page],
        queryFn: () => getList(page,10),
        staleTime: 1000*10
    })

    const queryClient =  useQueryClient()

    const changePage = (pageNum: number) => {

        if(page === pageNum){
            queryClient.invalidateQueries('product/list')
        }

        query.set("page", String(pageNum))
        setQuery(query)
    }

    return (
        <>
            <ul className='text-3xl'>
                <li className='text-3xl b-2 m-2 p-2 bg-blue-500'
                    onClick={()=> changePage(1)}>1</li>
                <li className='text-3xl b-2 m-2 p-2 bg-blue-500'
                    onClick={()=> changePage(2)}>2</li>
                <li className='text-3xl b-2 m-2 p-2 bg-blue-500'
                    onClick={()=> changePage(3)}>3</li>
            </ul>
        {isFetching&& <LoadingComponent/>}
        <div>
            <div>Product List page</div>
        </div>
    </>
    );
}

export default ProductListPage;

๐Ÿ‘‰ ํ˜„์žฌ์ฝ”๋“œ์—์„œ๋Š” getList๋ฉ”์„œ๋“œ๋ฅผ api์„œ๋ฒ„์—์„œ ํ˜ธ์ถœํ•˜๋Š”๋ฐ, page์— ๋”ฐ๋ฅธ ํ˜ธ์ถœ์ด ๊ฐ€๋Šฅํ•œ ์ƒํƒœ์ด๋‹ค. useSearchParams๋ฅผ ์ด์šฉํ•˜์—ฌ url์˜ page๊ฐ’์„ ๋ถˆ๋Ÿฌ์™€ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•˜๊ณ , changPage๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ํด๋ฆญ์‹œ ํŽ˜์ด์ง€๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค. ๋˜ํ•œ useQueryClient๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ํ˜„์žฌํŽ˜์ด์ง€์™€ ๋‚ด๊ฐ€ ์„ ํƒํ•œ ํŽ˜์ด์ง€๊ฐ€ ๋™์ผํ• ๋•Œ url์ด ๋™์ผํ•˜๋”๋ผ๋„ ๋ฆฌ๋กœ๋“œ ๋˜๋„๋ก ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํšจํ™”ํ•œ๋‹ค.

๐Ÿ‘‰ ๋˜ํ•œ isFeaching ์„ ํ†ตํ•ด ํ˜„์žฌ ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ณ  ์žˆ์„๋•Œ LoadingComponent๋ฅผ ์ถœ๋ ฅํ• ์ˆ˜์žˆ๋„๋ก ๊ตฌ์กฐํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค.




5. jwtToken Access / Refresh Token API ์‚ฌ์šฉ๋ฒ•

  • ๋กœ๊ทธ์ธ

POST - http://localhost:8090/api/member/login
body - (x-www-from-urlencoded)๋กœ
key: username / value: user1@aaa.com
key: password / value: 1111

send

accessToken / refreshToken์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

  • ํ˜ธ์ถœ

GET - http://localhost:8090/api/products/list
Headers
key: Authorization / value: Bearer (accessToken)

send

๊ฒฐ๊ณผ๊ฐ’ ๋ฐ›์„์ˆ˜์žˆ๋‹ค

  • accessToken ๋งŒ๋ฃŒ

GET - http://localhost:8090/api/member/refresh?(refreshToken)
Headers
Key: Authorization / value: Bearer (๋งŒ๋ฃŒ๋œ accessToken)
Body
Key:refreshToken

0๊ฐœ์˜ ๋Œ“๊ธ€