
0. Whom is this post for?
You know, it has been a trend making one’s own tech blog, especially not using the “boring” tools but coding your own web site. And as far as I know, Gatsby is the best option to make and manage your Tech Blog pretty easily. My Blog is also made with Gatsby (coded with TS, but using JS would be also fine) and GraphQL.
However, since Gatsby offers us to create a static web page, it is not easy to add a searching function to our blog. Everybody knows that we can’t send a query to Database, because we don’t even have a “proper” DB for our Gatsby blog.
So if there is anybody who wants to add a searchbox, here is a (potential) solution with some code examples!
1. Using Static query to render.
Let’s assume that you have a Search.tsx or Search.jsx where you defined your Search Page Component. I used static Query to render.
export default (props : any) => (
<StaticQuery
query={graphql`
query abc {
site {
siteMetadata {
title
description
siteUrl
}
}
allMarkdownRemark(
sort: {frontmatter: {date: DESC} }
) {
edges {
node {
id
fields {
slug
}
frontmatter {
title
summary
date(formatString: "YYYY.MM.DD.")
categories
thumbnail {
childImageSharp {
gatsbyImageData(width: 768, height: 400)
}
}
}
}
}
}
file(name: { eq: "profile-image" }) {
childImageSharp {
gatsbyImageData(width: 120, height: 120)
}
publicURL
}
}
`}
render={(data) => <Search data={data} {...props} />}
/>
);2. My Component
export type SearchPageProps = {
location: {
search: string
}
data: {
allMarkdownRemark: {
edges: PostListItemType[]
}
}
}
type SearchMeta = {
query : "",
filteredData : PostListItemType[]
}
const Search = (props : SearchPageProps) => {
const emptyQuery = "";
const [state, setState] = useState<SearchMeta>({
filteredData : [],
query: emptyQuery,
});
const handleInputChange = (event : any) => {
const query = event.target.value;
const { data } = props;
const posts = data.allMarkdownRemark.edges || [];
const filteredData : PostListItemType[] = posts.filter((post : PostListItemType) => {
const { summary, title, categories} = post.node.frontmatter;
return (
(summary &&
summary.toLowerCase().includes(query.toLowerCase())) ||
(title && title.toLowerCase().includes(query.toLowerCase())) ||
(categories && categories.join("").toLowerCase().includes(query.toLowerCase()))
);
});
setState({
query,
filteredData,
});
};
const renderSearchResults = () => {
const { query, filteredData } = state;
const hasSearchResults = filteredData && query !== emptyQuery;
const posts : PostListItemType[] = hasSearchResults ? filteredData : [];
return (
posts &&
<SearchPostList posts = {posts} />
);
};
return (
<div>
<input
type="text"
placeholder="Search"
aria-label="Search"
onChange={handleInputChange}
/>
{state.query && (
<div>
{renderSearchResults()}
</div>
)}
</div>
);
};Where the type “PostListItem” is simply a type which is required to render the list of posts. You might have your own “PostListItem” since you offer a page where you show multiple posts of yours.
3. Need something more?
Basically, it is a simple logic. My posts have generally three text components : summary, title and categories. Since the user types the keyword to find, the query will be set and and find the filtered posts.
const handleInputChange = (event : any) => {
const query = event.target.value;
const { data } = props;
const posts = data.allMarkdownRemark.edges || [];
const filteredData : PostListItemType[] = posts.filter((post : PostListItemType) => {
const { summary, title, categories} = post.node.frontmatter;
return (
(summary &&
summary.toLowerCase().includes(query.toLowerCase())) ||
(title && title.toLowerCase().includes(query.toLowerCase())) ||
(categories && categories.join("").toLowerCase().includes(query.toLowerCase()))
);
});
setState({
query,
filteredData,
});
};
Then, only the filtered posts are going to be rerendered and shown as a list, where the SearchPostList is my own component.
P.S : I didn’t make this entire logic, but I saw a guy who actually build this logic for his blog. But it wasn’t perfect by itself, thus, I changed some queries and logics, then I made it fit to my code (originally JS => TS) and to my own usage.
const renderSearchResults = () => {
const { query, filteredData } = state;
const hasSearchResults = filteredData && query !== emptyQuery;
const posts : PostListItemType[] = hasSearchResults ? filteredData : [];
return (
posts &&
<SearchPostList posts = {posts} />
);
};