I'm going to show you how to integrate a React component that uses GraphQL to fetch data from Github. We are working from an existing application so we will go through the process of refactoring the code to handle new functionality.
Specifically, we are working on a chat application that I built in my egghead course React real-time messaging with graphql using urql and onegraph. At the end of the course, we have a single chat feed that will update in real-time when new messages come in. Heres what the app looks like:
In this post, we are going to implement the ability to choose from multiple conversations. The application has hard coded values we need to refactor so we can accept props from a parent component. Controlling our components through props gives us the ability to dynamically switch what conversation the user is viewing.
Course Summary
We learned how to use urql's useQuery React hook to fetch data about the comments on a specific Github issue. We us OneGraph as a backend. OneGraph has provided has an auth package that we build into our app with React Context.
urql requires a little bit of set up. We create an urqlClient that we put into React context so our query hooks can use them. We then set up onegraph-auth and pass the authentication headers we get from the onegraph-auth package to our urql client. This way we can fetch Github queries which require an authenticate user.
We need a way for our chat app to send messages. GraphQL mutations handle this for us. With mutations, we can create comments on a specific github issue.
Finally, we implement a subscription client in urql. This enables the useSubscription hook and allows us to see new messages without refreshing the browser!
We are using Github as our backend database to store our messages. We use OneGraph as our GraphQL server for our urql client to send queries to. OneGraph will then take care of sending queries to Github and fetching the responses we ask for.
Set up the code
We will be working from code in this Github repository. You can clone the project with this:
git clone git@github.com:theianjones/egghead-graphql-subscriptions.git
# or
git clone https://github.com/theianjones/egghead-graphql-subscriptions.git
cd egghead-graphql-subscriptions
When the repository is cloned, you'll want to work out of the 20-commentHistory directory.
cd 20-commentHistory
Now you'll need to install package dependencies:
yarn
# or
npm install
Build the Issue Query in the graphiql editor
First, we need to navigate to OneGraph. Log in. Now select an existing app or create a new one.
Head over to the data explorer tab. This is where OneGraph's GraphiQL editor lives. We're going to build out our query to get all of the issues associated with the egghead-graphql-subscriptions github repo.
Heres what our GraphQL query will look like:
query IssueList(
$name: String = "egghead-graphql-subscriptions" // these are GraphQL Query parameters
$owner: String = "theianjones" // this is how we can pass variables to our GraphQL query
) {
gitHub {
repository(name: $name, owner: $owner) { // this is the part of the query that needs the variables we declared
id // We grab the id of the repository for urql to do better caching
issues(
first: 10
orderBy: { field: CREATED_AT, direction: DESC } // we can order our query be a specific field
) {
edges {
cursor // cursors allow you to paginate your query results
node {
id
title
comments(last: 1) { // we want the last comment to display under our issue title
totalCount
nodes {
id
bodyText
}
}
}
}
}
}
}
}
Pro tip: when you hover over the $name and $owner you can have the editor paramiterize the query for you:
Before you run the query, you are going to have to authorize your editor with GitHub. To do this, click the Authentication drop down and select "Log in with GitHub"
When you run the query, you should get some data back!
Now that we have a query, it's time to generate our code. OneGraph has a code snippet generation tool that will create the react and urql code for us 🤯 You'll notice there's quite a few options to choose from so if you want to quickly try out Apollo or even ReasonML with GraphQL, you can!
Click "Code Exporter". Now, in the two drop downs, select JavaScript in the first and react-urql in the second. Click the copy button to grab all the code that the exporter generated for us.
Integrate OneGraph generated snippet
We are going to create an IssueList component. Create a file in our components directory: src/components/IssueList.js.
touch src/components/IssueList.js
Paste the snippet we generated in the last step. You'll notice theres urql client code that we already have present in src/index.js so we can go ahead and delete all of that code.
Heres what we have left:
import React from "react";
import { useQuery } from 'urql'
const ISSUE_LIST = `
query IssueQuery($name: String = "egghead-graphql-subscriptions", $owner: String = "theianjones") {
gitHub {
repository(owner: $owner, name: $name) {
id
issues(first: 10, orderBy: {field: CREATED_AT, direction: DESC}) {
edges {
cursor
node {
id
number
title
comments(last: 1) {
totalCount
nodes {
id
bodyText
}
}
}
}
}
}
}
}
`;
const IssueList = (props) => {
const [{ data, fetching, error }, reexecuteQuery] = useQuery({
query: ISSUE_LIST,
variables: {"name": props.name, "owner": props.owner}});
if (fetching) return <pre>Loading...</pre>;
const dataEl = data ? <pre>{JSON.stringify(data, null, 2)}</pre> : null;
const errorEl = error ? (
<div className="error-box">
Error in IssueQuery. <br />
{error.message && error.message.startsWith('[Network]') ? <span>Make sure <strong>{window.location.origin}</strong> is in your CORS origins on the <a href={`https://www.onegraph.com/dashboard/app/${APP_ID}?add-cors-origin=${window.location.origin}`}>OneGraph dashboard for your app</a>.</span> : null}
<pre>
{JSON.stringify(error, null, 2)}
</pre>
</div>
) : null;
const needsLoginService = auth.findMissingAuthServices(error)[0];
return (
<div>
{dataEl}
{errorEl}
<br />
<button
onClick={async () => {
if (!needsLoginService) {
reexecuteQuery({requestPolicy: 'cache-and-network'});
} else {
await auth.login(needsLoginService);
const loginSuccess = await auth.isLoggedIn(needsLoginService);
if (loginSuccess) {
console.log("Successfully logged into " + needsLoginService);
reexecuteQuery({requestPolicy: 'cache-and-network'});
} else {
console.log("The user did not grant auth to " + needsLoginService);
}
}
}}>
{needsLoginService ? `Log in to ${needsLoginService}` : 'Run query: IssueQuery'}
</button>
</div>
);
};
First, we need to add export default IssueList at the bottom of the file. To get things rendering, lets import APP_ID and auth from ../utils/auth
import {APP_ID, auth} from '../utils/auth'
Now we can import our IssueList component in src/App.js. Add it right above the div:
//...
+import IssueList from './components/IssueList'
function App() {
const {login, status} = React.useContext(AuthContext)
if (!status || !status.github) {
return (
<div>
<h1>Log in to github</h1>
<p>In order to see your profile, you'll have to log in with Github.</p>
<button onClick={() => login('github')}>Log in with Github</button>
</div>
)
}
return (
<div className="App">
<header className="App-header">
+ <IssueList/>
<div style={{minWidth: 400}}>
<Comments />
<Input />
</div>
</header>
</div>
)
}
//...
The result isn't pretty, but we have the data we need to start rendering JSX!
Display JSON Data In a List
In the previous section, we were displaying the raw json in the ui. This is great to make sure everything is working but most people would be confused if we left JSON on the screen like that. Lets render some JSX 🤩
Back in our IssueList component, we can see the structure of our data that we will need to map over: data.gitHub.repository.issues.edges.
Lets create a view component that takes each issue node, and renders it as an li:
Next we want to add some styles to our IssueListItem component. We want the title of the issue to stand out. We have the first message of our issue as well so we should dim it but make it visible.
It would be nice to have the background color change when we hover over a list item. We dont want to pull in a whole css library to do this, so lets make a useHover hook to keep track of that state.
We apply the styles that are returned to the style prop on our li. Then we take the hoverProps object that contains the onMouse events and we add those functions to our li as well. Last thing we'll do in this component is remove the run query button.
//...
<div>
{dataEl}
{errorEl}
<br />
- <button
- onClick={async () => {
- if (!needsLoginService) {
- reexecuteQuery({requestPolicy: 'cache-and-network'});
- } else {
- await auth.login(needsLoginService);
- const loginSuccess = await auth.isLoggedIn(needsLoginService);
- if (loginSuccess) {
- console.log("Successfully logged into " + needsLoginService);
- reexecuteQuery({requestPolicy: 'cache-and-network'});
- } else {
- console.log("The user did not grant auth to " + needsLoginService);
- }
- }
- }}>
- {needsLoginService ? `Log in to ${needsLoginService}` : 'Run query: IssueQuery'}
- </button>
</div>
Now, we can head over to src/App.js and get the message list rendering on the side.
We have 2 conversations to choose from: "Discuss GraphQL" and "egghead chat". We want to auto select the first conversation that loads in the list. Based on this conversation, we want to load the whole chat history for that conversation.
What this tells me is that we need to add a onLoaded prop to the IssueList component so that we can store the issue ids in our app component.
In our src/App.js component, lets add a hook to hold our issue ids.
We are going to initialize the state to an empty array. Now we need to pass an onLoaded prop to our <IssueList/> component. This will be a function that takes the result of our query and plucks the issue ids off of the result.
You can see that we are destructuring the issueNumber from the props and passing this variable to our useCommentsHistory hook. This is the hook that loads the existing comments for the chat. We have to modify it to make sure it's not hard coded anymore.
Now when you load the page, the conversation should be rendering! Lets head back up to our src/App.js to update our <Input /> component. Our input mutation need the subjectId which is not the issue number. This means that we need more than the currentIssueNumber and issueNumbers.
Lets refactor our code to set the current issue:
function App() {
const {login, status} = React.useContext(AuthContext)
+ const [issues, setIssues] = React.useState([])
- const [issueNumbers, setIssueNumbers] = React.useState([])
+ const [currentIssue, setCurrentIssue] = React.useState()
- const [currentIssueNumber, setCurrentIssueNumber] = React.useState()
if (!status || !status.github) {
return (
<div>
<h1>Log in to github</h1>
<p>In order to see your profile, you'll have to log in with Github.</p>
<button onClick={() => login('github')}>Log in with Github</button>
</div>
)
}
const handleIssueListLoaded = (data) => {
- const issues = data?.gitHub.repository.issues
- const issueNumbers = issues.edges.map(({node: issue}) => issue.number)
- if(issueNumbers.length > 0){
- setCurrentIssueNumber(issueNumbers[0])
- }
+ const issues = data?.gitHub.repository.issues.edges.map(e => e.node)
+ setIssues(issues)
+ setCurrentIssue(issues[0])
}
We need to keep track of the whole issue because our two components need different data off of each. This is a good thing to keep in mind when you are designing your state in your components. Lets update our view now:
When you test the UI out, you will notice that nothing is changing. Thats because we are pausing our urql query in Comments. We can force a render when the currentIssue changes by adding a key prop to each of the components:
I'm going to show you how to integrate a React component that uses GraphQL to fetch data from Github. We are working from an existing application so we will go through the process of refactoring the code to handle new functionality.
Specifically, we are working on a chat application that I built in my egghead course React real-time messaging with graphql using urql and onegraph. At the end of the course, we have a single chat feed that will update in real-time when new messages come in. Heres what the app looks like:
In this post, we are going to implement the ability to choose from multiple conversations. The application has hard coded values we need to refactor so we can accept props from a parent component. Controlling our components through props gives us the ability to dynamically switch what conversation the user is viewing.
Course Summary
We learned how to use
urql
'suseQuery
React hook to fetch data about the comments on a specific Github issue. We us OneGraph as a backend. OneGraph has provided has an auth package that we build into our app with React Context.urql
requires a little bit of set up. We create anurql
Client
that we put into React context so our query hooks can use them. We then set uponegraph-auth
and pass the authentication headers we get from theonegraph-auth
package to oururql
client. This way we can fetch Github queries which require an authenticate user.We need a way for our chat app to send messages. GraphQL mutations handle this for us. With mutations, we can create comments on a specific github issue.
Finally, we implement a subscription client in
urql
. This enables theuseSubscription
hook and allows us to see new messages without refreshing the browser!We are using Github as our backend database to store our messages. We use OneGraph as our GraphQL server for our
urql
client to send queries to. OneGraph will then take care of sending queries to Github and fetching the responses we ask for.Set up the code
We will be working from code in this Github repository. You can clone the project with this:
When the repository is cloned, you'll want to work out of the
20-commentHistory
directory.Now you'll need to install package dependencies:
Build the Issue Query in the graphiql editor
First, we need to navigate to OneGraph. Log in. Now select an existing app or create a new one.
Head over to the data explorer tab. This is where OneGraph's GraphiQL editor lives. We're going to build out our query to get all of the issues associated with the egghead-graphql-subscriptions github repo.
Heres what our GraphQL query will look like:
Pro tip: when you hover over the
$name
and$owner
you can have the editor paramiterize the query for you:Before you run the query, you are going to have to authorize your editor with GitHub. To do this, click the
Authentication
drop down and select "Log in with GitHub"When you run the query, you should get some data back!
Now that we have a query, it's time to generate our code. OneGraph has a code snippet generation tool that will create the react and urql code for us 🤯 You'll notice there's quite a few options to choose from so if you want to quickly try out Apollo or even ReasonML with GraphQL, you can!
Click "Code Exporter". Now, in the two drop downs, select
JavaScript
in the first andreact-urql
in the second. Click the copy button to grab all the code that the exporter generated for us.Integrate OneGraph generated snippet
We are going to create an
IssueList
component. Create a file in our components directory:src/components/IssueList.js
.Paste the snippet we generated in the last step. You'll notice theres urql client code that we already have present in
src/index.js
so we can go ahead and delete all of that code.Heres what we have left:
First, we need to add
export default IssueList
at the bottom of the file. To get things rendering, lets importAPP_ID
andauth
from../utils/auth
Now we can import our
IssueList
component insrc/App.js
. Add it right above the div:The result isn't pretty, but we have the data we need to start rendering JSX!
Display JSON Data In a List
In the previous section, we were displaying the raw json in the ui. This is great to make sure everything is working but most people would be confused if we left JSON on the screen like that. Lets render some JSX 🤩
Back in our
IssueList
component, we can see the structure of our data that we will need to map over:data.gitHub.repository.issues.edges
.Lets create a view component that takes each issue node, and renders it as an
li
:Then we can change the
dataEl
variable to be aul
that maps the issues as its children:Notice that we are destructuring
node
off in the map function.Style Issue List
First we are going to remove the defualt padding
ul
's have:Next we want to add some styles to our
IssueListItem
component. We want the title of the issue to stand out. We have the first message of our issue as well so we should dim it but make it visible.It would be nice to have the background color change when we hover over a list item. We dont want to pull in a whole css library to do this, so lets make a
useHover
hook to keep track of that state.With this hook, you pass in the styles you want applied when the mouse is over our element. We put in a transition to make the hover a little nicer.
Now we can get the hover styles and the props we need to apply to our list items by destructuring the return array.
We apply the styles that are returned to the style prop on our
li
. Then we take thehoverProps
object that contains theonMouse
events and we add those functions to ourli
as well. Last thing we'll do in this component is remove the run query button.Now, we can head over to
src/App.js
and get the message list rendering on the side.Since we've made some changes to how our widths and heights are working in this component, we need to adjust the styles in
<Comments/>
and<Input/>
.We need to adjust the
ul
in<Comments/>
:And in
<Input/>
we need to adjust thebutton
styles.Switch Conversations on Selection
We have 2 conversations to choose from: "Discuss GraphQL" and "egghead chat". We want to auto select the first conversation that loads in the list. Based on this conversation, we want to load the whole chat history for that conversation.
What this tells me is that we need to add a
onLoaded
prop to theIssueList
component so that we can store the issue ids in our app component.In our
src/App.js
component, lets add a hook to hold our issue ids.We are going to initialize the state to an empty array. Now we need to pass an
onLoaded
prop to our<IssueList/>
component. This will be a function that takes the result of our query and plucks the issue ids off of the result.We need to tell our
<Comments/>
and<Input/>
components what thecurrentIssueNumber
is. Let's create anotherReact.useState
hook:It doesn't need an initial value because we dont know what issue number will come back.
First, lets protect our jsx from invalid renders. We need to make sure that
currentIssueNumber
is set before we render<Comments/>
and<Input/>
.Now, when the issue list loads, we have an issue number that we can set:
We make sure that there are more than 0 issues before setting the current one. We can pass the current issueNumber down into our components.
Lets head over to
src/components/CommentsSubscription.js
to update our hook thats fetching our comments data.You can see that we are destructuring the
issueNumber
from the props and passing this variable to ouruseCommentsHistory
hook. This is the hook that loads the existing comments for the chat. We have to modify it to make sure it's not hard coded anymore.Now when you load the page, the conversation should be rendering! Lets head back up to our
src/App.js
to update our<Input />
component. Our input mutation need thesubjectId
which is not the issue number. This means that we need more than the currentIssueNumber and issueNumbers.Lets refactor our code to set the current issue:
We need to keep track of the whole issue because our two components need different data off of each. This is a good thing to keep in mind when you are designing your state in your components. Lets update our view now:
Now we can update our
Input
component:And finally, we can add an
onClick
handler to update what current issue we are viewing! Inside ofsrc/App.js
:When you test the UI out, you will notice that nothing is changing. Thats because we are pausing our urql query in
Comments
. We can force a render when thecurrentIssue
changes by adding akey
prop to each of the components:This adds the functionality for switching conversations. If you go over to github and create a new issue, when you reload, you'll see it show up!