게시글 Update의 경우 원래 작성하기 page를 수정 페이지와 동일하게 사용하고 싶었다.
하지만 어떻게 해야할지 감이 잘 안잡혀서 일단 기능만 구현하기 위해 수정으로 이동하는 다른 페이지를 만들어서 수정 버튼 눌렀을 때 update 전용 페이지로 이동하게끔 만들었다.
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;
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였다. 결국 끝까지 제대로 구현하지 못 하고 기술 매니저님의 도움을 받았다.
매니저님의 설명이 아니었다면 지금까지 끙끙 앓면서 절대로 해결하지 못 했을 것 같다...
앞으로 많이 연습을 해야 겨우 구현할 수 있을 것 같다.
지금은 쓰는대로의 코드에 익숙해지고 언젠간 같은 결과를 도출하는 코드라도 다른 형식으로 짤 수 있으면 좋겠다.