29 Nov 2018 • 5 min read
I was recently working on my portfolio site, and I wanted to easily pull in the site title with a graphQL query. After a bunch of trial and error, I ended up using StaticQuery
to get the data and created a wrapper around react-helmet
to make setting the title easy. However, I wasn’t able to find any great resources for this in Typescript––here’s how to do all of this with Typescript.
Update: Gatsby recently added a React hook to do this with less code. Scroll to the bottom to see how to use the hook.
As part of my search for information on this, I came across some excellent documentation on using StaticQuery in Gatsby’s docs (worth a read if you want more background). The initial code sample they provided was this:
import React from 'react';
import {StaticQuery, graphql} from 'gatsby';
export default () => (
<StaticQuery
query={graphql`
query HeadingQuery {
site {
siteMetadata {
title
}
}
}
`}
render={(data) => (
<header>
<h1>{data.site.siteMetadata.title}</h1>
</header>
)}
/>
);
This code:
Then the author gets us a little bit closer with propTypes
:
import React from 'react';
import {StaticQuery, graphql} from 'gatsby';
import PropTypes from 'prop-types';
const Header = ({data}) => (
<header>
<h1>{data.site.siteMetadata.title}</h1>
</header>
);
export default (props) => (
<StaticQuery
query={graphql`
query {
site {
siteMetadata {
title
}
}
}
`}
render={(data) => <Header data={data} {...props} />}
/>
);
Header.propTypes = {
data: PropTypes.shape({
site: PropTypes.shape({
siteMetadata: PropTypes.shape({
title: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};
Now the code has a better separation of concerns, which allows us to add propTypes
to our Header object.
Let’s re-write this to use Typescript, and also add a className
prop that we can pass through to our title. First, our imports:
import {graphql, StaticQuery} from 'gatsby';
import * as React from 'react';
Next, we’ll define two interfaces: one for props we want to pass into this component, and one for the data. We’re going to extend the first interface (HeaderProps
) in our second interface (HeaderPropsWithData
), so that we have a comprehensive representation of our props types:
interface HeaderProps {
className: string;
}
interface HeaderPropsWithData extends HeaderProps {
data: {
site: {
siteMetadata: {
title: string,
},
},
};
}
Now that we have an interface, let’s use it in our Header
component. I’m using a stateless functional component here, but you could use a regular component, too. We’re extending the HeaderPropsWithData
so that we have type definitions for both user-defined and graphQL-provided props.
const Header: React.SFC<HeaderPropsWithData> = ({className, data}) => {
return (
<header>
<h1 className={className}>{data.site.siteMetadata.title}</h1>
</header>
);
};
Now that we have our Header
function defined, let’s declare our export and our StaticQuery
:
export default (props: HeaderProps) => (
<StaticQuery
query={graphql`
query {
site {
siteMetadata {
title
}
}
}
`}
// tslint:disable-next-line jsx-no-lambda
render={(data) => <Header data={data} {...props} />}
/>
);
I’m using HeaderProps
as the type definition for props
, which allows us to define things that the user can pass to this function (like className
). We wouldn’t want to use HeaderPropsWithData
because it would throw an error about missing the data object.
I’m also disabling TSLint because TSLint doesn’t like it when we pass a lambda to a render function. This can result in performance issues, but because this is using Gatsby and it is generating a static site for us, we can just ignore that error here.
If you’re just looking for the finished code, I decided to stick it in a Github gist. And if you happen to have the exact use case as me, you’re welcome to look at the wrapper class I created for react-helmet
.
Since the time I originally wrote this, Gatsby released a React hook that simplifies using StaticQuery. Here's the same example from before, but rewritten using the new useStaticQuery
hook.
// 1
import {graphql, useStaticQuery} from 'gatsby';
import * as React from 'react';
interface HeaderProps {
className: string;
}
// 2
interface HeaderData {
site: {
siteMetadata: {
title: string,
},
};
}
// 3
const Header: React.SFC<HeaderProps> = ({className}) => {
// 4
const data: HeaderData = useStaticQuery(graphql`
query {
site {
siteMetadata {
title
}
}
}
`);
return (
<header>
<h1 className={className}>{data.site.siteMetadata.title}</h1>
</header>
);
};
// 5
export default Header;
Here are the changes from the original version:
useStaticQuery
instead of StaticQuery
.Header
function directly uses HeaderProps
as its interface.data
object is declared and uses HeaderData
as its interface. The output of the useStaticQuery
hook (with the graphql statement) is then assigned to the data
object.StaticQuery
within an anonymous function, I can directly export the Header
component.Using the hook makes static queries within components cleaner and easier to follow.
Get my posts in your feed of choice. Opt-out any time.
If you enjoyed this article, you might enjoy one of these: