인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

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

자유인님의 프로필 이미지
자유인

작성한 질문수

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

WrappedApp created new store with.. 반복 오류 질문입니다.

해결된 질문

작성

·

1K

0

안녕하세요! 강의 잘 듣고 있습니다.

다름이 아니라, 유사 에러 겪은 다른 학생들 질문 찾아봤는데도 해결이 안되서 질문드립니다.

다른 페이지에서는 저 에러가 안 뜨는데 index 페이지에만 가면 state가 전부 초기화가 되네요. 그리고 해당 문구가 계속 무한루프 돌듯이 반복되구요. 또 실행 잘 되다가 갑자기 저럴 때가 생깁니다.

// reducers 내부 index.js
import { HYDRATE } from "next-redux-wrapper";
import { combineReducers } from "@reduxjs/toolkit";
import userSlice from "./user";
import alertSlice from "./alert";

const rootReducer = (state, action) => {
  switch (action.type) {
    case HYDRATE:
      console.log("root reducer", state, action);
      return { ...state, ...action.payload };
    default: {
      const combinedReducer = combineReducers({
        user: userSlice.reducer,
        alert: alertSlice.reducer,
      });
      return combinedReducer(state, action);
    }
  }
};

export default rootReducer;

// actions user.js
export const fetchUser = async () => {
  const response = await api.get(`/users/load`);
  console.log(response.data);
  return response.data;
};
export const loadUser = createAsyncThunk("user/loadUser", fetchUser);

// reducers user.js
import { createSlice } from "@reduxjs/toolkit";
import { loadUser } from "../action/user";

const initialState = {
  userData: null,
  requestId: null,
  auth: false,
};

const userSlice = createSlice({
  name: "user",
  initialState,
  extraReducers: (builder) =>
    builder
      .addCase(loadUser.pending, (state, action) => {
        const { meta } = action;
        state.requestId = meta.requestId;
        return {
          ...state,
        };
      })
      .addCase(loadUser.fulfilled, (state, action) => {
        state.requestId = null;
        state.auth = true;
        state.userData = action.payload;
        return {
          ...state,
        };
      })
      .addCase(loadUser.rejected, (state) => {
        state.requestId = null;
        state.auth = false;
        return {
          ...state,
        };
      })
      .addDefaultCase((state) => {
        return { ...state };
      }),
});

export default userSlice;



// store/index.js
import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
import { createWrapper } from "next-redux-wrapper";
import rootReducer from "../reducer";

const store = configureStore({
  middleware: [...getDefaultMiddleware()],
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== "production",
});

export const makeStore = (context) => store;

const wrapper = createWrapper(makeStore, {
  debug: process.env.NODE_ENV !== "production",
});

export default wrapper;

// pages/index.js
// 컴포넌트 코드 생략
export const getServerSideProps = wrapper.getServerSideProps(
  async (context) => {
    console.log("getServerSideProps start");
    console.log(context.req.headers);
    const cookie = context.req ? context.req.headers.cookie : "";
    axios.defaults.headers.Cookie = "";
    if (context.req && cookie) {
      axios.defaults.headers.Cookie = cookie;
    }
    context.store.dispatch(loadUser());
    console.log("getServerSideProps end");
  },
);

답변 16

1

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

이 부분은 제가 확인 후 thunk랑 연동하는 것 강좌 다시 찍어보겠습니다.

0

자유인님의 프로필 이미지
자유인
질문자

일단 get요청은 전부 swr로 대체하고 확인해보니 개발모드 서버 가동한 상태에서 수정 후 자동으로 업데이트되면서 생기더군요ㅠㅠ 클라이언트 사이드로 할 땐 해당 에러가 발생하지 않았습니다.

0

자유인님의 프로필 이미지
자유인
질문자

일단 get요청은 전부 swr로 바꾸고 post 부분은 그대로 두니 해결됐습니다. 바쁘신데 답변 감사합니다! 

0

자유인님의 프로필 이미지
자유인
질문자

오 감사합니다! 이제 구동은 됐는데, 처음 렌더링 시 상태가 페이지를 넘어갈 때 발생하는 엑션들이 리듀서에 적용이 안 된채로 진행 되는 에러가 있긴 하네요ㅠㅠ get serverside props 함수 내부에 dispatch promise 결과까지 찍어보았어요.

    context.store
      .dispatch(loadChannelProfile(channelid))
      .then((res) => console.log(res))
      .catch((err) => console.error(err));

요청은 잘 들어왔는데, 상태 업데이트가 계속 안되다가 다시 hydrate 적용할 당시랑 똑같은 문제가 뜨네요... 처음 돌려보았을 때 메인페이지는 잘 나왔었는데ㅠㅠ 그래서 서버 껐다 다시 켜면 메인만 또 제대로 나오고 라우터 넘어갈 시 바뀐 상태가 적용이 안되요.. 그 saga에서 하는것처럼 toPromise랑 end 처리를 해줘야 하나요? 아래 코드는 컴포넌트 아래 serverside props 입니다.

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;
    }
    const { channelid } = context.params;

    console.log(channelid);

    context.store.dispatch(loadUser());
    context.store
      .dispatch(loadChannelProfile(channelid))
      .then((res) => console.log(res))
      .catch((err) => console.error(err));

    await context.store.dispatch(
      channelSlice.actions.loadChannelProfileFinished(),
    );

    return { props: {} };
  },
);

0

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

아, typeof window !== "undefined"

undefined는 문자열이어야합니다.

0

자유인님의 프로필 이미지
자유인
질문자

바꿔도 똑같은 에러가 반복되서 브라우저 콘솔창에서 확인해봤는데 빈 state로 들어왔네요ㅠ 일단 __NEXT_DATA__는 브라우저에서 렌더링이 되었고, makestore에서 context도 콘솔에 req, res 모두 찍혔습니다. 비동기 처리를 한번 더 해야할까요..

[코드]

import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
import { createWrapper } from "next-redux-wrapper";
import rootReducer from "../reducer";

function getServerState() {
  return typeof window !== undefined && typeof document !== undefined
    ? JSON.parse(document.getElementById("__NEXT_DATA__").textContent).props
        .pageProps.initialState
    : undefined;
}

const makeStore = (context) => {
  console.log(context);
  return configureStore({
    middleware: [...getDefaultMiddleware()],
    reducer: rootReducer,
    devTools: process.env.NODE_ENV !== "production",
    preloadedState: getServerState(),
  });
};

const wrapper = createWrapper(makeStore, {
  debug: process.env.NODE_ENV !== "production",
});

export default wrapper;

0

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

preloadedState: document ? getServerState() : undefined,
이것 때문에 에러나는 겁니다. 그냥 getServerState()만 쓰세요

0

자유인님의 프로필 이미지
자유인
질문자

동일한 오류가 계속 뜨네요ㅠㅠ 일단 GET요청 일부는 급하니까 swr로 빼고, 이부분 완전 수정해서 적용해야 할 것 같네요ㅠ 혹시 store/index.js가 브라우저가 아니라 node에서 돌아가서 document를 못 읽어서 생기는 오류는 아니죠? 

import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
import { createWrapper } from "next-redux-wrapper";
import rootReducer from "../reducer";

function getServerState() {
  return typeof typeof window !== undefined && document !== undefined
    ? JSON.parse(document.getElementById("__NEXT_DATA__").textContent).props
        .pageProps.initialState
    : undefined;
}

const makeStore = (context) => {
  console.log(context);
  return configureStore({
    middleware: [...getDefaultMiddleware()],
    reducer: rootReducer,
    devTools: process.env.NODE_ENV !== "production",
    preloadedState: document ? getServerState() : undefined,
  });
};

const wrapper = createWrapper(makeStore, {
  debug: process.env.NODE_ENV !== "production",
});

export default wrapper;

0

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

preloadedState: getServerState()로 두고

function getServerState() {

  return typeof document !== undefined? JSON.parse(...) : undefined

}

안쪽으로 넣어보세요.

0

자유인님의 프로필 이미지
자유인
질문자

똑같은 에러가 뜨는데, 혹시 _app.js나 _document.js 파일을 손봐야할까요?ㅠㅠ 

// _app.js
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import Head from "next/head";
import wrapper from "../app/store";

import { appShortTitle, appDescription } from "../app/helper/appTitle";

function App({ Component, pageProps }) {
  useEffect(() => {
    if ("serviceWorker" in navigator) {
      window.addEventListener("load", function () {
        navigator.serviceWorker.register("/sw.js").then(
          function (registration) {
            console.log(
              "Service Worker registration successful with scope: ",
              registration.scope,
            );
          },
          function (err) {
            console.log("Service Worker registration failed: ", err);
          },
        );
      });
    }
  }, []);

  return (
    <>
      <Head>
        <meta charSet="utf-8" />
        <meta name="mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black" />
        <link rel="icon" href="/favicon.ico" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="theme-color" content="#000000" />
        <link rel="apple-touch-icon" href="/logo192.png" />
        <link rel="manifest" href="/manifest.json" />
        <meta property="og:image" content="/thumbnail.jpg" />
        <meta
          name="viewport"
          content="minimum-scale=1, initial-scale=1, width=device-width"
        />
        <meta name={appShortTitle} content={appDescription} />
        <meta property={"og:title"} name={appShortTitle} />
        <meta property={"og:description"} content={appDescription} />
      </Head>
      <Component {...pageProps} />
    </>
  );
}

App.propTypes = {
  Component: PropTypes.elementType.isRequired,
};

export default wrapper.withRedux(App);

0

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

typeof window !== undefined && typeof document !== undefined ? getServerState() : undefined

이렇게 하면요?

0

자유인님의 프로필 이미지
자유인
질문자

브라우저 콘솔 에러는 다음처럼 뜹니다ㅠ

0

자유인님의 프로필 이미지
자유인
질문자

https://forum.playcanvas.com/t/uncaught-referenceerror-document-is-not-defined/13073/8 여기서 시키는 대로 하니 이런 에러가 뜨네요ㅠ

0

자유인님의 프로필 이미지
자유인
질문자

_error.js를 다음과 같이 바꿔도 똑같은 결과가 나옵니다ㅠ

export const getServerSideProps = wrapper.getServerSideProps(
  async (context) => {
    const { res, error } = context;
    const statusCode = res ? res.statusCode : error ? error.statusCode : null;
    const cookie = context.req ? context.req.headers.cookie : "";
    axios.defaults.headers.Cookie = "";
    if (context.req && cookie) {
      axios.defaults.headers.Cookie = cookie;
    }
    context.store.dispatch(loadUser());
    const { tagname } = context.params;
    console.log(tagname);

    return { props: { statusCode } };
  },
);

0

자유인님의 프로필 이미지
자유인
질문자

다른 페이지는 _error.js 를 제외하고 전부 다 똑같이 적용 되었습니다! error.js는 커스텀 페이지라 이렇게 적었어요ㅠㅠ

ErrorPage.getInitialProps = ({ res, error }) => {
  const statusCode = res ? res.statusCode : error ? error.statusCode : null;
  return { statusCode };
};

말씀하신대로 적용해봤는데 store/index.js에서 미리 읽고 해서 그런가 not defined 에러가 뜨네요ㅠㅠ

0

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

다른 페이지에는 getServerSideProps가 있나요? 다른 페이지에서 새로고침하면 어떻게 되나요?

저는 툴킷 쓰실 때는 hydrate 액션 지우시고 다음과 같이 쓰시는 걸 추천드립니다.

function getServerState() {
  return JSON.parse(document.getElementById('__NEXT_DATA__').textContent).props.pageProps.initialState;
}
const store = configureStore({
  reducer,
  middleware: [...getDefaultMiddleware()],
  preloadedState: typeof window !== undefined ? getServerState() : undefined
});
자유인님의 프로필 이미지
자유인

작성한 질문수

질문하기