강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

br님의 프로필 이미지
br

작성한 질문수

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

오류 어디 봐야하나요??

해결된 질문

작성

·

201

0

index.js:43 {mainPosts.map((post) => ( 이부분입니다

_App.js:14 <Component /> 여기인데 원인을 모르겠네요

 

pages/_App.js

import React from 'react';
import 'antd/dist/antd.css';
import Head from 'next/head';
import PropTypes from 'prop-types';
import wrapper from '../store/configureStore';
const NodeBird = ({Component}) => (
        <>
            <Head>
                <meta charSet='utf-8' />
                <title>NodeBird</title>
            </Head>
            <Component />
        </>
);

NodeBird.propTypes = {
    Component: PropTypes.elementType.isRequired
};

export default wrapper.withRedux(NodeBird);

pages/index

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppLayout from '../components/AppLayout';
import PostForm from '../components/PostForm';
import PostCard from '../components/PostCard';
import { LOAD_POSTS_REQUEST } from '../reducers/post';

const Home = () => {
    const dispatch = useDispatch();
    const {me} = useSelector((state) => state.user);
    const {mainPosts, hasMorePosts, loadPostsLoading} = useSelector((state) => state.post);

    useEffect(() => {
        dispatch({
            type: LOAD_POSTS_REQUEST
        });
    }, []);

    useEffect(() => {
        function onScroll() {
            // scrollY: 얼마나 내렸는지, clientHeight: 화면에 보이는 길이, scrollHeight: 총 길이
            console.log(window.scrollY, 
                        document.documentElement.clientHeight, 
                        document.documentElement.scrollHeight);
            if(window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) {
                if(hasMorePosts && !loadPostsLoading) {
                    dispatch({
                        type: LOAD_POSTS_REQUEST,
                        data: mainPosts[mainPosts.length - 1].id,
                    });
                }
            }
        }
        window.addEventListener('scroll', onScroll);
        return() => {
            window.removeEventListener('scroll', onScroll);
        };
    }, [mainPosts, hasMorePosts, loadPostsLoading]);

    return (
        <AppLayout>
            {me && <PostForm />}
            {mainPosts.map((post) => ( 
                <PostCard key={post.id} post={post} />
                ))}
        </AppLayout>
    );
};

export default Home;

 

github 코드랑 강의코드랑 다른거 같아요..

github 보면서 index쪽이랑 _app쪽 코드 바꿔봤는데 안되네요

어디쪽 문제일까요 ??

 

그리고 faker 이렇게 뜨는데 안되는거 같아요

reducers/post.js

faker 강의 들을때 이부분 에러 나서 주석처리하니까 정상작동 됐어요

faker 4버전,5버전 둘다 해봐도 안됐어요 지금은 4버전 설치되있어요

근데 첫번째 에러 해결해야 faker쪽 에러 확인 가능할듯 합니다

답변 1

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

mainPosts가 배열이 아니라서 문제인 것입니다. mainPosts를 건드는 쪽을 집중적으로 보셔야 하고요. 리듀서 쪽일 겁니다. 그리고 faker 관련 이슈일 수도 있습니다.

br님의 프로필 이미지
br
질문자

reducers쪽 코드 보면서 비교해도 똑같은거 같아요ㅠ

github에 reducers폴더 복붙해도 똑같은 에러 뜨고..

여기가 아닌거 같아요..

 

 

index.js

import { HYDRATE } from 'next-redux-wrapper';
import { combineReducers } from "redux";
import user from './user';
import post from './post';

// 다른 State
// (이전상태, 액션) => 다음상태
const rootReducer =  combineReducers({
    index: (state = {}, action) => {
    switch(action.type) {
        case HYDRATE:
            console.log('HYDRATE', action);
            return {
                ...state,
                ...action.payload
            };
        default:
            return state;
        }
    },
    user,
    post,
});

export default rootReducer;

 

post.js

import shortId from "shortid";
import produce from "immer";
// import faker from 'faker';

export const initialState = {
    mainPosts: [],
    imagePaths: [],
    hasMorePosts: true,
    loadPostsLoading: false,
    loadPostsDone: false,
    loadPostsError: null,
    addPostLoading: false,
    addPostDone: false,
    addPostError: null,
    removePostLoading: false,
    removePostDone: false,
    removePostError: null,
    addCommentLoading: false,
    addCommentDone: false,
    addCommentError: null
};

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

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 addPost = (data) => ({
    type: ADD_POST_REQUEST,
    data
});

export const addComment = (data) => ({
    type: ADD_COMMENT_REQUEST,
    data
});

const dummyPost = (data) => ({
    id: data.id,
    content: data.content,
    User: {
        id: 1,
        nickname: 'ABC'
    },
    Images: [],
    Comments: []
});

const dummyComment = (data) => ({
    id: shortId.generate(),
    content: data,
    User: {
        id: 1,
        nickname: 'ABC'
    },
});

// 이전 상태를 액션을 통해 다음 상태로 만들어내는 함수(불변성은 지키면서)
const reducer = (state = initialState, action) => produce(state, (draft) => {
        switch(action.type) {
            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 = action.data.concat(draft.mainPosts);
                draft.hasMorePosts = draft.mainPosts.length < 50;
                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.addPostLoading = false;
                draft.addPostDone = true;
                draft.mainPosts.unshift(dummyPost(action.data));
                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);
                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(dummyComment(action.data.content));
                draft.addCommentLoading = false;
                draft.addCommentDone = true;
                break;

                // const postIndex = state.mainPosts.findIndex((v) => v.id === action.data.postId);
                // const post = { ...state.mainPosts[postIndex] };
                // post.Comments = [dummyComment(action.data.content), ...post.Comments];
                // const mainPosts = [...state.mainPosts];
                // mainPosts[postIndex] = post;
                // return {
                //     ...state,
                //     addCommentLoading: false,
                //     addCommentDone: true,
                // };
            };
            case ADD_COMMENT_FAILURE:
                draft.addCommentLoading = false,
                draft.addCommentError = action.error
                break;
            default:
                break;
        }
    });

export default reducer;

 

user.js

import produce from "immer";

export const initialState = {
    followLoading: false, // 팔로우 시도중
    followDone: false,
    followError: null,
    unfollowLoading: false, // 언팔로우 시도중
    unfollowDone: false,
    unfollowError: null,
    logInLoading: false, // 로그인 시도중
    logInDone: false,
    logInError: null,
    logOutLoading: false, // 로그아웃 시도중
    logOutDone: false,
    logOutError: null,
    signUpLoading: false, // 회원가입 시도중
    signUpDone: false,
    signUpError: null,
    changeNicknameLoading: false, // 닉네임 변경 시도중
    changeNicknameDone: false,
    changeNicknameError: null,
    me: null,
    signUpData: {},
    loginData: {}
}

const dummyUser = (data) => ({
    ...data,
    nickname: 'G',
    id: 1,
    Posts: [{id: 1}],
    Followings: [{nickname: 'R'}, {nickname: 'L'}, {nickname: 'N'}],
    Followers: [{nickname: 'R'}, {nickname: 'L'}, {nickname: 'N'}],
});

export const LOG_IN_REQUEST = 'LOG_IN_REQUEST';
export const LOG_IN_SUCCESS = 'LOG_IN_SUCCESS';
export const LOG_IN_FAILURE = 'LOG_IN_FAILURE';

export const LOG_OUT_REQUEST = 'LOG_OUT_REQUEST';
export const LOG_OUT_SUCCESS = 'LOG_OUT_SUCCESS';
export const LOG_OUT_FAILURE = 'LOG_OUT_FAILURE';

export const SIGN_UP_REQUEST = 'SIGN_UP_REQUEST';
export const SIGN_UP_SUCCESS = 'SIGN_UP_SUCCESS';
export const SIGN_UP_FAILURE = 'SIGN_UP_FAILURE';

export const CHANGE_NICKNAME_REQUEST = 'CHANGE_NICKNAME_REQUEST';
export const CHANGE_NICKNAME_SUCCESS = 'CHANGE_NICKNAME_SUCCESS';
export const CHANGE_NICKNAME_FAILURE = 'CHANGE_NICKNAME_FAILURE';

export const FOLLOW_REQUEST = 'FOLLOW_REQUEST';
export const FOLLOW_SUCCESS = 'FOLLOW_SUCCESS';
export const FOLLOW_FAILURE = 'FOLLOW_FAILURE';

export const UNFOLLOW_REQUEST = 'UNFOLLOW_REQUEST';
export const UNFOLLOW_SUCCESS = 'UNFOLLOW_SUCCESS';
export const UNFOLLOW_FAILURE = 'UNFOLLOW_FAILURE';

export const ADD_POST_TO_ME = 'ADD_POST_TO_ME';
export const REMOVE_POST_OF_ME = 'REMOVE_POST_OF_ME';

export const loginRequestAction = (data) => ({
        type: LOG_IN_REQUEST,
        data
    });

export const logoutRequestAction = () => ({
        type: LOG_OUT_REQUEST
    });

    // return(생략) produce 
const reducer = (state = initialState, action) => produce(state, (draft) => {
        switch(action.type) {
            case FOLLOW_REQUEST:
                draft.followLoading = true;
                draft.followError = null;
                draft.followDone = false;
                break;
            case FOLLOW_SUCCESS:
                draft.followLoading = false;
                draft.me.Followings.push({ id: action.data });
                draft.followDone = true;
                break;
            case FOLLOW_FAILURE:
                draft.followLoading = false;
                draft.followError = action.error;
                break;
            case UNFOLLOW_REQUEST:
                draft.unfollowLoading = true;
                draft.unfollowError = null;
                draft.unfollowDone = false;
                break;
            case UNFOLLOW_SUCCESS:
                draft.unfollowLoading = false;
                draft.me.Followings = draft.me.Followings.filter((v) => v.id !== action.data);
                draft.unfollowDone = true;
                break;
            case UNFOLLOW_FAILURE:
                draft.unfollowLoading = false;
                draft.unfollowError = action.error;
                break;
            case LOG_IN_REQUEST:
                draft.logInLoading = true;
                draft.logInError = null;
                draft.logInDone = false;
                break;
            case LOG_IN_SUCCESS:
                draft.logInLoading = false;
                draft.me = dummyUser(action.data);
                draft.logInDone = true;
                break;
            case LOG_IN_FAILURE:
                draft.logInLoading = false;
                draft.logInError = action.error;
                break;
            case LOG_OUT_REQUEST:
                draft.logOutLoading = true;
                draft.logOutError = null;
                draft.logOutDone = false;
                break;
            case LOG_OUT_SUCCESS:
                draft.logOutLoading = false;
                draft.logOutDone = true;
                draft.me = null;
                break;
            case LOG_OUT_FAILURE:
                draft.logOutLoading = false;
                draft.logOutError = action.error;
                break;
            case SIGN_UP_REQUEST:
                draft.signUpLoading = true;
                draft.signUpError = null;
                draft.signUpDone = false;
                break;
            case SIGN_UP_SUCCESS:
                draft.signUpLoading = false;
                draft.signUpDone = true;
                break;
            case SIGN_UP_FAILURE:
                draft.signUpLoading = false;
                draft.signUpError = action.error
                break;
            case CHANGE_NICKNAME_REQUEST:
                draft.changeNicknameLoading = true;
                draft.changeNicknameError = null;
                draft.changeNicknameDone = false;
                break;
            case CHANGE_NICKNAME_SUCCESS:
                draft.changeNicknameLoading = false;
                draft.changeNicknameDone = true;
                break;
            case CHANGE_NICKNAME_FAILURE:
                draft.changeNicknameLoading = false;
                draft.changeNicknameError = action.error;
                break;
            case ADD_POST_TO_ME:
                draft.me.Posts.unshift({id: action.data});
                break;

                // return {
                //     ...state,
                //     me: {
                //         ...state.me,
                //         Posts: [{id: action.data}, ...state.me.Posts],
                //     },
                // };
            case REMOVE_POST_OF_ME:
                draft.me.Posts = draft.me.Posts.filter((v) => v.id !== action.data);
                break;

                // return {
                //     ...state,
                //     me: {
                //         ...state.me,
                //         Posts: state.me.Posts.filter((v) => v.id !== action.data)
                //     }
                // }
            default:
                break;
        }
    });

export default reducer;
제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

redux-devtools에서 mainPosts 값이 무엇인지 확인해보세요.

br님의 프로필 이미지
br
질문자

image

redux-devtools 이렇게 뜹니다

 

pages/index.js

image

여기 주석 처리하면 정상작동 되는데 보니까 삭제 기능이 사라져 있네요..

이 부분은 어디로 가야하나요 ㅠㅠ

saga/post.js , reducers/post.js 안에 REMOVE_POST 다 확인했는데 왜이러죠

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

redux devtools에서 action 말고 state를 보셔야죠. 거기에 mainPosts가 있으니까요.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

문제를 하나씩 해결하세요. mainPosts 문제부터 해결 후에 삭제 쪽을 보시고요. 혼자만 보지 마시고 코드를 올려주세요.

다음 영상 보시는 게 좋을 것 같습니다.

https://www.youtube.com/watch?v=JM3-QdRZv9I

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

지금 보니까 LOAD_POSTS_SUCCESS의 data가 배열이 아니라 문자열로 들어옵니다. 그래서 reducer에서도 배열이 아니라 문자열이 되는 것이고요. LOAD_POSTS_SUCCESS를 하는 곳을 봐야겠죠.

br님의 프로필 이미지
br
질문자

image

이렇게 되있어요

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

에러난 곳은 state.post인데 state.user를 보시면... 위에 제 댓글 봐주세요.

br님의 프로필 이미지
br
질문자

찾았어요

sagas/post.js 안에 loadPosts

function* loadPosts(action) {
    try {
        // const result = yield call(loadPostAPI, action.data)
        yield delay(1000);
        yield put({
            type: LOAD_POSTS_SUCCESS,
            data: generateDummyPost(10)
        });
    } catch (err) {
        yield put({
            type: LOAD_POSTS_FAILURE,
            data: err.response.data
        });
    }
}

 

reducer/post.js 안에 generateDummyPost

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

data에 다른거 들어가 있어서 고쳤는데 generateDummyPost 이거는 faker인데

faker 위에 본문 보시면 4,5버전 다 저렇게 뜨면서 안되서 그런지 무한 로딩 걸리네요...

 

image

image

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

sagas/posts.js 의 catch (err) 바로 아래에 console.error(err) 넣어 에러 확인해보세요.

br님의 프로필 이미지
br
질문자

catch (err) {
        console.error(err);
        yield put({
            type: LOAD_POSTS_FAILURE,
            data: err.response.data
        });

에러 안뜨는거 같아요

vsCode에는 에러 안뜹니다

콘솔 오류

image

br님의 프로필 이미지
br
질문자

찾아서 해결했습니다..감사합니다

faker, 삭제기능 철자 하나땜에 ㅜ 오타가 문제였네요

br님의 프로필 이미지
br

작성한 질문수

질문하기