[TIL #34] Redux-toolkit 미들웨어 thunk 사용 중 생긴 일

Bora.K | 권보라·2023년 12월 1일
1

TIL

목록 보기
34/51
post-thumbnail

오늘 한 일


  • [React 심화] 강의 듣기
  • [개인 Project] 팬레터함 디벨롭(React Toolkit)
    • redux toolkit으로 전역 상태 관리
    • redux toolkit 내장 API인 thunk로 서버 상태 관리
    • JWT 토큰을 이용한 인증, 인가 기능 구현

학습 내용

[React 심화] 개인 프로젝트 : 팬레터함 사이트 리팩토링


Redux-thunk로 리팩토링

이번 과제는 기존에 Redux로 구현했던 팬레터함 사이트를 리덕스 툴킷(Redux-toolkit)을 이용하여 전역 상태를 관리하고, 리덕스 툴킷의 내장 미들웨어인 thunk 기능을 이용하여 서버 상태를 관리하는 것이다.

처음 접하는 개념이라 강의를 두번 씩 돌려보고, 과제를 하면서도 헷갈리는 부분은 돌려보면서 thunk를 이용하여 기본 셋팅을 진행했다. json-server와 thunk, Redux-toolkit으로 구현하다보니 에러가 무수히 많이 발생했다. 일부 에러들은 서칭을 통해 해결 가능했는데, 정말 이유를 알 수 없는 것들이 있어서 어제, 오늘 튜터님들을 많이 찾아갔다. 오늘 했던 실수는 생각해 보면 정말 간단한 것들인데, 아직 Redux 개념과 사용법을 정확하게 인지하지 못하고 있어서 발생한 문제였다.

1. 리팩토링

강의를 보면서 Redux-tooldit을 이용한 전역 상태를 셋팅하고,
thunk 기능을 추가하여 코드를 추가했다.

  • configStore.js
    configureStore()라는 내장 API를 사용하여 스토어 로직을 변경하였다.
import { configureStore } from "@reduxjs/toolkit";
import letters from "redux/modules/lettersSlice";
import member from "redux/modules/memberSlice";
import auth from "redux/modules/authSlice";

const store = configureStore({
  reducer: {
    letters,
    member,
    auth,
  },
});
export default store;
  • lettersSlice.js
    lettersSlice.js에서 redux-toolkit으로 전역 함수 코드를 수정한 후
    createAsynkThunk()로 thunk 함수를 구현하였다.
import { createSlice } from "@reduxjs/toolkit";

// 1. initialState에 통신과 관련된 state 추가
const initialState = {
  letters: [],
  isLoading: false,
  isError: false,
  error: null,
};

// 2. createAsyncThunk로 thunk 함수 구현
export const __getLetters = createAsyncThunk(
  "getLetters",
  async (payload, thunkAPI) => {
    try {
      const response = await axios.get("http://localhost:3001/letters");
      console.log("response :", response);
      return thunkAPI.fulfillWithValue(response.data);
    } catch (error) {
      console.log("error :", error);
      return thunkAPI.rejectWithValue(error);
    }
  }
);

// 3. 기존 리듀서 로직에 extraReducers 추가
const lettersSlice = createSlice({
  name: "letters",
  initialState,
  reducers: {
    addLetter: (state, action) => {
      const newLetter = action.payload;
      return [newLetter, ...state];
    },
    deleteLetter: (state, action) => {
      const letterId = action.payload;
      return state.filter((letter) => letter.id !== letterId);
    },
    editLetter: (state, action) => {
      const { id, editedContent } = action.payload;
      return state.map((letter) => {
        if (letter.id === id) {
          return { ...letter, content: editedContent };
        }
        return letter;
      });
    },
    setLetter: (state, action) => {
      return action.payload;
    },
  },
  
  // extraReducers
    extraReducers: (builder) => {
    builder
      .addCase(__getLetters.pending, (state, action) => {
        state.isLoading = true;
        state.isError = false;
      })
      .addCase(__getLetters.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isError = false;
        state.letters = action.payload;
      })
      .addCase(__getLetters.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.error = action.payload;
      });
  },
});

export default lettersSlice.reducer;
export const { addLetter, deleteLetter, editLetter, setLetter } =
  lettersSlice.actions;

2. 오류 발생

따라 하니까 생각보다 잘 풀리고 있다고 생각하고 있었는데, thunk로 json-server로 데이터를 받아와서 서버 상태 관리 로직을 추가하자마자 오류가 발생했다. 기존에는 정상적으로 잘 작동하던 등록, 수정, 삭제 기능에 문제가 생긴 것이다. json 데이터 상에는 정상적으로 등록, 수정, 삭제가 되었고, 페이지를 새로고침하면 반영은 되어있었다. 그런데 실시간으로 반영을 못하고 계속 에러가 뜨면서 undefined가 콘솔에 찍혔다.

결국 튜터님을 찾아가 해결할 수 있었는데, 알고보면 굉장히 당연한 실수를 하고 있었다.

(1) 문제의 원인 파악

문제가 발생한 부분은 등록, 수정, 삭제 버튼이 작동하는 과정에서였는데, 해당 버튼 핸들러 내부 로직에는 문제가 없었다. 문제가 되는 부분은 바로 letterSlice의 리듀서 부분! thunk 기능을 사용하면서 initialState를 변경하였는데, 이 부분을 리듀서에 수정 반영하지 않아서 생긴 문제였다.

  • 기존 initialState
const initialState = [];
  • 수정된 initialState
const initialState = {
  letters: [],
  isLoading: false,
  isError: false,
  error: null,
};

(2) 수정해야할 Reducer의 return 부분

기존 initialState는 배열이였다. 그런데 서버 상태 관련 state를 추가하면서 객체로 바뀌었다. 리듀서에서 return 부분은 initialState와 동일한 상태로 리턴해야 한다. 기존에는 배열이었기 때문에 배열로 리턴했었다. 그런데 객체로 바뀌었기 때문에 리듀서의 리턴문에도 객체 형태로 바꾸어 주어야 했다.

  • 기존 리듀서
return [newLetter, ...state];
  • 수정 리듀서 : 기존 state를 그대로 나열하고, state의 letters 부분만 변경한다.
return {...state, letters: [newLetter, ...state.letter]};

3. 수정한 코드

  • lettersSlice.js
const lettersSlice = createSlice({
  name: "letters",
  initialState,
  
  // 리듀서 로직 부분
  reducers: {
    addLetter: (state, action) => {
      const newLetter = action.payload;
      return { ...state, letters: [newLetter, ...state.letters] };
    },
    deleteLetter: (state, action) => {
      const letterId = action.payload;
      return state.filter((letter) => letter.id !== letterId);
    },
    editLetter: (state, action) => {
      const { id, editedContent } = action.payload;
      return state.map((letter) => {
        if (letter.id === id) {
          return { ...letter, content: editedContent };
        }
        return letter;
      });
    },
    setLetter: (state, action) => {
      return { ...state, letters: action.payload };
    },
  },
  extraReducers: (builder) => {
// ... 이하 생략...

오늘의 회고


어제와 오늘, 튜터님들을 여러 번 찾아가 보면서 느낀 점이 있다. 이미 다른 사람들과 대화 중인 경우가 많아서 매번 다른 튜터님을 찾아갔었는데, 공통적인 특징이 있었다. 문제의 원인을 찾아가는 과정이다. 물론 능숙하신 분들이기 때문에 무엇이 문제인지 빠르게 파악이 가능하지만, 내가 이해하기 쉽도록 문제의 원인을 찾아가는 과정을 알려주시려고 한 단계, 한 단계씩 같이 찾아나간다는 것이었다.

지금은 뭐가 뭔지 몰라서 개발자 창을 열어 콘솔을 보는 게 다이고, 그마저도 오류를 봐도 어디가 잘못됐다는 것인지, console log를 찍어봐도 왜 문제가 되는 것인지 잘 모른다. 튜터님들과 문제의 원인을 찾아가는 과정이 신기하기도 하고 재밌었다. 네트워크 창도 열어서 서버가 잘 받아지고 있는지 보고, 콘솔이 찍히는 순서도 보면서 문제의 원인을 찾아가는 과정이 신기했다.

나는 몇 시간을 붙잡고 서칭해도 해결이 안 되던 것들이, 콘솔 창과 네트워크 창만 보고 문제가 발생한 코드 부분을 찾아서 거슬러 올라가는데... 나는 언제쯤 저런 문제 원인을 찾는 과정을 거쳐서 문제를 해결할 수 있을까? 빨리 능숙해지고 싶다....그래도 튜터님께서 설명해 주실 때 이해하고 그 부분을 바로바로 찾아서 수정한 나 칭찬해!


profile
Frontend Engineers

0개의 댓글