Creating a React hook and fetching the comments
In this lesson, we'll create a React hook that will fetch the comments from the database. We'll cover how to create a custom hook as well as how to integrate with Hasura.
In the previous lesson, we introduced a sample blog application that will be our
use case for a commenting system. We saw a pages/posts/[slug].tsx
file which
contains a Post
component. This component is responsible for rendering posts'
content and details, and that's where we'll add a comments section and a form to
add a new comment.
What should we do there? When a user visits a particular post, for example, the
first one — dynamic-routing
, we need to fetch the post's comments. That means
we need to fetch those entries from the table comments
that have topic
set
to dynamic-routing
.
Note: This course assumes a basic understanding of React hooks — we will work with
useEffect
anduseState
hooks, and we'll create a custom one. If you want to revise your knowledge, we recommend checking out the officialReact docs.
Fetching the comments — requirements#
Let's open the [slug].tsx
file and list what should happen there:
On component load, we need to fetch comments added for a particular blog post from our Hasura instance.
We need a few state variables to store information:
comments
— at first, it's an empty list, which we will update after successfully fetching the comments,loading
— a boolean variable indicating whether fetching is in progress,error
— a variable indicating that there was an error when fetching comments. By default, it will benull
. If an error occurs, we'll set it to a string with an error message.
Let's write it down to help us visualize it in the code:
const Post = ({ post, morePosts, preview }: Props) => {
const [comments, setComments] = useState([]);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
// fetch comments
// on success: setComments(result)
// on error: setError(error)
}, []);
// ...
};
We could keep the fetching comments logic and state variables in the[slug].tsx
file, and that would be totally fine. However, we will extract it
into a separate custom hook, which will let us test the hook separately and
reuse it in other components.
Implementing the hook#
A note fromReact docs on implementing custom hooks:
Unlike a React component, a custom Hook doesn't need to have a specific signature. We can decide what it takes as arguments, and what, if anything, it should return. In other words, it's just like a normal function. Its name should always start with "use" so that you can tell at a glance that the rules of Hooks apply to it.
As hooks should have use
prefix, and our subject is comments, we will call our
hook useComments
. Let's create a new file in the lib/
directory —useComments.ts
.
Instead of going straight to writing implementation details, we'll first "model" the hook with TypeScript types. When we focus on types without being distracted by the implementation, we're more likely to consider multiple ideas and compare the alternatives. It allows us to stop for a moment and think about what we need, what the ideal API is, and how we'd like to use it.
Let's start from something that is kind of the base of our commenting system — aComment
type. In the previous module, we created a table comments
, so the
type should map the database model. Here's how it looks in TypeScript:
export interface Comment {
topic: string;
author: string;
content: string;
created_at: string;
}
Notice that
created_at
is typed as a string. It's because we get an ISOString from a GraphQL API. It's not being transformed into JavaScript'sDate
, even though its type istimestamptz!
in the schema spec:GraphQL comes with default scalar types like
Int
,Float
,String
,Boolean
, andID
, but it also allows defining custom scalar types.timestamptz!
is one of the examples, and it's being sent as a string from the API. On the client, we can manually parse it to aData
type.However, most popular GraphQL clients does not currently have a way to automatically interpret custom scalars, so there's no way to automatically reverse the serialization on the client.
As we have the Comment
type, let's write down the hook function signature. Our
hook will take two arguments:
hasuraUrl
— an URL to our Hasura backend. We could "hide" it inside the hook implementation, but this way, we can make the hook more generic — we can reuse it to connect to different Hasura instances.topic
— an identifier of content, e.g.,"my-blogpost-about-cats"
.
The hook returns an object, and I'll call its type UseCommentsResult
.
declare const useComments: (
hasuraUrl: string, // URL to our Hasura backend
topic: string // e.g. "my-blogpost-about-cats"
) => UseCommentsResult;
We use
declare
keyword to tell the compiler about types before we write any implementation.
Now, let's create UseCommentsResult
type. What information do we need from the
hook?
Most importantly, comments — an array of objects of type
Comment
.loading
— as we mentioned earlier, it will indicate whether the comments are being fetched.error
— a variable with an error message.
interface UseCommentsResult {
comments: Comment[];
loading: boolean;
error: string | null;
}
And here we go! We can proceed to implementation details now.
Implementation details#
Firstly, let's declare all the state variables that we mentioned before:
This page is a preview of The newline Guide to Full Stack Comments with Hasura and React