StarWarsAPI : 2021.02.04 ( useQuery ~ refresh ) Single Rocket Page not showing after Refresh( Parameter & React BrowserRouter )

오범준·2021년 2월 25일
0

Code

client.jsx

// React Setting
const React = require('react')
const ReactDOM = require('react-dom')
const { hot } = require('react-hot-loader/root')

// Redux
import {createStore} from 'redux'
import {composeWithDevTools} from 'redux-devtools-extension';
import {Provider} from 'react-redux';
import rootReducer from './src/modules'
const store = createStore(rootReducer, composeWithDevTools())

// App.js
import App from './src/App'

// ApolloClient
import {
    ApolloClient,
    ApolloProvider,
    HttpLink,
    InMemoryCache
} from '@apollo/client'

// Router
import { BrowserRouter ,HashRouter } from 'react-router-dom'

const client = new ApolloClient({
    cache :  new InMemoryCache() ,
    link : new HttpLink({
        uri : 'https://spacexdata.herokuapp.com/graphql'
    })
})

// Hot Loader
const Hot = hot(App)

ReactDOM.render(
    <ApolloProvider client = {client} >
        <Provider store = {store}>
            <BrowserRouter>
                <Hot/>
            </BrowserRouter>
        </Provider>
    </ApolloProvider> ,
    document.querySelector('#root')
)

ShipSingleConatainer.js

import React,{memo} from 'react'
import {useQuery , gql } from '@apollo/client' 
import PhotoGrid from '../../components/PhotoGrid'

const ShipSingleContainer = memo(({match}) => {

    let contentId = match.params.shipId
    let shipId, image, shipName, roles, shipType, typeName ;

    const { loading, error, data } = useQuery(SINGLE_SHIP, {
        variables: { id: contentId },
    });

    console.log("data", data)

    // 여기에 redux 관련 요소 넣기 


    if (loading) return <p>Loading ...</p> 
    else{
        shipId = data.ship.ship_id
        image = data.ship.image 
        shipName = data.ship.ship_name 
        roles = data.ship.roles 
        typeName = data.ship.__typename
    }

    return (
        <div className = "SingleContent" >
            <div>
                <PhotoGrid id = {shipId} img = {image} contentType = {typeName} /> 
            </div>
            <div>
                <h1>Hello {shipName && shipName}!</h1>
            </div>
        </div>

    );
    
})

export default ShipSingleContainer

const SINGLE_SHIP = gql`
    query SingleShip($id : String!){
    ship(id : $id){
        ship_id
        ship_type
        ship_name
        image
        roles
        year_built
        successful_landings
    }
}` 

In short

1) use 'param' to get the 'id' of rocekt
2) use 'id' from 'param' to use 'graphql query'
3) get the data from 'graphql query'

" id passed through params get lost on page refresh "

BUT

after I refresh ,
'param' is undefined,
therefore, the 'data' is not loaded

How Can I maintain 'param' even after refresh ?

In order to solve this, we have to understand
how React deals with 'route' or 'url'

If you have no Server running, and only Client Side is running ,

when you render multiple pages through React,

for example

you want to render 2 different pages

1) '/about
2) '/mypage

the truth is ,you are not having two different html page,

you only receive one and only html file from the server
for initialzation

after that, with one html file, you are managing entire website with JS

which means, you are tricked by React,

React makes 'fakeUrl' and renders component according to that 'specific fakeUrl'

which means, React, Client Side is managing 'url' info since it's making url by itself

and Server has no way to know on 'url' info

BUT

if you manually refresh the page ,
it means you are sending request to server on the specific html file for that specific url

but as React, Client is handling the 'url' info without notifying the Server

Server cannot know the 'url' info and give no response to request, since it does not have any html files or data related to request 'url'

> Solution : HASHROUTER


관련 키워드 
- React-Route-Dom
- SPA vs MPA
- BrowserRouter, HashRouter


현재 저의 React-router-dom 세팅
<BrowserRouter>
	<Route path = "/about" component = {<Component/>}  />
	<Route path = "/mypage" component = {<Component/>}  />
</BrowserRouter>

저는 서버 없이, React, 즉, Client Side에서만 작업중이었습니다.

React 상에서 url을 통한, 다양한 페이지를 render 시킬 때, 

에를 들어
'/about'
'/mypage'
라는 2개의 서로 다른 페이지를 render 시킬 때,

사실은 2개의 서로 다른 페이지가 있는 것이 아닙니다.
( SPA 원리를 이해하시면 좋습니다 )

CSR 상에서는, 사실 하나의 html 파일을 두고,
JS로 모든 웹사이트를 관리하는 형식입니다.

그말은 즉슨, 하나의 html 파일이 있다는 것이고
우리는 항상 하나의 html 파일만을 보고 있다는 것이고
실제로 페이지마다 다른 html을 보고 있는 것은 아니라는 것입니다.

React 가 virtual-dom이라는 원리를 통해서
우리가 마치 서로 다른 url에서
서로 다른 html 을 보고 있는 듯하게
착각을 일으키는 것입니다

그래서 우리가 페이지를 이동할때도
예를 들어
'/mypage'
/about'

이렇게 페이지를 서로 이동할때,
url 만 다르게 보여줄 뿐,

실제로는 같은 html 상에서
보여주는 content 만 달라지는 원리입니다.

여기서 중요한 점은,
현재 서버는 없는 상태이고

url 관련 처리를 모두 Client 측에서 담당하고 있기 때문에
Client 상에서 url이 바뀌거나 할때
Client만이 관련 정보를 알 뿐, Server는 알길이 없습니다.

< 그런데 >

예를 들어 우리가
1) 페이지를 수동 새로고침하거나
2) 'localhost:8080/about' 이라는 url을 복사해서, 다른 url 창에다가 복붙하면

'Cannot Get '/about' '이라는 404 Error을 만나게 됩니다

< 왜냐하면 >
위의 2 과정은, Server에게 해당 url에 대한 정보를 달라고 요청하는 것이기 때문입니다.

그런데 말했듯이, 별도의 Server를 세팅해 놓지 않는 이상, 
Server는 '/about'이라는 url에 대한 정보도 모르고
해당 url 에 맞는 html 파일을 준비해두지도 않았기 때문에

우리가 새로고침 해서
Server에게 '/about' 에 해당하는 정보를 달라고 요청을 하더라도
Server는 뭔소린인지 모르겠어 ! 라고 하면서 
response를 보내주지 않는 것입니다.

즉, React > React-Route-Dom을 이용할 경우
모든 url 정보는 Client가 관리하고 Client만이 알고 있기 때문에

수동 새로고침. 등 Server에게 정보를 request하는 과정이 관여되는 순간
Server는 아무것도 모르는 상황이라면 
위와 같이 NOT FOUND 에러가 뜰 수 있다는 것입니다.


< 해결책 >
현재 제가 해결한 방식은
BrowserRoute 가 아니라 HashRouter를 사용한 것입니다
( 토이프로젝트라서, SEO 등의 고려사항이 필요하지 않아서
사실 땜빵용 해결책 ;; ) 

<BrowserRouter>
	<Route path = "/about" component = {<Component/>}  />
	<Route path = "/mypage" component = {<Component/>}  />
</BrowserRouter>

로 세팅되어있던 것을

<HashRouter>
	<Route path = "/about" component = {<Component/>}  />
	<Route path = "/mypage" component = {<Component/>}  />
<HashRouter>


이와 같이 바꾸게 되는 것이고
그렇게 되면 url이 아래와 같이 표현됩니다

'/#about'
'/#mypage'

그리고 # 뒤의 부분은 역시 Client 만이 알고 있습니다.

이 상황에서 우리가 
'/#about' page 에서 수동 새로고침을 하게 되면

서버에 요청하기 전에
Client가 ' 어 나 /about 관련 url도 알고 그에 맞는 component도 알아' 라고 하면서
자기가 알고 있는 url의 경우, 
서버에게 해당 url에 대한 페이지를 요청하는 것이 아니라
자체 rendering을 다시 해주기 때문에

위와 같은 문제가 해결됩니다


client.jsx

// React Setting
const React = require('react')
const ReactDOM = require('react-dom')
const { hot } = require('react-hot-loader/root')

// Redux
import {createStore} from 'redux'
import {composeWithDevTools} from 'redux-devtools-extension';
import {Provider} from 'react-redux';
import rootReducer from './src/modules'
const store = createStore(rootReducer, composeWithDevTools())

// App.js
import App from './src/App'

// ApolloClient
import {
    ApolloClient,
    ApolloProvider,
    HttpLink,
    InMemoryCache
} from '@apollo/client'

// Router
import { BrowserRouter ,HashRouter } from 'react-router-dom'

const client = new ApolloClient({
    cache :  new InMemoryCache() ,
    link : new HttpLink({
        uri : 'https://spacexdata.herokuapp.com/graphql'
    })
})

// Hot Loader
const Hot = hot(App)

ReactDOM.render(
    <ApolloProvider client = {client} >
        <Provider store = {store}>
            <HashRouter>
                <Hot/>
            </HashRouter>
        </Provider>
    </ApolloProvider> ,
    document.querySelector('#root')
)

As you can see, I Changed 'BrowserRoute' to 'HashRouter'

Now if I refresh, then Client Side will automatically notice the 'url' and re-render before sending request to server

( Additonal Concerns such as SEO is not considered in this post, since I am not using any Server and have no need for SEO ... !! Just Temporary Solution )

profile
Dream of being "물빵개" ( Go abroad for Dance and Programming)

0개의 댓글