-
카테고리
-
세부 분야
풀스택
-
해결 여부
미해결
Cannot read property 'Likers' of null
21.10.06 19:53 작성 조회수 137
0
고생이 많으십니다.
위 에러가 났는데 찾기가 너무 어려워서 올렸습니다.
찾는다고 찾았는데 두개의 이메일로 각각 게시글을 10개씩 썼는데도 막상 해당 post 주소로 들어가게 되면
이렇게 존재하지 않는 게시글(404)이라고 표시되고 있습니다.
그리고 콘솔창에 계속해서 Internal Server Error 500 에러 렌더링이 반복되고 있습니다.
코드는
routes/ post.js
const express = require('express')
const multer = require('multer')
const path = require('path')
const fs = require('fs')
const { Post, Image, Comment, User, Hashtag } = require('../models')
const { isLoggedIn } = require('./middlewares')
const router = express.Router()
try {
fs.accessSync('uploads')
} catch (error) {
console.log('uploads 폴더가 없으므로 생성합니다.')
fs.mkdirSync('uploads')
}
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, 'uploads')
},
filename(req, file, done) { // 제로초.png
const ext = path.extname(file.originalname) // 확장자 추출(.png)
const basename = path.basename(file.originalname, ext) // 제로초
done(null, basename + '_' + new Date().getTime() + ext) // 제로초15134314.png
}
}),
limits: { fileSize: 20 * 1024 * 1024 } // 20mb
})
router.post('/', isLoggedIn, upload.none(), async (req, res) => { // POST /post
try {
const hashtags = req.body.content.match(/#[^\s#]+/g)
const post = await Post.create({
content: req.body.content,
UserId: req.user.id
})
if (hashtags) {
const result = await Promise.all(hashtags.map((tag) => Hashtag.findOrCreate({ where: { name: tag.slice(1).toLowerCase() } }))) // [[노드, true], [리액트, true]]
await post.addHashtags(result.map((v) => v[0]))
}
if (req.body.image) {
if (Array.isArray(req.body.image)) { // 이미지를 여러개 올리면 image: [제로초.png, 부기초.png]
const images = await Promise.all(req.body.image.map((image) => Image.create({ src: image })))
await post.addImages(images)
} else { // 이미지를 하나만 올리면 image: 제로초.png
const image = await Image.create({ src: req.body.image })
await post.addImages(image)
}
}
const fullPost = 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(fullPost)
} catch (error) {
console.error(error)
next(error)
}
})
router.post('/images', isLoggedIn, upload.array('image'), (req, res, next) => { // POST /post/images
console.log(req.files)
res.json(req.files.map((v) => v.filename))
})
router.post('/:postId/comment', isLoggedIn, async (req, res, next) => { // POST /post/1/comment
try {
const post = await Post.findOne({
where: { id: req.params.postId }
})
if (!post) {
return res.status(403).send('존재하지 않는 게시글입니다.')
}
const comment = await Comment.create({
content: req.body.content,
PostId: parseInt(req.params.postId, 10),
UserId: req.user.id
})
const fullComment = await Comment.findOne({
where: { id: comment.id },
include: [{
model: User,
attributes: ['id', 'nickname']
}]
})
res.status(201).json(fullComment)
} catch (error) {
console.error(error)
next(error)
}
})
router.get('/:postId', async (req, res, next) => { // GET /post/1
try {
const post = await Post.findOne({
where: { id: req.params.postId }
})
if (!post) {
return res.status(404).send('존재하지 않는 게시글입니다.')
}
const fullPost = await Post.findOne({
where: { id: post.id },
include: [{
model: Post,
as: 'Retweet',
include: [{
model: User,
attributes: ['id', 'nickname']
}]
}, {
model: User,
attributes: ['id', 'nickname']
}, {
model: Image
}, {
model: Comment,
include: [{
model: User,
attributes: ['id', 'nickname']
}]
}, {
model: User,
as: 'Likers',
attributes: ['id']
}, {
model: Post,
as: 'Retweet',
include: [{
model: User,
attributes: ['id', 'nickname']
}, {
model: Image
}]
}]
})
res.status(200).json(fullPost)
} catch (error) {
console.error(error)
next(error)
}
})
router.post('/:postId/retweet', isLoggedIn, async (req, res, next) => { // POST /post/1/retweet
try {
const post = await Post.findOne({
where: { id: req.params.postId },
include: [{
model: Post,
as: 'Retweet'
}]
})
if (!post) {
return res.status(403).send('존재하지 않는 게시글입니다.')
}
if (req.user.id === post.UserId || (post.Retweet && post.Retweet.UserId === req.user.id)) {
return res.status(403).send('자신의 글은 리트윗할 수 없습니다.')
}
const retweetTargetId = post.RetweetId || post.id
const exPost = await Post.findOne({
where: {
UserId: req.user.id,
RetweetId: retweetTargetId
}
})
if (exPost) {
return res.status(403).send('이미 리트윗했습니다.')
}
const retweet = await Post.create({
UserId: req.user.id,
RetweetId: retweetTargetId,
content: 'retweet'
})
const retweetWithPrevPost = await Post.findOne({
where: { id: retweet.id },
include: [{
model: Post,
as: 'Retweet',
include: [{
model: User,
attributes: ['id', 'nickname']
}]
}, {
model: User,
attributes: ['id', 'nickname']
}, {
model: Image
}, {
model: Comment,
include: [{
model: User,
attributes: ['id', 'nickname']
}]
}, {
model: User,
as: 'Likers',
attributes: ['id']
}, {
model: Post,
as: 'Retweet',
include: [{
model: User,
attributes: ['id', 'nickname']
}, {
model: Image
}]
}]
})
res.status(201).json(retweetWithPrevPost)
} catch (error) {
console.error(error)
next(error)
}
})
router.patch('/:postId/like', isLoggedIn, async (req, res, next) => { // PATCH /post/1/like
try {
const post = await Post.findOne({ where: { id: req.params.postId }})
if (!post) {
return res.status(403).send('게시글이 존재하지 않습니다.')
}
await post.addLikers(req.user.id)
res.json({ PostId: post.id, UserId: req.user.id })
} catch (error) {
console.error(error)
next(error)
}
})
router.delete('/:postId/like', isLoggedIn, async (req, res, next) => { // DELETE /post/1/like
try {
const post = await Post.findOne({ where: { id: req.params.postId }})
if (!post) {
return res.status(403).send('게시글이 존재하지 않습니다.')
}
await post.removeLikers(req.user.id)
res.json({ PostId: post.id, UserId: req.user.id })
} catch (error) {
console.error(error)
next(error)
}
})
router.delete('/:postId', isLoggedIn, async (req, res, next) => { // DELETE /post/10
try {
await Post.destroy({
where: {
id: req.params.postId,
UserId: req.user.id
}
})
res.json({ PostId: parseInt(req.params.postId, 10) })
} catch (error) {
console.error(error)
next(error)
}
})
module.exports = router
reducers/ post.js
import produce from 'immer'
export const initialState = {
mainPosts: [],
singlePost: null,
imagePaths: [],
hasMorePosts: true,
likePostLoading: false,
likePostDone: false,
likePostError: null,
unlikePostLoading: false,
unlikePostDone: false,
unlikePostError: null,
loadPostLoading: false,
loadPostDone: false,
loadPostError: null,
loadPostsLoading: false,
loadPostsDone: false,
loadPostsError: null,
addPostLoading: false,
addPostDone: false,
addPostError: null,
removePostLoading: false,
removePostDone: false,
removePostError: null,
addCommentLoading: false,
addCommentDone: false,
addCommentError: null,
uploadImagesLoading: false,
uploadImagesDone: false,
uploadImagesError: null,
retweetLoading: false,
retweetDone: false,
retweetError: null
}
export const UPLOAD_IMAGES_REQUEST = 'UPLOAD_IMAGES_REQUEST'
export const UPLOAD_IMAGES_SUCCESS = 'UPLOAD_IMAGES_SUCCESS'
export const UPLOAD_IMAGES_FAILURE = 'UPLOAD_IMAGES_FAILURE'
export const LIKE_POST_REQUEST = 'LIKE_POST_REQUEST'
export const LIKE_POST_SUCCESS = 'LIKE_POST_SUCCESS'
export const LIKE_POST_FAILURE = 'LIKE_POST_FAILURE'
export const UNLIKE_POST_REQUEST = 'UNLIKE_POST_REQUEST'
export const UNLIKE_POST_SUCCESS = 'UNLIKE_POST_SUCCESS'
export const UNLIKE_POST_FAILURE = 'UNLIKE_POST_FAILURE'
export const LOAD_POSTS_REQUEST = 'LOAD_POSTS_REQUEST'
export const LOAD_POSTS_SUCCESS = 'LOAD_POSTS_SUCCESS'
export const LOAD_POSTS_FAILURE = 'LOAD_POSTS_FAILURE'
export const LOAD_POST_REQUEST = 'LOAD_POST_REQUEST'
export const LOAD_POST_SUCCESS = 'LOAD_POST_SUCCESS'
export const LOAD_POST_FAILURE = 'LOAD_POST_FAILURE'
export const ADD_POST_REQUEST = 'ADD_POST_REQUEST'
export const ADD_POST_SUCCESS = 'ADD_POST_SUCCESS'
export const ADD_POST_FAILURE = 'ADD_POST_FAILURE'
export const REMOVE_POST_REQUEST = 'REMOVE_POST_REQUEST'
export const REMOVE_POST_SUCCESS = 'REMOVE_POST_SUCCESS'
export const REMOVE_POST_FAILURE = 'REMOVE_POST_FAILURE'
export const ADD_COMMENT_REQUEST = 'ADD_COMMENT_REQUEST'
export const ADD_COMMENT_SUCCESS = 'ADD_COMMENT_SUCCESS'
export const ADD_COMMENT_FAILURE = 'ADD_COMMENT_FAILURE'
export const RETWEET_REQUEST = 'RETWEET_REQUEST'
export const RETWEET_SUCCESS = 'RETWEET_SUCCESS'
export const RETWEET_FAILURE = 'RETWEET_FAILURE'
export const REMOVE_IMAGE = 'REMOVE_IMAGE'
export const addPost = (data) => ({
type: ADD_POST_REQUEST,
data
})
export const addComment = (data) => ({
type: ADD_COMMENT_REQUEST,
data
})
const reducer = (state = initialState, action) => {
return produce(state, (draft) => {
switch (action.type) {
case REMOVE_IMAGE:
draft.imagePaths = draft.imagePaths.filter((v, i) => i !== action.data)
break
case RETWEET_REQUEST:
draft.retweetLoading = true
draft.retweetDone = false
draft.retweetError = null
break
case RETWEET_SUCCESS: {
draft.retweetLoading = false
draft.retweetDone = true
draft.mainPosts.unshift(action.data)
break
}
case RETWEET_FAILURE:
draft.retweetLoading = false
draft.retweetError = action.error
break
case UPLOAD_IMAGES_REQUEST:
draft.uploadImagesLoading = true
draft.uploadImagesDone = false
draft.uploadImagesError = null
break
case UPLOAD_IMAGES_SUCCESS: {
draft.imagePaths = action.data
draft.uploadImagesLoading = false
draft.uploadImagesDone = true
break
}
case UPLOAD_IMAGES_FAILURE:
draft.uploadImagesLoading = false
draft.uploadImagesError = action.error
break
case LIKE_POST_REQUEST:
draft.likePostLoading = true
draft.likePostDone = false
draft.likePostError = null
break
case LIKE_POST_SUCCESS: {
const post = draft.mainPosts.find((v) => v.id === action.data.PostId)
post.Likers.push({ id: action.data.UserId })
draft.likePostLoading = false
draft.likePostDone = true
break
}
case LIKE_POST_FAILURE:
draft.likePostLoading = false
draft.likePostError = action.error
break
case UNLIKE_POST_REQUEST:
draft.unlikePostLoading = true
draft.unlikePostDone = false
draft.unlikePostError = null
break
case UNLIKE_POST_SUCCESS: {
const post = draft.mainPosts.find((v) => v.id === action.data.PostId)
post.Likers = post.Likers.filter((v) => v.id !== action.data.UserId)
draft.likePostLoading = false
draft.likePostDone = true
break
}
case UNLIKE_POST_FAILURE:
draft.unlikePostLoading = false
draft.unlikePostError = action.error
break
case LOAD_POST_REQUEST:
draft.loadPostLoading = true
draft.loadPostDone = false
draft.loadPostError = null
break
case LOAD_POST_SUCCESS:
draft.singlePost = action.data
draft.loadPostLoading = false
draft.loadPostDone = true
break
case LOAD_POST_FAILURE:
draft.loadPostLoading = false
draft.loadPostError = action.error
break
case LOAD_POSTS_REQUEST:
draft.loadPostsLoading = true
draft.loadPostsDone = false
draft.loadPostsError = null
break
case LOAD_POSTS_SUCCESS:
draft.mainPosts = draft.mainPosts.concat(action.data)
draft.loadPostsLoading = false
draft.loadPostsDone = true
draft.hasMorePosts = action.data.length === 10
break
case LOAD_POSTS_FAILURE:
draft.loadPostsLoading = false
draft.loadPostsError = action.error
break
case ADD_POST_REQUEST:
draft.addPostLoading = true
draft.addPostDone = false
draft.addPostError = null
break
case ADD_POST_SUCCESS:
draft.mainPosts.unshift(action.data)
draft.addPostLoading = false
draft.addPostDone = true
draft.imagePaths = []
break
case ADD_POST_FAILURE:
draft.addPostLoading = false
draft.addPostError = action.error
break
case REMOVE_POST_REQUEST:
draft.removePostLoading = true
draft.removePostDone = false
draft.removePostError = null
break
case REMOVE_POST_SUCCESS:
draft.removePostLoading = false
draft.removePostDone = true
draft.mainPosts = draft.mainPosts.filter((v) => v.id !== action.data.PostId)
break
case REMOVE_POST_FAILURE:
draft.removePostLoading = false
draft.removePostError = action.error
break
case ADD_COMMENT_REQUEST:
draft.addCommentLoading = true
draft.addCommentDone = false
draft.addCommentError = null
break
case ADD_COMMENT_SUCCESS: {
const post = draft.mainPosts.find((v) => v.id === action.data.PostId)
post.Comments.unshift(action.data)
draft.addCommentLoading = false
draft.addCommentDone = true
break
}
case ADD_COMMENT_FAILURE:
draft.addCommentLoading = false
draft.addCommentError = action.error
break
default:
break
}
})
}
export default reducer
sagas/ post.js
import axios from 'axios'
import { all, delay, put, takeLatest, fork, throttle, call } from "redux-saga/effects";
import shortId from 'shortid';
import {
ADD_POST_FAILURE, ADD_POST_SUCCESS, ADD_COMMENT_SUCCESS,
ADD_COMMENT_FAILURE, ADD_POST_REQUEST, ADD_COMMENT_REQUEST,
REMOVE_POST_FAILURE, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS,
LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE,
LIKE_POST_REQUEST, UNLIKE_POST_REQUEST, LIKE_POST_SUCCESS,
LIKE_POST_FAILURE, UNLIKE_POST_SUCCESS, UNLIKE_POST_FAILURE,
UPLOAD_IMAGES_REQUEST, UPLOAD_IMAGES_SUCCESS, UPLOAD_IMAGES_FAILURE,
RETWEET_REQUEST, RETWEET_SUCCESS, RETWEET_FAILURE,
LOAD_POST_REQUEST, LOAD_POST_SUCCESS, LOAD_POST_FAILURE
} from '../reducers/post'
import { ADD_POST_TO_ME, REMOVE_POST_OF_ME } from '../reducers/user';
function addPostAPI(data) {
return axios.post('/post', data)
}
function* addPost(action) {
try {
const result = yield call(addPostAPI, action.data)
const id = shortId.generate()
yield put({
type: ADD_POST_SUCCESS,
data: result.data
})
yield put({
type: ADD_POST_TO_ME,
data: result.data.id
})
} catch (err) {
yield put({
type: ADD_POST_FAILURE,
error: err.response.data
})
}
}
function retweetAPI(data) {
return axios.post(`/post/${data}/retweet`, data)
}
function* retweet(action) {
try {
const result = yield call(retweetAPI, action.data)
yield put({
type: RETWEET_SUCCESS,
data: result.data
})
} catch (err) {
yield put({
type: RETWEET_FAILURE,
error: err.response.data
})
}
}
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) {
yield put({
type: UPLOAD_IMAGES_FAILURE,
error: err.response.data
})
}
}
function likePostAPI(data) {
return axios.patch(`/post/${data}/like`)
}
function* likePost(action) {
try {
const result = yield call(likePostAPI, action.data)
yield put({
type: LIKE_POST_SUCCESS,
data: result.data
})
} catch (err) {
yield put({
type: LIKE_POST_FAILURE,
error: err.response.data
})
}
}
function unlikePostAPI(data) {
return axios.delete(`/post/${data}/like`)
}
function* unlikePost(action) {
try {
const result = yield call(unlikePostAPI, action.data)
yield put({
type: UNLIKE_POST_SUCCESS,
data: result.data
})
} catch (err) {
yield put({
type: UNLIKE_POST_FAILURE,
error: err.response.data
})
}
}
function loadPostsAPI(lastId) {
return axios.get(`/posts?lastId=${lastId || 0}`)
}
function* loadPosts(action) {
try {
const result = yield call(loadPostsAPI, action.lastId)
yield put({
type: LOAD_POSTS_SUCCESS,
data: result.data
})
} catch (err) {
yield put({
type: LOAD_POSTS_FAILURE,
error: err.response.data
})
}
}
function loadPostAPI(data) {
return axios.get(`/post/${data}`)
}
function* loadPost(action) {
try {
const result = yield call(loadPostAPI, action.data)
yield put({
type: LOAD_POST_SUCCESS,
data: result.data
})
} catch (err) {
yield put({
type: LOAD_POST_FAILURE,
error: err.response.data
})
}
}
function removePostAPI(data) {
return axios.delete(`/post/${data}`)
}
function* removePost(action) {
try {
const result = yield call(removePostAPI, action.data)
yield put({
type: REMOVE_POST_SUCCESS,
data: result.data
})
yield put({
type: REMOVE_POST_OF_ME,
data: action.data
})
} catch (err) {
yield put({
type: REMOVE_POST_FAILURE,
error: err.response.data
})
}
}
function addCommentAPI(data) {
return axios.post(`/post/${data.postId}/comment`, data) // POST /post/1/comment
}
function* addComment(action) {
try {
const result = yield call(addCommentAPI, action.data)
yield put({
type: ADD_COMMENT_SUCCESS,
data: result.data
})
} catch (err) {
console.error(error)
yield put({
type: ADD_COMMENT_FAILURE,
error: err.response.data
})
}
}
function* watchRetweet() {
yield takeLatest(RETWEET_REQUEST, retweet)
}
function* watchUploadImages() {
yield takeLatest(UPLOAD_IMAGES_REQUEST, uploadImages)
}
function* watchLikePost() {
yield takeLatest(LIKE_POST_REQUEST, likePost)
}
function* watchUnlikePost() {
yield takeLatest(UNLIKE_POST_REQUEST, unlikePost)
}
function* watchAddPost() {
yield takeLatest(ADD_POST_REQUEST, addPost)
}
function* watchLoadPosts() {
yield throttle(5000, LOAD_POSTS_REQUEST, loadPosts)
}
function* watchLoadPost() {
yield takeLatest(LOAD_POST_REQUEST, loadPost)
}
function* watchRemovePost() {
yield takeLatest(REMOVE_POST_REQUEST, removePost)
}
function* watchAddComment() {
yield takeLatest(ADD_COMMENT_REQUEST, addComment)
}
export default function* postSaga() {
yield all([
fork(watchRetweet),
fork(watchUploadImages),
fork(watchLikePost),
fork(watchUnlikePost),
fork(watchAddPost),
fork(watchLoadPosts),
fork(watchLoadPost),
fork(watchRemovePost),
fork(watchAddComment)
])
}
pages/post/ [id].js
import axios from "axios"
import { useRouter } from "next/router"
import { useSelector } from "react-redux"
import { END } from 'redux-saga'
import AppLayout from "../../components/AppLayout"
import PostCard from "../../components/PostCard"
import { LOAD_POST_REQUEST } from "../../reducers/post"
import { LOAD_MY_INFO_REQUEST } from "../../reducers/user"
import wrapper from "../../store/configureStore"
const Post = () => {
const router = useRouter()
const { id } = router.query
const { singlePost } = useSelector((state) => state.post)
return (
<AppLayout>
<PostCard post={singlePost} />
</AppLayout>
)
}
export const getServerSideProps = wrapper.getServerSideProps(async (context) => {
const cookie = context.req ? context.req.headers.cookie : '';
console.log(context)
axios.defaults.headers.Cookie = '';
if (context.req && cookie) {
axios.defaults.headers.Cookie = cookie
}
context.store.dispatch({
type: LOAD_MY_INFO_REQUEST
})
context.store.dispatch({
type: LOAD_POST_REQUEST,
data: context.params.id
})
context.store.dispatch(END)
await context.store.sagaTask.toPromise()
})
export default Post
입니다
답변을 작성해보세요.
0
거휘
질문자2021.10.06
아 말씀해주신 내용 토대로 확인하니 완전히 제가 착각하고 있었습니다. 해결은 했습니다만
그동안 강의들을 보면서 에러가 발생할때마다 경우에따라 게시글 db를 전체 삭제한경우가 있었는데
저는 그동안 전체 삭제후 id가 다시 1부터 시작하는줄 알았습니다.
그런데 워크벤치를 통해 확인해보니 db에서 삭제한 게시글들의 id가 고유하게 남아있는지 그 뒤로 계속해서 추가된걸 모르고 있었습니다. 결국 새로 생성한 게시글 id로 post주소에 입력하니 정상적으로 동작되는것으로 확인됐습니다. 소중한 시간내주셔서 너무 감사합니다.
혹시 게시글을 전체삭제한후 id를 1부터 다시 생성되게 할 방법은 없을까요?
조현영
지식공유자2021.10.06
auto_increment를 1로 초기화해야하는데 sql문을 사용해야 합니다. 그런데 id에 너무 신경쓰실필요는 없습니다. id가 연속적이면 예측이 돼서 문제가 되는 경우도 있습니다.
답변 1