npx create-react-app 프로젝트명
yarn add styled-components react-router-dom apollo-boost @apollo/react-hooks graphql
//App.js
import { HashRouter as Router, Route } from "react-router-dom";
import Home from "../routes/Home";
import Detail from "../routes/Detail";
function App() {
return (
<Router>
<Route exact path="/" component={Home} />
<Route path="/:id" component={Detail} />
</Router>
);
}
export default App;
기본적으로 query를 Axios의 fetch와 함께 POST request로 보내야한다. 하지만 이러한 방식으로 하면 반복적으로 POST를 해줘야한다. 이러한 것들을 Apollo에서 자동적으로 쉽게 해준다.
여기서 http://localhost:4000/
은 저번 GraphQL 수업에서 만들어 놓은 프로젝트를 실행시켜 놓으면 된다.
//apollo.js
import { ApolloClient } from "@apollo/client";
const client = new ApolloClient({
uri: "http://localhost:4000/",
cache: new InMemoryCache(),
});
export default client;
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import { ApolloProvider } from '@apollo/client';
import client from "./apollo";
ReactDOM.render(
<ApolloProvider client={client}>
<React.StrictMode>
<App />
</React.StrictMode>
</ApolloProvider>,
document.getElementById('root')
);
영화의 id와 이미지를 가져오는 query를 받아온다.
//Home.js
import React from "react";
import { gql } from "apollo-boost";
import { useQuery } from "@apollo/client";
const GET_MOVIES = gql`
{
movies{
id
medium_cover_image
}
}
`;
export default () => {
const { loading, error, data } = useQuery(GET_MOVIES);
if (loading) {
return "loading...";
} else {
return data.movies.map(m => <h1>{m.id}</h1>);
}
};
//Detail.js
import React from "react";
import { useParams } from "react-router-dom";
import { gql } from "apollo-boost";
import { useQuery } from "@apollo/client";
const GET_MOVIE = gql`
query getMovie($id: Int!) { //이부분은 Apollo에서 변수의 type을 검사하도록 한다.
movie(id: $id) { //여기서부터 아래부분만 query로 내 서버로 간다.
id
title
medium_cover_image
language
rating
description_intro
}
suggestions(id: $id) {
id
medium_cover_image
}
}
`;
export default () => {
const { id } = useParams();// url의 파마미터를 받아올 수 있다.
const { loading, data } = useQuery(GET_MOVIE, {
variables: { id: parseInt(id) }
});
if (loading) {
return "loading...";
}
if (data && data.movie){
return data.movie.title;
}
};
Apollo는 cache를 가지고 있디. Apollo는 뭔가를 얻으면 저장한다. 만약 다시 같은 id로 이동한다면 다시 request하지 않고 cahce에서 꺼내 보여준다. user가 loading창을 아주 덜 보게 되는 것이다.
랜더링 하는 곳에 바로 data를 쓰게 되면 data를 받아오기 전이기 때문에 movie가 없다는 error가 뜬다. 그래서 항상 loading과 같이 써줘야한다.
{loading ? "Loading..." : data.movie.title}
{!loading && data.movie && (<><Description>{data.movie.description_intro}</Description></>)}
javascript에 optional chaining이 추가되어 아래와 같이 바로 쓸 수 있다.
<Description>{data?.movie?.description_intro}</Description>
Apollo Dev Tool을 이용하면 브라우저에서 Apollo에서 관리하는 cache와 query들을 볼 수 있다.
Local state는 API에서 넘어온 data를 바꿀 수 있다.
//apollo.js
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: "http://localhost:4000/",
cache: new InMemoryCache(),
resolvers: {
Movie: {
isLiked: () => false
}
}
});
export default client;
Local state의 값을 바꾸고 싶다면 client에 mutation을 생성해서 바꿀 수 있다. backend에서의 resolver를 사용할 수 있다.
//apollo.js
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: "http://localhost:4000/",
cache: new InMemoryCache(),
resolvers: {
Movie: {
isLiked: () => false
},
Mutation: {
toggleLikeMovie: (_, { id, isLiked }, { cache }) => {
cache.modify({
id: `Movie:${id}`,
fields: {
isLiked: (isLiked) => !isLiked
}
});
}
}
}
});
export default client;
//Movie.js
const LIKE_MOVIE = gql`
mutation toggleLikeMovie($id: Int!, $isLiked: Boolean!) {
toggleLikeMovie(id: $id, isLiked: $isLiked) @client
}
`;
//@client를 통해 apollo에 mutation이 client에 있다고 알려줘야한다.
export default ({ id, bg, isLiked }) => {
const [toggleMovie] = useMutation(LIKE_MOVIE, {
variables: { id: parseInt(id), isLiked }
});
return (
<Container>
<Link to={`/${id}`}>
<Poster bg={bg} />
</Link>
<button onClick={toggleMovie}>{isLiked ? "Unlike" : "Like"}</button>
</Container>
);
};
Detail.js에서 Movie query를 받아올 때 id도 같이 받아와야 movie와 movies의 값이 동일하게 바뀐다.
//Detail.js
const GET_MOVIE = gql`
query getMovie($id: Int!) {
movie(id: $id) {
id
title
medium_cover_image
language
rating
description_intro
isLiked @client
}
suggestions(id: $id) {
id
medium_cover_image
}
}
`;
export default () => {
const { id } = useParams();
const { loading, data } = useQuery(GET_MOVIE, {
variables: { id: parseInt(id) }
});
return (
<Container>
<Column>
<Title>
{loading
? "Loading..."
: `${data.movie.title} ${data.movie.isLiked ? "💖" : "😞"}`}
</Title>
<Subtitle>
{data?.movie?.language} · {data?.movie?.rating}
</Subtitle>
<Description>{data?.movie?.description_intro}</Description>
</Column>
<Poster bg={data?.movie?.medium_cover_image}></Poster>
</Container>
);
};
하지만 위의 방식으로 하면 Detail.js에서 movie query를 다시 부르기 때문에 isLiked가 flase로 초기화 된다. 영화를 한번은 클릭해야지만 like기능을 쓸 수 있었다.