DEV Community

loading...
Cover image for Updated with Hooks How To GraphQL with React Apollo

Updated with Hooks How To GraphQL with React Apollo

bdesigned profile image Brittney Postma Originally published at console-logs.netlify.app Updated on ・7 min read

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
Enter fullscreen mode Exit fullscreen mode
  • 2. Add git origin
git add remote origin URL
git add .
git commit -m 'init fresh repo'
git push --set-upstream origin master
Enter fullscreen mode Exit fullscreen mode
  • 3. Restructure app
mkdir src/components src/styles
Enter fullscreen mode Exit fullscreen mode
  • 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';
Enter fullscreen mode Exit fullscreen mode
// App.js
import logo from '../logo.svg';
import '../styles/App.css';
Enter fullscreen mode Exit fullscreen mode
  • 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"/>
Enter fullscreen mode Exit fullscreen mode
  • 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;
}
Enter fullscreen mode Exit fullscreen mode
  • 8. Add Apollo and GraphQL packages
yarn add @apollo/client graphql
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode
  • 2. Create variables to connect ApolloClient.
const httpLink = createHttpLink({
  uri: 'http://localhost:4000'
})
const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache()
})
Enter fullscreen mode Exit fullscreen mode
  • 3. Change out wrapper component around <App /> to the Apollo Provider.
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode
  • 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
    }
  }
}
`
Enter fullscreen mode Exit fullscreen mode
  • 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>
      )}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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')
);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
  });
Enter fullscreen mode Exit fullscreen mode

Now, the list of links and the create new link are on separate pages. You should have a page that looks similar to this.

Hackernews Clone Page

Discussion (2)

pic
Editor guide
Collapse
ajaypatel profile image
FoodBite

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?

Collapse
bdesigned profile image
Brittney Postma Author

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.