inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

[리뉴얼] React로 NodeBird SNS 만들기

다이나믹 라우터 애러 도와주시면 감사하겠습니다.

해결된 질문

298

uphoon

작성한 질문수 26

0

 1 of 1 unhandled error

Server Error

TypeError: Cannot read properties of null (reading 'Likers')

This error happened while generating the page. Any console logs will be displayed in the terminal window.

Source

components\PostCard.js (18:23) @ PostCard


  16 |     const dispatch = useDispatch();
  17 |     const id = useSelector((state) => state.user.me?.id)
> 18 |     const liked = post.Likers.find((item) => item.id === id)
     |                       ^
  19 | 
  20 |     const onToggleComment = useCallback(() => {
  21 |         setCommentFormOpened((prev) => !prev)
Call Stack
Function.getInitialProps

pages\_document.tsx (91:33)

Show collapsed frames

선생님도 같은 오류 나왔었던거 있어서 그대로 오류 해결하는데도 이 오류가 안없어지네요...

//서버 메세지

GET /posts?lastId=0 200 12.677 ms - 2197
GET /post/60 200 15.146 ms - 216

서버에서는 요청 잘 받는것 같은데 Likers가 계속 비어있는거 같습니다...

 

메인 화면에서는 작동이제 다 잘되는데 다이나믹 라우트 post/ 로 넘어가기만 하면 오류 발생하네요... 하루종일 매달리고 있는데... 선생님 깃허브 코드랑 강의 코드가 달라서 조금 헷갈려서요... 깃허브 주소 남기겠습니다 한번 봐주시면 감사하겠습니다. ㅠㅠ

https://github.com/wihyanghoon/react-nodebird

// [id].js

import React from 'react';
import { useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import { END } from 'redux-saga';

import axios from 'axios';
import { LOAD_POST_REQUEST } from '../../reducers/post';
import wrapper from '../../store/configureStore';
import PostCard from '../../components/PostCard';
import AppLayout from '../../components/AppLayout';
import { LOAD_MYINFO_REQUEST } from '../../reducers/user';

const Post = () => {
    const { singlePost } = useSelector((state) => state.post);
    const router = useRouter();
    const { id } = router.query;

    // if (router.isFallback) {
    //   return <div>Loading...</div>
    // }

    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_MYINFO_REQUEST,
    });
    context.store.dispatch({
        type: LOAD_POST_REQUEST,
        data: context.params.id,
    });
    context.store.dispatch(END);
    await context.store.sagaTask.toPromise();
});

export default Post;
import { delay, all, fork, takeLatest, put, throttle, call } from "redux-saga/effects";
import {
    ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE,
    REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE,
    ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE,
    LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE,
    LIKE_POST_REQUEST, LIKE_POST_SUCCESS, LIKE_POST_FAILURE,
    UNLIKE_POST_REQUEST, 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_TO_ME } from "../reducers/user";
import axios from "axios";
import shortId from 'shortid';

function addPostAPI(data) {
    return axios.post('/post', data);
}

function* addPost(action) {
    try {
        const result = yield call(addPostAPI, action.data);
        yield console.log(result)
        yield put({
            type: ADD_POST_SUCCESS,
            data: result.data,
        });
        yield put({
            type: ADD_POST_TO_ME,
            data: result.data.id,
        });
    } catch (err) {
        console.error(err);
        yield put({
            type: ADD_POST_FAILURE,
            error: err.response.data,
        });
    }
}

function removePostAPI(data) {
    return axios.delete(`/post/${data}`)
}

function* removePost(action) {
    console.log(action.data)
    try {
        const result = yield call(removePostAPI, action.data)
        yield console.log(typeof result.data.PostId)
        yield put({
            type: REMOVE_POST_SUCCESS,
            data: result.data
        })
        yield put({
            type: REMOVE_POST_TO_ME,
            data: result.data.PostId
        })
    } catch (err) {
        yield put({
            type: REMOVE_POST_FAILURE,
            error: err.response.data
        });
    }
}

function addCommentAPI(data) {
    return axios.post(`/post/${data.postId}/comment`, data)
}

function* addComment(action) {
    try {
        const result = yield call(addCommentAPI, action.data)
        yield put({
            type: ADD_COMMENT_SUCCESS,
            data: result.data
        });
    } catch (err) {
        yield put({
            type: ADD_COMMENT_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) {
        console.error(err);
        yield put({
            type: LOAD_POSTS_FAILURE,
            data: 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) {
        console.error(err);
        yield put({
            type: LOAD_POST_FAILURE,
            error: err.response.data,
        });
    }
}

function likePostAPI(data) {
    return axios.patch(`/post/${data}/like `, data);
}

function* likePost(action) {
    try {
        const result = yield call(likePostAPI, action.data);
        yield put({
            type: LIKE_POST_SUCCESS,
            data: result.data,
        });
    } catch (err) {
        console.error(err);
        yield put({
            type: LIKE_POST_FAILURE,
            data: 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) {
        console.error(err);
        yield put({
            type: UNLIKE_POST_FAILURE,
            data: 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) {
        console.error(err);
        yield put({
            type: UPLOAD_IMAGES_FAILURE,
            data: err.response.data,
        });
    }
}

function retweetApi(data){
    return axios.post(`/post/${data}/retweet`);
}

function* retweet(action) {
    try {
        const result = yield call(retweetApi, action.data);
        yield put({
            type: RETWEET_SUCCESS,
            data: result.data,
        });
    } catch (err) {
        console.error(err);
        yield put({
            type: RETWEET_FAILURE,
            err: err.response.data,
        });
    }
}

function* watchLoadPosts() {
    yield throttle(5000, LOAD_POSTS_REQUEST, loadPosts);
}

function* watchLoadPost() {
    yield takeLatest(LOAD_POST_REQUEST, loadPost);
}

function* watchAddPost() {
    yield takeLatest(ADD_POST_REQUEST, addPost)
}

function* watchRemovePost() {
    yield takeLatest(REMOVE_POST_REQUEST, removePost)
}

function* watchCommentPost() {
    yield takeLatest(ADD_COMMENT_REQUEST, addComment)
}

function* watchLikePost() {
    yield takeLatest(LIKE_POST_REQUEST, likePost)
}

function* watchUnLiketPost() {
    yield takeLatest(UNLIKE_POST_REQUEST, UnLikePost)
}

function* watchUpLoadImages() {
    yield takeLatest(UPLOAD_IMAGES_REQUEST, upLoadImages)
}

function* watchRetweet() {
    yield takeLatest(RETWEET_REQUEST, retweet)
}


export default function* postSaga() {
    yield all([
        fork(watchAddPost),
        fork(watchCommentPost),
        fork(watchRemovePost),
        fork(watchLoadPosts),
        fork(watchLoadPost),
        fork(watchLikePost),
        fork(watchUnLiketPost),
        fork(watchUpLoadImages),
        fork(watchRetweet),
    ]);
}
import shortId from 'shortid';
import produce from 'immer';
import faker from 'faker';
import { LIKE_FAILURE, LIKE_REQUEST, LIKE_SUCCESS } from './user';

export const initialState = {
    mainPosts: [],
    imagePath: [],
    hasMorePosts: true,
    loadPostsLoading: false,
    loadPostsDone: false,
    loadPostsError: null,
    loadPostLoading: false,
    loadPostDone: false,
    loadPostError: null,
    likeLoading: false,
    likeDone: false,
    likeError: null,
    unLikeLoading: false,
    unLikeDone: false,
    unLikeError: null,
    addPostLoadding: false,
    addPostDone: false,
    addPostErr: null,
    removePostLoadding: false,
    removePostDone: false,
    removePostErr: null,
    addCommentLoadding: false,
    addCommentDone: false,
    addCommentErr: null,
    upLoadImagesLoadding: false,
    upLoadImagesDone: false,
    upLoadImagesErr: null,
    retweetLoadding: false,
    retweetDone: false,
    retweetErr: null,
    singlePost: null,
}


// export const getDemmuyPost = (number) => Array(number).fill().map(() => ({
//     id: shortId.generate(),
//     User: {
//         id: shortId.generate(),
//         nickname: faker.name.findName(),
//     },
//     content: faker.lorem.paragraph(),
//     Images: [{
//         src: faker.image.image(),
//     }],
//     Comments: [{
//         User: {
//             id: shortId.generate(),
//             nickname: faker.name.findName(),
//         },
//         content: faker.lorem.sentence(),
//     }],
// }))
export const REMOVE_IMAGES_SUCSESS = 'REMOVE_IMAGES_SUCSESS';

export const LOAD_POST_REQUEST = 'LOAD_POSTS_REQUEST';
export const LOAD_POST_SUCCESS = 'LOAD_POSTS_SUCCESS';
export const LOAD_POST_FAILURE = 'LOAD_POSTS_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 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 LIKE_POST_REQUEST = 'LIKE_POST_REQUEST';
export const LIKE_POST_SUCCESS = 'LIKE_POST_SUCCESS';
export const LIKE_POST_FAILURE = 'LIKE_POST_FAILURE';

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 UNLIKE_POST_REQUEST = 'UNLIKE_POST_REQUEST';
export const UNLIKE_POST_SUCCESS = 'UNLIKE_POST_SUCCESS';
export const UNLIKE_POST_FAILURE = 'UNLIKE_POST_FAILURE';

export const RETWEET_REQUEST = 'RETWEET_REQUEST'
export const RETWEET_SUCCESS = 'RETWEET_SUCCESS'
export const RETWEET_FAILURE = 'RETWEET_FAILURE'

export const addPostAction = (data) => {
    return {
        type: ADD_POST_REQUEST,
        data
    }
}

export const addCommentAction = (data) => {
    return {
        type: ADD_COMMENT_REQUEST,
        data
    }
}

const reducer = (state = initialState, action) => {
    return produce(state, (draft) => {
        switch (action.type) {
            case RETWEET_REQUEST:
                draft.retweetLoadding = true
                draft.retweetDone = false
                draft.retweetErr = null
                break;

            case RETWEET_SUCCESS:
                draft.retweetLoadding = false
                draft.retweetDone = true
                draft.mainPosts.unshift(action.data)
                break;

            case RETWEET_FAILURE:
                draft.retweetLoadding = false
                draft.retweetErr = action.err
                break;

            case REMOVE_IMAGES_SUCSESS:
                console.log(action.data)
                draft.imagePath = draft.imagePath.filter((item, index) => index !== action.data)
                break;

            case LOAD_POSTS_REQUEST:
                draft.loadPostsLoading = true;
                draft.loadPostsDone = false;
                draft.loadPostsError = null;
                break;

            case LOAD_POSTS_SUCCESS:
                draft.loadPostsLoading = false;
                draft.loadPostsDone = true;
                draft.mainPosts = draft.mainPosts.concat(action.data);
                draft.hasMorePosts = draft.mainPosts.length === 10;
                break;

            case LOAD_POSTS_FAILURE:
                draft.loadPostsLoading = false;
                draft.loadPostsError = action.error;
                break;

            case LOAD_POST_REQUEST:
                draft.loadPostLoading = true;
                draft.loadPostDone = false;
                draft.loadPostError = null;
                break;

            case LOAD_POST_SUCCESS:
                draft.loadPostLoading = false;
                draft.loadPostDone = true;
                draft.singlePost = action.data;
                break;

            case LOAD_POST_FAILURE:
                draft.loadPostLoading = false;
                draft.loadPostError = action.error;
                break;

            case ADD_POST_REQUEST:
                draft.addPostLoadding = true
                draft.addPostDone = false
                draft.addPostErr = null
                break;

            case ADD_POST_SUCCESS:
                draft.addPostLoadding = false
                draft.addPostDone = true
                draft.mainPosts.unshift(action.data)
                draft.imagePath = []
                break;

            case ADD_POST_FAILURE:
                draft.addPostLoadding = false
                draft.addPostErr = action.err
                break;

            case REMOVE_POST_REQUEST:
                draft.removePostLoadding = true
                draft.removePostDone = false
                draft.removePostErr = null
                break;

            case REMOVE_POST_SUCCESS:
                draft.removePostLoadding = false
                draft.removePostDone = true
                draft.mainPosts = state.mainPosts.filter((item) => item.id !== action.data.PostId)
                break;

            case REMOVE_POST_FAILURE:
                draft.removePostLoadding = false
                draft.removePostErr = action.err
                break;

            case ADD_COMMENT_REQUEST:
                draft.addCommentLoadding = true
                draft.addCommentDone = false
                draft.addCommentErr = null
                break;

            case ADD_COMMENT_SUCCESS:
                const post = draft.mainPosts.find((item) => { return item.id === action.data.PostId })
                post.Comments.unshift(action.data)
                draft.addCommentLoadding = false
                draft.addCommentDone = true
                break;

            case ADD_COMMENT_FAILURE:
                draft.addCommentLoadding = false
                draft.addCommentErr = action.error
                break;

            case LIKE_POST_REQUEST:
                draft.likeLoading = true
                draft.likeDone = false
                draft.likeError = null
                break;

            case LIKE_POST_SUCCESS: {
                draft.likeLoading = false
                draft.likeDone = true
                const post = draft.mainPosts.find((item) => item.id === action.data.PostId)
                post.Likers.push({ id: action.data.UserId })
                break;
            }
            case LIKE_POST_FAILURE:
                draft.unLikeLoading = false
                draft.unLikeError = true
                break;

            case UNLIKE_POST_REQUEST:
                draft.unLikeLoading = true
                draft.unLikeDone = false
                draft.unLikeError = null
                break;

            case UNLIKE_POST_SUCCESS: {
                draft.unLikeLoading = false
                draft.unLikeDone = true
                const post = draft.mainPosts.find((v) => v.id === action.data.PostId);
                post.Likers = post.Likers.filter((v) => v.id !== action.data.UserId);
                break;
            }
            case UNLIKE_POST_FAILURE:
                draft.unLikeLoading = false
                draft.unLikeDone = true
                break;

            case UPLOAD_IMAGES_REQUEST:
                draft.upLoadImagesLoadding = true
                draft.upLoadImagesDone = false
                draft.upLoadImagesErr = null
                break;

            case UPLOAD_IMAGES_SUCCESS:
                draft.upLoadImagesLoadding = true
                draft.upLoadImagesDone = false
                draft.imagePath = action.data
                break;

            case UPLOAD_IMAGES_FAILURE:
                draft.upLoadImagesLoadding = false
                draft.upLoadImagesErr = action.error
                break;
            default:
                return state
        }
    })
}

export default reducer
const express = require('express')
const multer = require('multer')
const path = require('path')
const fs = require('fs')
const { Post, Comment, Image, User, Hashtag } = require('../models')
const { isLoggedIn } = require('./middlewares')
const user = require('../models/user')

const router = express.Router();

try {
  fs.accessSync('uploads')
} catch (error) {
  console.log('폴더가 없으므로 생성합니다.')
  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); // 제로초15184712891.png
    },
  }),
  limits: { fileSize: 20 * 1024 * 1024 }, // 20MB
});

router.post('/', isLoggedIn, upload.none(), async (req, res, next) => { // 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.delete('/:PostId', isLoggedIn, async (req, res, next) => {
  try {
    await Post.destroy({
      where: {
        id: req.params.PostId,
        UserId: req.user.id
      },
    })
    res.status(201).json({ PostId: parseInt(req.params.PostId, 10) })
  } catch (error) {
    console.error(error)
    next(error)
  }
})

router.post('/:postId/comment', isLoggedIn, async (req, res) => {
  try {
    const post = await Post.findOne({
      where: { id: req.params.postId }
    })
    if (!post) {
      return res.status(403).send('존재하지 않는 게시글 입니다.')
    }
    const commnet = await Comment.create({
      content: req.body.content,
      PostId: Number(req.params.postId),
      UserId: req.user.id,
    })
    const fullComment = await Comment.findOne({
      where: { id: commnet.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) => {
  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: Image,
        }]
      }, {
        model: User,
        attributes: ['id', 'nickname']
      }, {
        model: User,
        as: 'Likers',
        attributes: ['id', 'nickname']
      }, {
        model: Image,
      }, {
        model: Comment,
        include: [{
          model: User,
          attributes: ['id', 'nickname']
        }]
      }]
    })
    res.status(200).json(fullPost)
  } catch (err) {
    console.error(err)
    next(err)
  }
});


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/like', (req, res, next) => {

})

router.post('/images', isLoggedIn, upload.array('image'), (req, res, next) => {
  res.json(req.files.map((item) => item.filename))
})

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: Image,
        }]
      }, {
        model: User,
        attributes: ['id', 'nickname'],
      }, {
        model: User, // 좋아요 누른 사람
        as: 'Likers',
        attributes: ['id'],
      }, {
        model: Image,
      }, {
        model: Comment,
        include: [{
          model: User,
          attributes: ['id', 'nickname'],
        }],
      }],
    })
    res.status(201).json(retweetWithPrevPost);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

redux react express nodejs Next.js

답변 1

0

제로초(조현영)

에러메시지를 잘 읽으셔야 합니다. Likers가 비어있는게 아닙니다. post가 null인 겁니다. post가 null이라는건 서버사이드렌더링이 안 된겁니다. 리덕스에서 hydrate 액션 시 post가 null인지 확인해보시면 되겠죠?

0

uphoon

네 선생님 hydrate 확인하고 리덕스 본건데

image

index.js 즉 localhost:3000 에서는 잘나오고 이제

import React from 'react';
import { useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import { END } from 'redux-saga';

import axios from 'axios';
import { LOAD_POST_REQUEST, LOAD_POSTS_REQUEST } from '../../reducers/post';
import wrapper from '../../store/configureStore';
import PostCard from '../../components/PostCard';
import AppLayout from '../../components/AppLayout';
import { LOAD_MYINFO_REQUEST } from '../../reducers/user';

const Post = () => {
    const { singlePost } = useSelector((state) => state.post);
    const router = useRouter();
    const { id } = router.query;

    // if (router.isFallback) {
    //   return <div>Loading...</div>
    // }

    return (
        <AppLayout>
            <PostCard post={singlePost} />
        </AppLayout>
    );
};

export const getServerSideProps = wrapper.getServerSideProps(async (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_MYINFO_REQUEST,
    });
    context.store.dispatch({
        type: LOAD_POST_REQUEST,
    });
    context.store.dispatch(END);
    await context.store.sagaTask.toPromise();
})

export default Post;

여기가 다이나믹 라우터 쪽 로직인데 제가 밑에 ServerSideProps를 잘 못 만든 걸까요?

0

제로초(조현영)

아뇨 mainPosts가 아니라 post를 보셔야죠. state.post니까요

0

uphoon

네 선생님 그러니까 지금

imageinit 일때

imagehydrate 일때 state.post안에 값들 다들어오는데요? 제가 질문을 잘못이해 했을까요?

0

제로초(조현영)

post 페이지서 렌더링되는 주체는 mainPosts가 아니라 singlePost입니다. singlePost가 null이네요

0

uphoon

어 선생님 제가 그래서 위에 말씀드린게 인덱스에서는 나오고 post페이지에서는 아예 리덕스 저거 추적조차 못해서 계속 말씀드리는거여서...

0

제로초(조현영)

loadPost saga를 잘 봐보시고 dispatch부분도 잘 봐보세요. 틀린 부분이있습니다.

0

uphoon

네 찾아보겠습니다!

0

uphoon

선생님 제가 틀린게 있나요? loadPost saga 및 디스패치 계속 확인해도 ....

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_MYINFO_REQUEST,
    });
    context.store.dispatch({
        type: LOAD_POST_REQUEST,
        data: context.params.id,
    });
    context.store.dispatch(END);
    await context.store.sagaTask.toPromise();
    return { props: {} };
});
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) {
        console.error(err);
        yield put({
            type: LOAD_POST_FAILURE,
            error: err.response.data,
        });
    }
}
function* watchLoadPost() {
    yield takeLatest(LOAD_POST_REQUEST, loadPost);
}
export default function* postSaga() {
    yield all([
        fork(watchAddPost),
        fork(watchCommentPost),
        fork(watchRemovePost),
        fork(watchLoadPosts),
        fork(watchLoadPost),
        fork(watchLikePost),
        fork(watchUnLiketPost),
        fork(watchUpLoadImages),
        fork(watchRetweet),
    ]);
}

0

제로초(조현영)

context.params.id를 추가하신 건 맞고요. 네트워크탭이랑 서버쪽 콘솔에서 /post/아이디 요청이 제대로 가는지 봐야합니다.

0

uphoon

//서버 메세지

GET /posts?lastId=0 200 12.677 ms - 2197
GET /post/60 200 15.146 ms - 216

서버쪽에서 응답은 잘받는것같습니다 ㅠㅠ 그냥 밀고 다시 해야할까요…

0

제로초(조현영)

음.. 프론트서버쪽에서 콘솔에 리덕스 스테이트들이 ssr되는 과정이 나올텐데요. 1 2 3 4모두 singlePost가 null인가요? *loadPost에서도 console.log(result)도 해보세요.

1

uphoon

선생님 오류 찾았습니다...

export const LOAD_POST_REQUEST = 'LOAD_POSTS_REQUEST';
export const LOAD_POST_SUCCESS = 'LOAD_POSTS_SUCCESS';
export const LOAD_POST_FAILURE = 'LOAD_POSTS_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';

action 함수가 함수이름만 다르고... string값은 같아서 오류났던거였습니다..... 드디어 해결했습니다 ㅠㅠ

넥스트 버젼 질문

0

90

2

로그인시 401 Unauthorized 오류가 뜹니다

0

104

1

무한 스크롤 중 스크롤 튐 현상

0

198

1

특정 페이지 접근을 막고 싶을 때

0

117

2

createGlobalStyle의 위치와 영향범위

0

103

2

인라인 스타일 리렌더링 관련

0

98

2

vsc 에서 npm init 설치시 오류

0

159

2

nextjs 15버전 사용 가능할까요?

0

166

1

화면 새로고침 문의

0

129

1

RTK에서 draft, state 차이가 있나요?

0

164

2

Next 14 사용해도 될까요?

0

455

1

next, node 버전 / 폴더 구조 질문 드립니다.

0

359

1

url 오류 질문있습니다

0

218

1

ssh xxxxx로 우분투에 들어가려니까 port 22: Connection timed out

0

391

1

sudo certbot --nginx 에러

0

1295

2

Minified React error 콘솔에러 (hydrate)

0

481

1

카카오 공유했을 때 이전에 작성했던 글이 나오는 버그

0

257

1

프론트서버 배포 후 EADDRINUSE에러 발생

0

341

1

npm run build 에러

0

526

1

front 서버 npm run build 중에 발생한 에러들

0

399

1

서버 실행하고 브라우저로 들어갔을때 404에러

0

351

2

css 서버사이드 랜더링이 적용되지 않아서 문의 드립니다.

0

291

1

팔로워 3명씩 불러오고 데이터 합쳐주는걸로 바꾸고 서버요청을 무한으로하고있습니다.

0

251

2

해시태그 검색에서 throttle에 관해 질문있습니다.

0

207

1