React/게시판만들기 v1.

[React][CRUD] 리액트로 간단한 게시판 페이지 만들어보기 - 8. saga에서 게시글 저장부터 화면 이동까지 처리하기, saga에서 router 연결하기, redux-saga, react router, history

binaryJournalist 2020. 11. 2. 08:37
반응형

 

 

 

 

 

목차 돌아가기: binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial

 

[React][CRUD] create-board-tutorial

code: github.com/jwlee-lnd/react-create-board jwlee-lnd/react-create-board Description(korean) : https://binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial - jwlee-lnd/react-create-boar..

binaryjourney.tistory.com

 

 

 

 

saga 로 이용 중인 파일에서는 react-router-dom이 바로 import 되지 않는 것으로 알고 있다

.

import 를 쓰려해도 아마 terminal에서 module not found가 드거나 import가 impossible 하다고 뜰 것이다.

 

 

우회적인 방법을 써야 하는데 나는 여기를 참고하였다.

 

react.vlpt.us/redux-middleware/12-redux-saga-with-router.html

 

12. saga에서 라우터 연동하기 · GitBook

12. saga에서 라우터 연동하기 우리가 이전에 redux-thunk를 배울 때 thunk함수에서 리액트 라우터의 history 를 사용하는 방법을 배워보았습니다. 예를 들어서 로그인 요청을 할 때 성공 할 시 특정 주소

react.vlpt.us

react-router.vlpt.us/1/05.html

 

1-5. withRouter · GitBook

05. withRouter 라우트가 아닌 컴포넌트에서 라우터에서 사용하는 객체 - location, match, history 를 사용하려면, withRouter 라는 HoC 를 사용해야합니다. src/components/ShowPageInfo.js import React from 'react'; import { wi

react-router.vlpt.us

 

 

john ahn을 만나기 전 나의 빛과 소금이었던 velopert..

 

사실 이 방법 외에도 다른 방법이 있을 것 같은데 나는 잘 모르겠다.

 

만약 내 블로그를 방문한 분들 중 더 좋은 방법을 아시는 분이 있다면 꼭 댓글을 달아주셨으면 좋겠다. (제발요)

 

 

일단 오늘까지 처리한 것만 업로드하겠다.

화면 이동이 끝까지 안 되는데 그건 내일 출근해서 알아봐야 할 듯하다.  (해결! 해결된 내용은 아래에 있다!)

 

 

먼저 store.js 에 가서 history의 createBrowserHistory import한 후 customHistory 라는 변수에 받는다.

그리고 customHistory를 sagaMiddleware와 store 적용시킨다.

 

 

 

 

store.js의 full code는 다음과 같다.

 

// store.js

import { configureStore } from "@reduxjs/toolkit";
import createSagaMiddleware from "redux-saga";
import logger from "redux-logger";
import rootReducer from "./slice/rootSlice";
import rootSaga from "./sagas/rootSaga";
import { createBrowserHistory } from "history";

const customHistory = createBrowserHistory();
const sagaMiddleware = createSagaMiddleware({
  context: { history: customHistory },
});
const initialState = {};

const store = configureStore({
  reducer: rootReducer,
  middleware: [sagaMiddleware, logger],
  devTools: true,
  preloadedState: initialState,
});

sagaMiddleware.run(rootSaga);

export default store;

 

 

 

그리고 index.js 로 가서 똑같이 history의 createBrowserHistory import한 후 customHistory 라는 변수에 받는다.

BrowserRouter 컴포넌트에 history라는 property로 customHistory 를 넣어준다.

 

 

 

 

index.js의 full code는 다음과 같다.

 

// index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import { createBrowserHistory } from "history";
import reportWebVitals from "./reportWebVitals";
import store from "./store";

const customHistory = createBrowserHistory();

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter history={customHistory}>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

 

 

 

그리고 articleSaga 로 가서

 

const postedData = yield Axios.post(`http://localhost:4000/board/`, data);

 

이 코드를 아래처럼 바꿔주었다.

 

 

const response = yield Axios.post(`http://localhost:4000/board/`, data);

 

 

 

 

사실 안 바꿔도 상관없는데 나는 response.data 이런 형식으로 쓰는 게 더 익숙해서 바꿔주었다.

 

 

 

 

그리고 redux-saga/effects 에서 getContext를 import 한후 getContext("history") 값을 history 라는 변수에 담아준다.

 

import { getContext } from "redux-saga/effects";

  const history = yield getContext("history");

 

 

 

 

history 에 화면 이동 경로인 `/article/${response.data.id}`를 push해준다.

 

  yield history.push(`/article/${response.data.id}`);

 

 

 

full code는 다음과 같다.

 

import { call, put, getContext } from "redux-saga/effects";
import Axios from "axios";
import { articleActions } from "../slice/articleSlice";

export function* registerArticleAsync(action) {
  const data = action.payload;

  const response = yield Axios.post(`http://localhost:4000/board/`, data);

  yield alert("저장되었습니다.");

  console.log(response.data.id);

  const history = yield getContext("history");

  yield history.push(`/article/${response.data.id}`);

}

 

 

 

여기서 마주한 문제점은 주소 이동은 되는데 컴포넌트까지 불러오지 못한다는 점이다.

 

새로고침하면 컴포넌트가 그제서야 바뀌긴 한다.

 

지금 ArticlePage 컴포넌트는 다음과 같이 작성해놓은 상태이다.

 

 

 

// ArticlePage.js

import React from "react";

function ArticlePage({ match, location }) {
  return <div>ArticlePage {match.params.articleId}</div>;
}


export default ArticlePage;

 

새로고침 후 화면을 보면 match.params.articleId 로 생성된 글 id가 잘 들어간 게 보인다.

 

 

 

아마 Redirect 문제가 아닐까 싶은데

 

오늘 하루 안에 해결방법을 찾지 못해서 내일도 이어가려고 한다.

 

 

 

11/2 ------------->

 

 

출근하자 마자 아침부터 찾아봤다.

 

 

어제 온갖 키워드로 검색해도 마음에 드는 답변을 못 찾았는데

 

 

 

오늘 아침에 찾은 것도 솔직히 반신반의했으나 바로 해결되어서 속시원하였지만 허무하기도 하였다.

 

 

내가 참고한 답변은 아래 사이트이다.

 

역시 모든 해결책은 stackoverflow에 있나니..

 

 

stackoverflow.com/questions/42941708/react-history-push-is-updating-url-but-not-navigating-to-it-in-browser

 

React history.push() is updating url but not navigating to it in browser

I've read many things about react-router v4 and the npm history library to this point, but none seems to be helping me. My code is functioning as expected up to the point when it should navigate...

stackoverflow.com

 

 

페이지 이동 문제를 해결하면서 리팩토링도 같이 진행하려 한다.

 

 

앞서 store와 middleware에 createBrowserHistory를 매번 import하고 customHistory 변수로 값을 받으면 나중에 유지보수가 어려워질 것 같아 /src/utils/history.js 파일을 따로 만들었다.

 

// /src/utils/history.js

import { createBrowserHistory } from "history";

export default createBrowserHistory();

 

 

 

 

그리고 index.js와 store.js, articleSaga.js 에서 /src/utils/history.js 를 import 하였다.

이렇게 하면 createBrowerHistory를 두 번 이상 import + 선언할 필요가 없다.

 

// index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import reportWebVitals from "./reportWebVitals";
import store from "./store";
import history from "./utils/history"; // 수정된 부분

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter history={history}> {/* 수정된 부분 */}
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

// store.js

import { configureStore } from "@reduxjs/toolkit";
import createSagaMiddleware from "redux-saga";
import logger from "redux-logger";
import rootReducer from "./slice/rootSlice";
import rootSaga from "./sagas/rootSaga";
import history from "./utils/history"; // 수정된 부분

const sagaMiddleware = createSagaMiddleware({
  context: { history: history }, // 수정된 부분
});
const initialState = {};

const store = configureStore({
  reducer: rootReducer,
  middleware: [sagaMiddleware, logger],
  devTools: true,
  preloadedState: initialState,
});

sagaMiddleware.run(rootSaga);

export default store;

 

 

index와 store는 import 부분만 바꿔주고 const customHistory = createBrowserHistory(); 이 코드 지운 뒤에 변수 이름을 history로 고쳐주면 될 것이다.

 

 

 

articleSaga.js에서는 getContext 를 빼고 history에서  바로 push하는 것으로 바꾸었다.

 

 

// articleSaga.js

import { call, put } from "redux-saga/effects";
import Axios from "axios";
import { articleActions } from "../slice/articleSlice";
import history from "../utils/history";

export function* registerArticleAsync(action) {
  const data = action.payload;

  const response = yield Axios.post(`http://localhost:4000/board/`, data);

  alert("저장되었습니다.");

  console.log(response.data.id);

  // history.push(`/article/${response.data.id}`);

  history.push(`/article/${response.data.id}`, response.data.id);
}

 

history.push(url) 과 history.push(url, object) 이 두 방법이 있는데 url 페이지로 이동하는 것은 둘다 같으나

history.push(url, object) 일 경우 object는 state로 보내진다.

 

나는 둘 중 어느 방법으로 쓸지 몰라 일단 object도 보내는 것으로 하고 url만 push 하는 부분은 주석처리해놨다.

 

 

그리고 화면이 넘어갔을 때 내가 바로 전에 등록한 글인지 알 수 있도록 ArticlePage 컴포넌트에 생성된 Id가 바로 보일 수 있도록 match.params.articleId 로넣어놨다.

 

// ArticlePage.js

import React from "react";

function ArticlePage({ match, location }) {
  console.log(match.params.articleId);
  return <div>ArticlePage - id: {match.params.articleId}</div>;
}



export default ArticlePage;

 

 

 

 

 

이제 문제해결 부분인데

위 참조해놓은 stackoverflow를 보면 index.js 에서 App 컴포넌트를 react-router-dom의 BrowserRouter 가 아니라 react-router의 Router로 감싸줘야 redirect 가 올바르게 기능한다고 한다.

 

 

매우 간단한 해결 방법이었다.

 

 

// index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import { Provider } from "react-redux";
import { Router } from "react-router"; // 수정
import reportWebVitals from "./reportWebVitals";
import store from "./store";
import history from "./utils/history";

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}> {/* 수정 */}
      <App />
    </Router>
  </Provider>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

수정 후 json-server를 열고 바로 글을 등록해보면

 

npx json-server ./board.json --port 4000

 

 

RegisterPage
ArticlePage

 

 

성공한 것을 볼 수 있다!

 

 

 

 

목차 돌아가기: binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial

 

[React][CRUD] create-board-tutorial

code: github.com/jwlee-lnd/react-create-board jwlee-lnd/react-create-board Description(korean) : https://binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial - jwlee-lnd/react-create-boar..

binaryjourney.tistory.com

 

반응형