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

정중한 전어님의 프로필 이미지
정중한 전어

작성한 질문수

React로 NodeBird SNS 만들기

특정 페이지에서 새로고침 시 LOAD_USER_REQUEST를 실행하지 않는 문제

해결된 질문

작성

·

515

0

안녕하세요! 강의 듣고 토이프로젝트 진행중인 학생입니다.

SSR을 통해 데이터를 프론트에서 미리 가져와주는 작업을 진행중입니다.

page/index.js의 경우 해당 페이지에 로그인 후 새로고침을 하면

app.js에서 계속해서 me 데이터를 가져와주어 문제가 없는데

page/user.js 페이지를 만들어서 작업중인데 해당 페이지는 링크를 타고 그대로 들어가면 문제가 없으나

새로고침 시 me데이터가 아예 사라지는 문제가 있습니다.

콘솔로그 찍어보니 LOAD_USER_REQUEST가 실행은 되는것 같은데 제대로 값을 불러내지 못하는것 같습니다. 

어느 부분이 잘못된 것인지 저 나름대로 코드를 비교해보았으나 갈피를 잡지 못하여 질문 드립니다 ㅠㅠ

덧글로 파일 코드 작성해보겠습니다! 도움 주시면 감사하겠습니다 ㅠㅠ 

답변 7

1

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

_app.js에서 이미 LOAD_USER_REQUEST를 보내는데 user.js에서 다시 또 LOAD_USER_REQUEST 하시는 이유가 있나요?

0

ㅠㅠㅠㅠ아이고.... 뭐가 잘못됐던건지 찾아냈습니다 제가 load_user_request 부분을 사가에서 takeEvery가 아닌 takeLatest로 해놨었네요ㅠㅠ 아예 뭐가 문젠지를 갈피를 못잡고 있었는데 LOAD_USER_REQUEST 반복 찝어주셔서 저부분 확인할 수 있었습니다 코드 봐주시고 도움 주셔서 감사합니다 !!

0

네! _app.js에선 모든 페이지에서 공통적으로 사용자 me를 데려오도록 하고 user페이지에서 userInfo를 데려오는데 해당 페이지에서 그 상태로 새로고침 할 시에는 user.me 데이터가 아예 null이 되어버립니다 ㅠㅠ..! userInfo는 새로고침 하더라도 잘 불러오구요..! 해당 페이지에서 덧글을 다는 기능을 위해 me가 그대로 보존되어야 하는데 새로고침 시 사라집니다 ㅜㅜ 

user페이지의 request_load_user는 제대로 작동되는데 _app.js에서 request_load_user가 제대로 동작 했음에도(user.me에 사용자 본인의 데이터를 가져오도록) 데브툴을 확인해보면 user.me에 아무것도 담기지 않는(null) 문제가 생깁니다..!

0

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

user 페이지에서는 action.me가 아니라 action.userInfo 아닌가요? 위에 리덕스 데브툴 사진 보니까 action.userInfo에 시민 잘 들어있네요.

0

_app.js에선 사용자 본인의 정보를 불러와 me에 담고 user.js에선 /user/1로 이동했을 경우 id가 1인 사용자의 정보를 불러와 userInfo에 담는 형식으로 하고 있습니다! 이 부분은 nodebird의 page/user.js를 참고하여 작성하였어요..! 혹시 둘 다 같은 reducer를 사용하는 것이 문제가 될 수도 있을까요...? nodebird에선 user페이지 접속 시 me가 제대로 불러와졌던것 같은데 제 코드는 다른 페이지 접속시엔 잘 동작하는데 user페이지만 me를 제대로 불러오지 못하네요 ㅠㅠ 

0

제로초님 계속 말을 번복해서 죄송합니다 ㅠㅠ 

계속 콘솔로그 찍어서 확인해보니.. 사용자 데이터를 잘 불러와 LOAD_USER_SUCCESS까지 닿아서

값을 바꿔준 직후에 draft.me를 찍어보면 제대로 잘 뜨는데

브라우저에서 확인하면 me  가 뜨지 않네요 ㅠㅠ

다른 곳에서 draft.me를 초기화 한 것은 없는것 같은데

문제가 뭔지를 모르겠습니다 ㅠㅠ

action.data가 draft.me로 들어가기는 하나 브라우저에선 me가 null로 뜹니다!

index.js와 그 외 다른 페이지에선 me를 제대로 넣어주는데 user.js 페이지에서만 이런 문제가 나타납니다 ㅜㅜ

<user reducer 전체> (현재 LOAD_USER_SUCCESS가 강의내용과는 조금 다른 형식으로 되어있는데 제로초님께서 써주셨던 코드 그대로 복붙해보아도 같은 문제가 생깁니다)

import produce from 'immer';

export const initialState = {
isLoggingOut: false, // 로그아웃 시도중
isLoggingIn: false, // 로그인 시도중
logInErrorReason: "", // 로그인 실패 사유
isSignedUp: false, // 회원가입 성공
isSigningUp: false, // 회원가입 시도중
signUpErrorReason: "", // 회원가입 실패 사유
me: null, // 내 정보
friends: [],
userInfo: null,
};

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 LOAD_USER_REQUEST = 'LOAD_USER_REQUEST';
export const LOAD_USER_SUCCESS = 'LOAD_USER_SUCCESS';
export const LOAD_USER_FAILURE = 'LOAD_USER_FAILURE';

export const LOAD_FRIENDS_REQUEST = 'LOAD_FRIENDS_REQUEST';
export const LOAD_FRIENDS_SUCCESS = 'LOAD_FRIENDS_SUCCESS';
export const LOAD_FRIENDS_FAILURE = 'LOAD_FRIENDS_FAILURE';

export const ADD_FRIEND_REQUEST = 'ADD_FRIEND_REQUEST';
export const ADD_FRIEND_SUCCESS = 'ADD_FRIEND_SUCCESS';
export const ADD_FRIEND_FAILURE = 'ADD_FRIEND_FAILURE';

export const REMOVE_FRIEND_REQUEST = 'REMOVE_FRIEND_REQUEST';
export const REMOVE_FRIEND_SUCCESS = 'REMOVE_FRIEND_SUCCESS';
export const REMOVE_FRIEND_FAILURE = 'REMOVE_FRIEND_FAILURE';

export const UPDATE_LASTSTART_REQUEST = 'UPDATE_LASTSTART_REQUEST';
export const UPDATE_LASTSTART_SUCCESS = 'UPDATE_LASTSTART_SUCCESS';
export const UPDATE_LASTSTART_FAILURE = 'UPDATE_LASTSTART_FAILURE';

export const GET_STARS = 'GET_STARS';
export const USE_STARS = 'USE_STARS';

const reducer = (state = initialState, action) => {
return produce(state, (draft)=>{
switch (action.type) {
case LOG_IN_REQUEST: {
draft.isLoggingIn = true;
draft.logInErrorReason = '';
break;
}
case LOG_IN_SUCCESS: {
draft.isLoggingIn = false;
draft.me = action.data;
break;
}
case LOG_IN_FAILURE: {
draft.isLoggingIn = false;
draft.logInErrorReason = action.error;
draft.me = null;
break;
}
case LOG_OUT_REQUEST: {
draft.isLoggingOut = true;
break;
}
case LOG_OUT_SUCCESS: {
draft.isLoggingOut = false;
draft.me = null;
break;
}
case LOG_OUT_FAILURE: {
draft.isLoggingOut = false;
break;
}
case SIGN_UP_REQUEST: {
draft.isSignedUp = false;
draft.isSigningUp = true;
draft.signUpErrorReason = '';
break;
}
case SIGN_UP_SUCCESS: {
draft.isSigningUp = false;
draft.isSignedUp = true;
break;
}
case SIGN_UP_FAILURE: {
draft.isSigningUp = false;
draft.signUpErrorReason = action.error;
draft.me = null;
break;
}
case LOAD_USER_REQUEST: {
break;
}
case LOAD_USER_SUCCESS: {
console.log('action.me check:',action.me);
action.me ? draft.me = action.data : draft.userInfo = action.data;
console.log('draft.me:', draft.me);
break;
}
case LOAD_USER_FAILURE: {
break;
}
case UPDATE_LASTSTART_REQUEST: {
break;
}
case UPDATE_LASTSTART_SUCCESS: {
draft.me.lastStart = action.data;
break;
}
case UPDATE_LASTSTART_FAILURE: {
break;
}
case GET_STARS: {
draft.me.star += action.data;
break;
}
case USE_STARS: {
draft.me.star -= action.data;
break;
}
case LOAD_FRIENDS_REQUEST: {
break;
}
case LOAD_FRIENDS_SUCCESS: {
draft.friends = action.data;
break;
}
case LOAD_FRIENDS_FAILURE: {
break;
}
case REMOVE_FRIEND_REQUEST: {
break;
}
case REMOVE_FRIEND_SUCCESS: {
const friendIndex = draft.friends.findIndex(v=>v.id===action.data);
draft.friends.splice(friendIndex, 1);
break;
}
case REMOVE_FRIEND_FAILURE: {
break;
}
case ADD_FRIEND_REQUEST: {
break;
}
case ADD_FRIEND_SUCCESS: {
draft.friends.push(action.data);
break;
}
case ADD_FRIEND_FAILURE: {
break;
}
default: {
break;
}
}
})
};

export default reducer;

0

front/pages/_app.js

import React from "react";
import Head from "next/head";
import propTypes from "prop-types";
import withRedux from "next-redux-wrapper";
import withReduxSaga from 'next-redux-saga';
import { createStore, compose, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import reducer from "../reducers";
import createSagaMiddleware from "redux-saga";
import rootsaga from "../sagas";
import {ThemeProvider} from 'styled-components';
import themes from '../components/styledComponents/theme';
import GlobalStyle from '../components/styledComponents/GlobalStyle';
import Axios from "axios";
import { LOAD_USER_REQUEST } from "../reducers/user";

const DreamWitch = ({ Component, store, pageProps }) => (
<>
<Provider store={store}>
<Head>
<title>DreamWitch</title>
<meta name="viewport" content="width=device-width, user-scalable=no"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css"/>
</Head>
<ThemeProvider theme={themes}>
<GlobalStyle></GlobalStyle>
<Component {...pageProps}/>
</ThemeProvider>
</Provider>
</>
);

DreamWitch.propTypes = {
Component: propTypes.elementType.isRequired,
store: propTypes.object.isRequired,
pageProps: propTypes.object.isRequired,
};

const middleware = (store) => (next) => (action) => {
console.log(action);
next(action);
};

DreamWitch.getInitialProps = async (context) => {
const {ctx, Component} = context;
let pageProps = {};
const state= ctx.store.getState();
const cookie = ctx.isServer ? ctx.req.headers.cookie : '';
if(ctx.isServer && cookie){
Axios.defaults.headers.cookie = cookie;
}
if(!state.user.me){
ctx.store.dispatch({
type:LOAD_USER_REQUEST
})
}
if(Component.getInitialProps){
pageProps = await Component.getInitialProps(ctx);
}
return {pageProps};
}

const configureStore = (initialState, options) => {
const sagaMiddleware = createSagaMiddleware();
const middlewares = [
sagaMiddleware,
(store)=>(next)=>(action)=>{
next(action);
}
];
const enhancer =
process.env.NODE_ENV === "production"
? comspose(applyMiddleware(...middlewares))
: compose(
applyMiddleware(...middlewares),
!options.isServer &&
window.__REDUX_DEVTOOLS_EXTENSION__ !== "undefined"
? window.__REDUX_DEVTOOLS_EXTENSION__()
: (f) => f
);
const store = createStore(reducer, initialState, enhancer);
store.sagaTask = sagaMiddleware.run(rootsaga);
return store;
};

export default withRedux(configureStore)(withReduxSaga(DreamWitch));

front/pages/index.js

import React, { useState, useEffect, useCallback } from "react";
import Link from "next/link";
// import Head from 'next/head';
// import AppLaout from '../components/AppLayout';
import Character from "../components/Character";
import History from "../components/History";
import TodoList from "../components/TodoList";
import Shop from "../components/Shop";
import Visit from '../components/Visit';
import { useSelector, useDispatch } from "react-redux";
import Router from "next/router";
import { LOAD_USER_REQUEST, LOG_OUT_REQUEST } from "../reducers/user";
import styled from 'styled-components';
import { LOAD_HISTORIES_REQUEST } from "../reducers/history";
import { LOAD_TODOS_REQUEST } from "../reducers/todo";
import { LOAD_ITEMS_REQUEST, LOAD_EQUIPMENT_REQUEST } from "../reducers/item";

const Index = () => {
const dispatch = useDispatch();
const { me, logInErrorReason } = useSelector((state) => state.user);
const [page, setPage] = useState(1);

useEffect(()=>{
if(!me){
Router.push('/login');
}
}, [me]);

const onChangePage = useCallback(pageNum => () => {
if(page!==pageNum){
setPage(pageNum);
}
}, [page]);

const onLogout = () => {
dispatch({
type: LOG_OUT_REQUEST
})
}

return (
<>
<Wrap>
<TopContent>
<UserStatue>
<Star>{me && me.star}</Star><Level>{me && me.level}레벨</Level>
</UserStatue>
<LogoutButton onClick={onLogout}><i/></LogoutButton>
<Character></Character>
<Tab>
<ul>
<TabItem onClick={onChangePage(1)} active={page===1}><TabIcon iconName={'star'}/></TabItem>
<TabItem onClick={onChangePage(2)} active={page===2}><TabIcon iconName={'list'}/></TabItem>
<TabItem onClick={onChangePage(3)} active={page===3}><TabIcon iconName={'shop'}/></TabItem>
<TabItem onClick={onChangePage(4)} active={page===4}><TabIcon iconName={'friend'}/></TabItem>
</ul>
</Tab>
</TopContent>
<Page>
{page === 1 && <TodoList/>}
{page === 2 && <History/>}
{page === 3 && <Shop/>}
{page === 4 && <Visit/>}
</Page>
</Wrap>
</>
);
};
(styled component 코드 생략)

Index.getInitialProps = async (context) => {
context.store.dispatch({
type: LOAD_HISTORIES_REQUEST
})
context.store.dispatch({
type: LOAD_TODOS_REQUEST
})
context.store.dispatch({
type: LOAD_ITEMS_REQUEST
})
context.store.dispatch({
type: LOAD_EQUIPMENT_REQUEST
})
}


export default Index;

front/pages/user.js (문제가 되는 부분입니다)

import React, { useState, useEffect, useCallback } from "react";
import Link from "next/link";
// import Head from 'next/head';
// import AppLaout from '../components/AppLayout';
import Character from "../components/Character";
import History from "../components/History";
import TodoList from "../components/TodoList";
import Shop from "../components/Shop";
import Visit from '../components/Visit';
import { useSelector, useDispatch } from "react-redux";
import Router from "next/router";
import { LOAD_USER_REQUEST, LOG_OUT_REQUEST } from "../reducers/user";
import styled from 'styled-components';
import { LOAD_HISTORIES_REQUEST } from "../reducers/history";
import { LOAD_TODOS_REQUEST } from "../reducers/todo";
import { LOAD_ITEMS_REQUEST, LOAD_EQUIPMENT_REQUEST } from "../reducers/item";
import propTypes from 'prop-types';

const User = ({id}) => {
const dispatch = useDispatch();
const { userInfo } = useSelector((state) => state.user);
const [page, setPage] = useState(1);

const onChangePage = useCallback(pageNum => () => {
if(page!==pageNum){
setPage(pageNum);
}
}, [page]);

const onLogout = () => {
dispatch({
type: LOG_OUT_REQUEST
})
}

return (
<>
<Wrap>
<TopContent>
<UserStatue>
<Star>{userInfo && userInfo.star}</Star><Level>{userInfo && userInfo.level}레벨</Level>
</UserStatue>
<LogoutButton onClick={onLogout}><i/></LogoutButton>
<Character></Character>
<Tab>
<ul>
<TabItem onClick={onChangePage(1)} active={page===1}><TabIcon iconName={'star'}/></TabItem>
<TabItem onClick={onChangePage(2)} active={page===2}><TabIcon iconName={'list'}/></TabItem>
</ul>
</Tab>
</TopContent>
<Page>
{page === 1 && <TodoList id={id}/>}
{page === 2 && <History id={id}/>}
</Page>
</Wrap>
</>
);
};

(styled component 코드 생략)

User.propTypes = {
id: propTypes.number.isRequired,
}

User.getInitialProps = async (context) => {
const id = parseInt(context.query.id, 10);
console.log('user getInitialProps', context.query.id);
context.store.dispatch({
type: LOAD_USER_REQUEST,
data: id,
})
return {id};
}


export default User;

front/server.js

const express = require('express');
const next = require('next');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const dotenv = require('dotenv');

const dev = process.env.NODE_ENV !== 'production';
const prod = process.env.NODE_ENV === 'production';

const app = next({dev});
const handle = app.getRequestHandler();
dotenv.config();

app.prepare().then(()=>{
const server = express();
server.use(morgan('dev'));
server.use(express.json());
server.use(express.urlencoded({extended:true}));
server.use(cookieParser(process.env.COOKIE_SECRET));
server.use(expressSession({
resave:false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
}
}));

server.get('/user/:id', (req, res)=>{
return app.render(req, res, '/user', {id:req.params.id});
})

server.get('*', (req, res)=>{
return handle(req, res);
});

server.listen(3060, (err)=>{
console.log('next-express running on 3060');
})
})

정중한 전어님의 프로필 이미지
정중한 전어

작성한 질문수

질문하기