작성
·
738
0
안녕하세요. 제로초님 강의 잘 듣고있습니다.
게시글 수정을 구현하다 궁금한 점이 있어서 질문드립니다.
저는 게시글의 수정버튼을 클릭하면 해당 post의 정보를 state에 저장한 뒤 posting페이지로 이동하려고 했습니다.
하지만 두페이지 모두 ssr이 적용되어 있어 수정페이지로 이동하면 state가 초기화되어서 수정 포스트정보가 없어지더라고요
혹시 링크이동 시 데이터를 포함하거나 ssr시 초기화되지 않을 state를 따로 지정할 수 있는 방법이 있을까요?
// 게시글 수정
const onClickEditBtn = useCallback((post) => () => {
dispatch(moveToPostEditRequestAction(post)); // 수정할 게시글의 정보를 state로 저장한 뒤
Router.push('/posting'); // 포스팅 페이지로 이동
}, []);
<ContentBtn type='text' onClick={onClickEditBtn(post)}>수정</ContentBtn>
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { message } from 'antd';
import Head from 'next/head';
import Router from 'next/router';
import wrapper from '../store/configureStore';
import axios from 'axios';
import { END } from 'redux-saga';
import AppLayout from '../components/AppLayout/';
import PostingForm from '../components/PostingForm';
import { LOAD_MY_INFO_REQUEST } from '../reducers/user';
import { PostingText, PageMainText, PageSubText } from '../styles/pageStyles';
const Posting = () => {
const { me } = useSelector((state) => state.user);
const { addPostDone } = useSelector((state) => state.post);
useEffect(() => {
if (addPostDone) {
message.success('게시글이 정상적으로 포스팅되었습니다.', 1.5);
Router.replace('/');
}
}, [addPostDone]);
useEffect(() => {
if (!me) {
message.error('로그인이 필요한 서비스입니다.', 1.5);
Router.push('/');
}
}, [me]);
return (
<>
<Head>
<title>게시글 작성 | Recipe.io</title>
</Head>
<AppLayout>
<PostingText>
<PageMainText className='bolder'>POSTING</PageMainText>
<PageSubText>Sharing your recipes leads to the joyous happiness of others</PageSubText>
</PostingText>
<PostingForm />
</AppLayout>
</>
)
};
// 하지만 포스팅페이지는 ssr을 사용해서 전달한 수정 포스트가 없다
export const getServerSideProps = wrapper.getServerSideProps(async (context) => {
console.log(`context: ${context}`);
const cookie = context.req ? context.req.headers.cookie : '';
axios.defaults.headers.Cookie = '';
if (context.req && cookie) {
axios.defaults.headers.Cookie = cookie;
}
context.store.dispatch({
type: LOAD_MY_INFO_REQUEST,
});
context.store.dispatch(END);
await context.store.sagaTask.toPromise();
});
export default Posting;
답변 1
0
항상 답변 감사합니다!!
답변해주신대로 return { ...state }를 적용했는데 어떤 이유인지 로그인도 안되고 ssr로 불러오던 게시글의 정보도 받아올 수 가 없게되네요 ㅜㅜ
import { HYDRATE } from 'next-redux-wrapper';
import { combineReducers } from 'redux';
import user from './user'
import post from './post'
const rootReducer = (state, action) => {
switch (action.type) {
case HYDRATE:
console.log(('HYDRATE', action));
// return action.payload;
return { ...state };
default: {
const combineReducer = combineReducers({
user,
post,
});
return combineReducer(state, action);
}
}
}
export default rootReducer;
아닙니다 곤란한 질문드려서 죄송합니다.
제가 자료를 찾은 뒤 redux-persist를 사용해서 localstorage 에 editPost state 를 저장한 뒤 hydrate 하는 걸 시도해봤는데
Server Side Render시에는 localstorage 접근이 불가능해서 제가 생각한대로 hydrate 가 제대로 동작하지 안더라고요
혹시 위와같은 방법으로는 해결이 불가능한가요?
https://github.com/kirill-konshin/next-redux-wrapper#server-and-client-state-separation
공식문서에도 이런식으로 client용을 따로 구분하라고 되어있네요.
게시글 수정을 구현하다 궁금점이 있어서 추가로 질문드릴께요
이전 질문과 마찬가지로 저는 수정할 게시글을 클릭하면 게시글의 정보를 editPost state로 가져와 initialvalues로 설정해 수정 기능을 구현하고 있습니다.
근데 editPost.Images는 DB에서 가져온 자료여서 upload에 initialvalues로 설정해도 기존 이미지 제거, 새로운 이미지를 추가..등등 이미지와 관련된 수정작업시에 onChange함수가 동작을 안합니다.
혹시 위와 같은 문제를 해결할 수 있는 방법이 있을까요? 코드 함께 첨부하겠습니다.
postingForm
const PostingForm = () => {
const { editPost } = useSelector((state) => state.post);
const normFile = useCallback((e) => {
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
}, []);
const onChangeImages = useCallback((e) => {
const imageFormData = new FormData();
e.fileList.forEach((f) => {
imageFormData.append('image', f.originFileObj);
});
dispatch({
type: UPLOAD_IMAGES_REQUEST,
data: imageFormData,
});
}, []);
const onBeforeUpload = useCallback((file, fileList) => {
return false
}, []);
useEffect(() => {
if (editPost) {
const images = editPost.Images.map((v) => v.src);
dispatch({
type: EDIT_POST_UPLOAD_IMAGES,
data: images,
});
}
}, []);
return (
<section>
<PostingFormWrapper
initialValues={
editPost && {
title: editPost.title,
desc: editPost?.desc,
images: editPost.Images,
ingredient: editPost.ingredient,
recipes: editPost.recipes,
tips: editPost?.tips,
tags: editPost?.tags,
}}
form={form}
name="posting"
onFinish={onSubmitForm}
scrollToFirstError
encType='multipart/form-data'
>
<ImageUploaderWrapper
name="images"
rules={[
{
required: true,
message: '조리사진을 첨부하세요.',
},
]}
valuePropName="fileList"
getValueFromEvent={normFile}
>
<Upload.Dragger
name="image"
multiple
listType="picture"
onRemove={onRemoveUpload}
onChange={onChangeImages}
beforeUpload={onBeforeUpload}
>
<ImageUploaderText className='bold'>Drag files here OR</ImageUploaderText>
<Button type='primary' size='large' icon={<UploadOutlined />}>Upload</Button>
</Upload.Dragger>
</ImageUploaderWrapper>
)
};
export default PostingForm;
reducers/post.js
case EDIT_POST_UPLOAD_IMAGES:
draft.imagePaths = action.data;
break;
case UPLOAD_IMAGES_REQUEST:
draft.uploadImagesLoading = true;
draft.uploadImagesDone = false;
draft.uploadImagesError = null;
break;
case UPLOAD_IMAGES_SUCCESS:
draft.editPost ? draft.imagePaths.push(...action.data) : draft.imagePaths = action.data;
draft.uploadImagesLoading = false;
draft.uploadImagesDone = true;
break;
case UPLOAD_IMAGES_FAILURE:
draft.uploadImagesLoading = false;
draft.uploadImagesError = action.error;
break;
saga/post.js
function uploadImagesAPI(data) {
return axios.post('/post/images', data);
}
function* uploadImages(action) {
try {
const result = yield call(uploadImagesAPI, action.data);
yield put({
type: UPLOAD_IMAGES_SUCCESS,
data: result.data,
})
} catch(err) {
console.error(err);
yield put({
type: UPLOAD_IMAGES_FAILURE,
error: err.response.data,
})
}
}
routes/post.js
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, 'uploads');
},
filename(req, file, done) {
const ext = path.extname(file.originalname);
const basename = path.basename(file.originalname, ext);
done(null, basename + '_' + new Date().getTime() + ext);
},
}),
limits: { fileSize: 20 * 1024 * 1024 },
});
router.post('/images', isLoggedIn, upload.array('image'), async (req, res, next) => { // uploadImagesAPI / POST /post/images
try {
res.json(req.files.map((v) => v.filename));
} catch (error) {
console.error(error);
next(error);
}
});
이미 업로드된 이미지들에 대해서는 input type file에 넣을 수 없으므로 이미 업로드된 이미지들에 대한 데이터와 새로 업로드할 이미지에 대한 데이터 이렇게 두 개로 분리하여 관리해야 할 것 같습니다. 그래야 새 이미지를 추가하는 것 뿐만 아니라 기존 이미지를 삭제하는 것에도 대응 가능합니다.
제로초님 정말 죄송한데 게시글 수정 관련해서 마지막으로 질문드릴께요.
게시글 수정기능을 구현한 뒤 실행했더니 EDIT_POST_FAILURE 액션이 실행되면서 다음과 같은 에러가 발생했습니다.
code: 'ER_TRUNCATED_WRONG_VALUE',
errno: 1292,
sqlState: '22007',
sqlMessage: "Truncated incorrect DOUBLE value: '[object SequelizeInstance:Image],false'",
sql: "UPDATE `images` SET `PostId`=?,`updatedAt`=? WHERE `id` IN ('[object SequelizeInstance:Image],false', '[object SequelizeInstance:Image],false', '[object SequelizeInstance:Image],true')",
parameters: [ 119, '2022-10-22 11:50:30' ]
},
sql: "UPDATE `images` SET `PostId`=?,`updatedAt`=? WHERE `id` IN ('[object SequelizeInstance:Image],false', '[object SequelizeInstance:Image],false', '[object SequelizeInstance:Image],true')",
parameters: [ 119, '2022-10-22 11:50:30' ]
}
이 후 해당 게시글을 확인해보니 이미지를 제외한 나머지부분은 전부 정상적으로 변경되었습니다.
확실하지는 않지만 해당 오류가 `PostId`=?,`부분 때문에 발생한것같아 여러 시도를 해봤는데 문제를 해결할 수 없었습니다
바쁘시겠지만 해당 문제 관련해서 피드백 해주시면 감사하겠습니다.
관련 코드함께 첨부하겠습니다.
saga/post.js
// data: {
fullEditImagePaths, // 수정된 이미지파일의 경로
content: value, // 수정된 게시글의 컨텐츠
postId: editPost.id, // 수정 게시글의 id
}
function editPostAPI(data) {
return axios.patch(`/post/${data.postId}/edit`, data);
}
function* editPost(action) {
try {
const result = yield call(editPostAPI, action.data);
yield put({
type: EDIT_POST_SUCCESS,
data: result.data,
})
} catch(err) {
console.error(err);
yield put({
type: EDIT_POST_FAILURE,
error: err.response.data,
})
}
}
route/post.js
router.patch('/:postId/edit', isLoggedIn, async (req, res, next) => { // editPostAPI / PATCH /post/1/edit
try {
await Post.update({
title: req.body.content.title,
desc: req.body.content.desc,
ingredient: req.body.content.ingredient,
recipes: req.body.content.recipes,
tips: req.body.content.tips,
tags: req.body.content.tags,
}, {
where: {
id: req.params.postId,
}
});
const post = await Post.findOne({
where: { id: req.params.postId }
})
if (req.body.content.tags && req.body.content.tags.length !== 0) {
const hashtags = req.body.content.tags.split(/(#[^\s#]+)/g).map((v, i) => {
if (v.match(/(#[^\s#]+)/)) {
return v;
}
return null;
});
const hashtagArr = hashtags.filter((v, i) => v != null);
const result = await Promise.all(hashtagArr.map((tag) => Hashtag.findOrCreate({
where: { name: tag.slice(1).toLowerCase() },
})));
await post.setHashtags(result.map((v) => v[0]));
};
// 이미지를 수정하는 과정에서 해당 오류가 발생한 것 같습니다.
if (req.body.fullEditImagePaths) {
if (Array.isArray(req.body.fullEditImagePaths)) {
const images = await Promise.all(req.body.fullEditImagePaths.map((image) => Image.findOrCreate({
where: { src: image },
})));
await post.setImages(images);
} else {
const image = await Image.findOrCreate({
where: { src: req.body.fullEditImagePaths },
});
await post.addImages(image);
}
};
const fullEditPost = await Post.findOne({
where: { id: post.id },
include: [{
model: Image,
}, {
model: Comment,
include: [{
model: User,
attributes: ['id', 'nickname'],
}],
}, {
model: User,
attributes: ['id', 'nickname'],
}, {
model: User,
as: 'Likers',
attributes: ['id'],
}]
});
res.status(201).json(fullEditPost);
} catch (error) {
console.error(error);
next(error);
}
})
findOrCreate의 return값이 무엇인지 생각해보시면 왜 IN 부분이 저렇게 되었는지 아실 수 있습니다. 강좌에서도 map을 통해 별도로 추려냅니다.
설명해주셨는데 findOrCreate는 결과를 배열로 return하는걸 잊어버렸었네요 감사합니다.
근데 게시글 삭제 후에 DB를 확인해보니 comments, hashtags, images테이블에는 삭제된 게시글의 정보가 그대로 남아있던데 혹시 이유가 있을까요?
해당 내용의 강좌를 다시보고, 깃허브 코드도 확인해보니 게시글 삭제 라우터에서는 comments, hashtags에 대한 수정은 따로 안해주셔서요
image는 설명해주신대로 남겨서 관리되는 유지비용보다 머신러닝에 이용되어 얻을 수 있는 가치가 더 크기때문이라고 하셔서 이해는 되는데
나머지 comments, hashtags 테이블은 남겨두는 이유가 궁금해서 질문드립니다.
이것도 다 데이터라서 보관해두면 좋을 수 있습니다. 게시글도 소프트딜리트 해도 됩니다.
아니면 테이블에 on delete cascade를 설정해둬서 다 같이 지워지게 하면 됩니다.
제로초님 답변을 듣고 밤새 hydrate 액션을 조작해서 문제를 해결해보려고 했는데 제가 부족해서인지 방법을 찾기 힘들어서 재질문드릴께요 죄송합니다 ㅜㅜ
현재 게시글의 수정버튼을 누르면 게시글 정보를 가시고 posting페이지로 이동하려고합니다.
이러한 상황에서 포스팅페이지로 이동할 때 post reducer의 editPost state만 초기화되지 않을 방법이 있을까요? ㅜㅜ
rootReducer는 다음과 같이 설정되어있습니다.