Hi, I'm Brittney and I'm an instructor over at ZTM Academy and the owner, designer, and developer at bDesigned. You can find more dev notes by me at Console Logs.
How To GraphQL Updated Tutorial Part 1
Table of Contents
TLDR: The How To GraphQL with React Apollo is quiet outdated. This is part 1 of the updated hooks version.
Frontend Setup
- 1. Create project
yarn create react-app hackernews-react-apollo
cd hackernews-react-apollo
- 2. Add git origin
git add remote origin URL
git add .
git commit -m 'init fresh repo'
git push --set-upstream origin master
- 3. Restructure app
mkdir src/components src/styles
- 4. Move
App.js
into components folder, then move index.css and App.css into styles folder.
- 5. Update imports.
// index.js
import './styles/index.css';
import App from './components/App';
// App.js
import logo from '../logo.svg';
import '../styles/App.css';
- 6. Add tacheyons to
public/index.html
<!-- public/index.html under other links in head -->
<link rel="stylesheet" href="https://unpkg.com/tachyons@4.2.1/css/tachyons.min.css"/>
- 7. Replace CSS in
index.css
/* index.css */
body {
margin: 0;
padding: 0;
font-family: Verdana, Geneva, sans-serif;
}
input {
max-width: 500px;
}
.gray {
color: #828282;
}
.orange {
background-color: #ff6600;
}
.background-gray {
background-color: rgb(246,246,239);
}
.f11 {
font-size: 11px;
}
.w85 {
width: 85%;
}
.button {
font-family: monospace;
font-size: 10pt;
color: black;
background-color: buttonface;
text-align: center;
padding: 2px 6px 3px;
border-width: 2px;
border-style: outset;
border-color: buttonface;
cursor: pointer;
max-width: 250px;
}
- 8. Add Apollo and GraphQL packages
yarn add @apollo/client graphql
That's it for the setup, we are now ready to start writing some code.
Into the code
index.js
- 1. Add packages to
index.js
.
import {
createHttpLink,
InMemoryCache,
ApolloClient,
ApolloProvider,
} from "@apollo/client";
- 2. Create variables to connect ApolloClient.
const httpLink = createHttpLink({
uri: 'http://localhost:4000'
})
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
})
- 3. Change out wrapper component around
<App />
to the Apollo Provider.
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
Server
The code to download the backend of the server was not correct on the tutorial. In order to get the correct version, I cloned the React-Apollo Tutorial Repo. Then, I copied the server folder and pasted it into the root of my project. This will add a directory called server to your application. Inside there are prisma files to connect to the database and inside the src folder is the GraphQL server files. We now need to deploy the Prisma database so the GraphQL server can access it.
cd server
yarn install prisma1 global
yarn install
prisma1 deploy
After running prisma1 deploy
navigate to Demo server + MySQL database, hit enter and then choose the location closest to you to create your database. Next, we need to run our backend locally. While still in the server directory run yarn start
and leave it running. Now we can run two mutations to check our connection to the database. Navigate to http://localhost:4000/ and paste in the following mutations.
mutation CreatePrismaLink {
post(
description: "Prisma turns your database into a GraphQL API 😎",
url: "https://www.prismagraphql.com"
) {
id
}
}
mutation CreateApolloLink {
post(
description: "The best GraphQL client for React",
url: "https://www.apollographql.com/docs/react/"
) {
id
}
}
Press the play button and select each mutation once. It should return an id. If this worked, we can verify the links were added by running the following query.
{
feed {
links {
id
description
url
}
}
}
It should return the json data with the id, description, and url of the 2 links.
Frontend
Now that the backend is working, we can implement the client side of the application. First, we are going to display a list of Link elements. Inside of the components directory, create a file named Link.js
and add the following code to it.
import React from 'react'
const Link = (props) => {
const link = props.link
return (
<div>
{link.description} ({link.url})
</div>
)
}
export default Link
This is a React component that is being passed props and then displaying the links from those props. Now we can create the component that will list the links. Add a new file in the components directory called LinkList.js
and put the following code inside. For now, we will just hard-code some data do display.
import React from 'react'
import Link from './Link'
const ListLinks = () => {
const links = [
{
id: '1',
description: 'Prisma turns your database into a GraphQL API 😎',
url: 'https://www.prismagraphql.com',
},
{
id: '2',
description: 'The best GraphQL client',
url: 'https://www.apollographql.com/docs/react/',
},
]
return (
<div>
{links.map(link => <Link key={link.id} link={link} />)}
</div>
)
}
export default ListLinks
Now to see the changes, we need to go to App.js
and change the contents to the following.
import React from 'react';
import ListLinks from './ListLinks'
import '../styles/App.css';
function App() {
return (
<div className="App">
<ListLinks />
</div>
);
}
export default App;
Now if we run yarn start
from the root directory, we should see the 2 links displayed on the screen.
GraphQL Query
Next, we'll need to actually query the database for the links stored so they are dynamic instead of hard-coded in. Head to LinkList.js
and we are going to change a few things.
- 1. Import new packages
import gql from 'graphql-tag'
import { useQuery } from '@apollo/client'
- 2. Underneath the imports add in LINK_QUERY and remove hard-coded links.
// export to be used later and create query for links
export const LINK_QUERY = gql`
{
feed {
links {
id
url
description
}
}
}
`
- 3. Destructure off the useQuery hook and update the return statement.
const ListLinks = () => {
const { loading, error, data } = useQuery(LINK_QUERY)
return (
<>
{/* IF LOADING */}
{loading && <div>Fetching...</div>}
{/* IF ERROR */}
{error && <div>There was an error fetching the data.</div>}
{/* ELSE RETURN DATA FROM QUERY */}
{data && (
<div>{data.feed.links.map(link =>
<Link key={link.id} link={link} />
)}
</div>
)}
</>
)
}
If this worked correctly, we should now have a page that has different states able to be seen on the screen. One while loading, one if there is an error, and the list of links being returned.
Mutations
To add new links to our list we need to add a new file in our components folder called CreateLink.js
that includes the following code.
import React, { useState } from 'react'
import { gql, useMutation } from "@apollo/client";
const LINK_MUTATION = gql`
mutation PostMutation($description: String!, $url: String!) {
post(description: $description, url: $url) {
id
url
description
}
}
`
const CreateLink = () => {
const [description, setDescription] = useState("")
const [url, setUrl] = useState("")
const [createLink] = useMutation(LINK_MUTATION)
return (
<div>
<div className="flex flex-column mt3">
<input
className="mb2"
value={description}
onChange={e => setDescription(e.target.value)}
type="text"
placeholder="A description for the link"
/>
<input
className="mb2"
value={url}
onChange={e => setUrl(e.target.value)}
type="text"
placeholder="The URL for the link"
/>
</div>
<button
onClick={() => {
createLink({
variables: {
description,
url
}
})
}}
>
Submit
</button>
</div>
)
}
export default CreateLink
This file includes the import to use gql and the useMutation hook, the GraphQL mutation, and some state to handle updating the url and description of the link. This can be tested by adding the component into App.js
below <ListLinks />
component.
import React from 'react';
import ListLinks from './ListLinks'
import CreateLink from './CreateLink';
import '../styles/App.css';
function App() {
return (
<div className="App">
<ListLinks />
<CreateLink />
</div>
);
}
export default App;
To actually see the update, the page needs to be refreshed or queried in the playground. To avoid this, we can add in React Router to the application to refresh the page.
React Router
Make sure you are in the root directory of the application and run the following command.
yarn add react-router react-router-dom
Now we need to add it to the application in index.js
.Import react-router-dom
and wrap the ApolloProvider
in the router.
import { BrowserRouter as Router } from 'react-router-dom'
ReactDOM.render(
<Router>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</Router>,
document.getElementById('root')
);
Header
Now, lets create a Header component to hold the links. In the components folder create a new file called Header.js
. The following code will import React and the Link component from react-router-dom and display a title and two links.
import React from 'react'
import { Link } from 'react-router-dom'
const Header = () => {
return (
<div className="flex pa3 justify-between nowrap orange">
<div className="fw7 mr1 black">Hacker News</div>
<div className='flex'>
<Link to="/" className="ml1 no-underline black">
new
</Link>
<div className="ml1 white">|</div>
<Link to="/create" className="ml1 no-underline black">
submit
</Link>
</div>
</div>
)
}
export default Header
To see the header, we need to add it into App.js
. We need to import the Header
and the Switch
and Route
components from react-router-dom
.
// add these imports
import { Switch, Route } from 'react-router-dom'
import Header from './Header'
// update App component to the following
function App() {
return (
<div className="center w85">
<Header />
<div className="ph3 pv1 background-gray">
<Switch>
<Route exact path="/" component={ListLinks} />
<Route exact path="/create" component={CreateLink} />
</Switch>
</div>
</div>
);
}
Last, we need to update the CreateLink
component so the browser will go back to the list after submitting a new link.
// add useHistory import and query to imports
import { LINK_QUERY } from './ListLinks'
import { useHistory } from "react-router-dom";
// initiate useHistory inside component
let history = useHistory();
// update cached links
const updateCache = (cache, { data }) => {
const currentLinksList = cache.readQuery({
query: LINK_QUERY
})
const updatedLinksList = [...currentLinksList.feed.links, data.post]
cache.writeQuery({
query: LINK_QUERY,
data: {
feed: {
__typename: "Feed",
links: updatedLinksList,
count: updatedLinksList.length
}
}
})
}
// update createLink variable
const [createLink] = useMutation(LINK_MUTATION, {
onCompleted: () => history.push("/"),
onError: () => history.push("/"),
update: updateCache
});
Now, the list of links and the create new link are on separate pages. You should have a page that looks similar to this.
Top comments (2)
When i try to console log data from useQuery it prints in console 3 times, and causes re render , why do you think that's happening?
It could be that the query is being used in the ListLinks component and in CreateLink component to check for updates. I had to do this to make the link show on url redirect without hard refreshing the browser. There is probably a more efficient way to do it with a useRef or useMemo, but I just wanted to get it working.