[TIL 2022. 12. 15] Axios와 React, Redux-Toolkit 사용 (Update 구현)

김헤일리·2022년 12월 15일
0

TIL

목록 보기
13/46
post-custom-banner

게시글 Update의 경우 원래 작성하기 page를 수정 페이지와 동일하게 사용하고 싶었다.

하지만 어떻게 해야할지 감이 잘 안잡혀서 일단 기능만 구현하기 위해 수정으로 이동하는 다른 페이지를 만들어서 수정 버튼 눌렀을 때 update 전용 페이지로 이동하게끔 만들었다.


1. 게시글 수정하기 (Axios PATCH, Update 구현 부분)

❗️ UPDATE의 화면 부분

  • 일기의 상세 페이지에서 [수정] 버튼 클릭 시 해당 게시글의 내용을 수정할 수 있는 페이지로 이동한다.
  • 상세 페이지에서 이동하게된 수정 페이지를 UpdateCard라는 컴포넌트로 분리했다.
function UpdateCard() {
  const detail = useSelector((state) => state.diary.detail);
  // 1. 일기를 수정할 때 다시 사용할 제목과 컨텐츠값의 state를 useSelector로 불러서 상수 detail에 담는다.
  // 1-1. 일기 수정은 1개의 일기만 필요하기 때문에 diary state중 detail 부분을 불러온다.
  const [title, setTitle] = useState(detail.title);
  // 2. 수정할 때는 이미 있는 일기의 값을 불러오기 때문에 useState로 관리할 title의 값을 특정 일기의 title값으로 지정한다.
  // 2-1. 특정 일기의 title값으로 지정하기 위해 useState 내부에 있는 initial value를 detail.title로 설정한다.
  const [content, setContent] = useState(detail.content);
  // 3. 위의 title과 마찬가지로 content도 useState의 초기값을 detail.content로 설정한다.

  const { id } = useParams();
  // 4. useParams를 이용해서 url의 파라미터 (detail의 아이디값으로 사용)을 가져와서 id라는 상수에 담는다.

  const dispatch = useDispatch();

  const onEditHandler = () => {
  // 11. 수정 페이지에서 저장 버튼 클릭 시 실행되는 함수다.
    const editDiary = { id, title, content };
    // 11-1. editDiary라는 상수에 변경된 내역의 id, title, content를 담는다.
    // 11-2. 객체의 key/value값을 뜻하는 상수 이름이 동일하기 때문에 "key":value 형식을 사용하지 않고 상수명을 통합한다.
    if (title === '' || content === '') {
      alert('제목과 타이틀을 모두 작성해주세요.');
    } else {
      dispatch(updateDiary(editDiary));
      // 11-3. title과 content의 값이 비어있지 않은 경우, editDiary를 dispatch를 통해 updateDiary로 전달한다.
    }
  };

  useEffect(() => {
    dispatch(getDiaryId(id));
    // 5. 상세 페이지에서 이동한 경우, 해당 일기의 값을 그대로 불러오기 위해선 다시 한번 getDiaryId를 이용한다.
    // 5-1. 매개변수로 url의 파라미터를 넘겨서 수정하고자 하는 일기가 상세 페이지의 일기와 동일하도록 한다.
    // 5-2. url의 파라미터는 특정 일기의 고유 id값과 동일하다.
  }, []); 
  // 5-3. 의존성 배열에 있는 값이 변경될 때 useEffect 내부 로직을 실행한다.
  // 5-4. 배열에 아무 값이 없기 때문에 useEffect는 컴포넌트가 렌더링되는 최초 1회만 실행된다.
  // 5-5. 수정하기 위해서 getDiaryId의 호출은 최초 1번이면 되기 때문에 의존성 배열을 비워둔다.


  useEffect(() => {
    setTitle(detail.title);
    setContent(detail.content);
  }, [detail]);
  // 6. 수정의 경우, 기존값의 title과 content를 불러오기 위해 상수 detail을 의존성 배열에 넣는다.
  // 6-1. 상수 detail은 다시 쓰기를 선택했을 때 불러오는 값아기 때문에, 변경되어야할 값의 state가 저장되어있다.
  // 6-2. useEffect를 통해 setTitle과 setContent 함수를 조종하여 title과 content에 기존값의 제목과 내용을 담는다.
  

  return (
    <DiaryWrite>
      <h2
        style={{
          display: 'flex',
          flexDirection: 'column',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        일기 작성
      </h2>
      <LinkDiv>
        <Link to={`/details/${id}`} className="datail">
		// 7. useParams로 가져온 id는 특정 일기의 id와 마찬가지기 때문에 [그만쓰기] 클릭 시 기존의 상세 페이지로 이동한다.
		// 7-1. 그냥 이동만 하기 때문에 수정된 내역이 저장되지 않는다.
          <span>그만쓰기</span>
        </Link>
        <Button type="submit" color="rgb(78, 183, 164)" onClick={onEditHandler}>
          저장
        </Button>
		// 8. 저장 버튼 클릭 시 onEditHandler 함수가 실행된다.
      </LinkDiv>
      <StInputContainer>
        <Input
          id="title"
          name="DiaryTitle"
          label="제목"
          value={title}
		  // 10. 인풋필드의 값이 title로 지정되어 있고, 수정 페이지에서의 title은 detail의 title이다.
		  // 10-1. 그렇기 때문에 기존에 작성되어 있던 값이 그대로 불러와진다.
          onChange={(event) => {
            setTitle(event.target.value);
			// 10-2. 인풋필드의 값이 변할 경우, 변한 값이 setTitle에 입력되어 기존 title값이 변하게된다.
          }}
        />
		// 8-3. 인풋필드의 값이 title로 지정되어 있고 setTitle로 값을 변경시키기 때문에 기존 값이 수정된다,
        <textarea
          value={content}
		  // 9. textarea에 값도 content로 지정되어있고 수정 페이지의 content는 detail의 content이다.
		  // 9-1. 때문에 textarea가 불러와질 때 빈 내역이 아니라 기존 내역이 담긴채로 불러와진다.
          onChange={(event) => {
            setContent(event.target.value);
			// 9-2. textarea의 값이 변할 경우, 변한 값이 setContent에 담기기 때문에 기존 content의 내용이 변경된다.
          }}
        />
      </StInputContainer>
    </DiaryWrite>
  );
}

export default UpdateCard;

❗️ UPDATE의 slice 파일 부분

const initialState = {
  diary: [
    {
      id: 1,
      title: 'diary',
      content: 'write diary',
    },
  ],
  detail: {
    id: 1,
    title: 'diary',
    content: 'write diary',
  },
  isLoading: false,
  error: null,
};

export const updateDiary = createAsyncThunk(
// 1. updateDiary라는 상수에 비동기 함수 createAsyncThunk 함수를 할당한다.
  'diary/updateDiary',
  // 2. 비동기 함수의 첫번째 매개변수로 액션 밸류를 지정한다.
  async (editDiary, thunkAPI) => {
  // 3. 두번째 매개변수로 미들웨어가 처리할 또 다른 비동기 함수를 할당한다.
  // 3-1. async 함수의 첫번째 매개변수는 컴포넌트 파일에서 dispatch를 통해 가져온 값인 editDiary다.
  // 3-2. 두번째 매개변수는 리턴값을 promise 형태로 반환하는 thunkAPI다.
  try {
  // 4. 만약 실행이 잘 될 경우:
    const response = await axios.patch(`http://localhost:3001/diary/${editDiary.id}`, editDiary);
    // 4-1. reponse라는 상수에 axios를 통해 연결할 api 주소를 함수의 첫번째 매개변수로 정의한다.
    // 4-2. 이때 api 주소의 파라미터로 변경된 일기의 고유 id값을 넘긴다.
    // 4-3. axios와 연결되는 함수의 두번째 매개변수로 서버에 전달할 값 (editDiary)를 보낸다.
    return thunkAPI.fulfillWithValue(response.data);
    // 4-4. 서버와 연결이 잘된 경우, response에 담긴 config 중 data를 뽑아서 리듀서로 보낸다.
  } catch (error) {
    // 5. 비동기 함수가 처리될 때 에러가 생길 경우:
    return thunkAPI.rejectWithValue(error);
    // 5-1. 연결 실패 케이스로 에러를 담아서 리듀서에 보낸다.
  }
});


export const diarySlice = createSlice({
  name: 'diary',
  initialState,
  reducers: {},
  extraReducers: {
    [updateDiary.fulfilled]: (state, action) => {
    // 6. updateDiary가 성공할 경우 실행되는 리듀서로, 매개변수에 state와 action을 둔다.
      state.detail = action.payload;
      // 6-1. 기존 일기의 state를 액션을 통해 받아온 payload의 값으로 대체해서 수정된 내용이 새로 store에 저장되게 한다.
    },
    [updateDiary.rejected]: (state, action) => {
    // 7. updateDiary가 실패할 경우 실행되는 리듀서로, 매개변수에 동일하게 state와 action을 둔다
      state.error = action.payload;
      // 7-1. 기존 state에 있는 error (null로 설정되어있음)에 실패시 받아오는 payload를 대입한다.
    },
  },
});


CRUD 중 가장 어려운 것은 Update였다. 결국 끝까지 제대로 구현하지 못 하고 기술 매니저님의 도움을 받았다.
매니저님의 설명이 아니었다면 지금까지 끙끙 앓면서 절대로 해결하지 못 했을 것 같다...
앞으로 많이 연습을 해야 겨우 구현할 수 있을 것 같다.
지금은 쓰는대로의 코드에 익숙해지고 언젠간 같은 결과를 도출하는 코드라도 다른 형식으로 짤 수 있으면 좋겠다.

profile
공부하느라 녹는 중... 밖에 안 나가서 버섯 피는 중... 🍄
post-custom-banner

0개의 댓글