In this task, we will create custom hooks to fetch and mutate data in the Posts app. We will refactor the components to use these custom hooks for data management.
Step 1: The opportunity for custom hooks
Consider the handleDelete operation in the DeletePostDialog component:
This operation involves deleting a post by calling the API (deletePost function) and then updating the state (calling the removePost function). This pattern is repeated in other components as well, to create or update posts.
We can abstract this pattern into a custom hook that handles the API calls and state updates. This will make our components cleaner and more maintainable.
Custom hooks in React
Custom hooks are JavaScript functions whose names start with use and may call other hooks. They allow you to reuse stateful logic across components. Custom hooks can be used to encapsulate complex logic and provide a clean interface for components to interact with.
Here is an example of a custom hook that prints a count to the console:
In this example:
We use the useState hook to create a count state.
We use the useEffect hook to log the count to the console whenever it changes.
We define increment and decrement functions to update the count state.
We return the count, increment, and decrement functions from the custom hook.
Here is how you can use this custom hook in a component:
The useCounter custom hook encapsulates the logic for managing the count state and provides a clean interface for components to interact with it. We will follow a similar pattern to create custom hooks for fetching and mutating data in the Posts app.
Step 2: Create a custom hook to fetch data
We’ll start by creating a custom hook to fetch data. This hook will handle the API call to fetch posts and interacts with the store to update the state.
Create a new file use-query-posts.tsx in the web/src/hooks directory:
In this custom hook:
We use the useStore hook from @nanostores/react to access the store state.
We define a loadPosts function that fetches posts using the fetchPosts API call and updates the store state using the setPosts function.
We use the useEffect hook to call loadPosts when the component mounts.
We return the posts state to be used in the components.
We handle any errors that occur during the API call. (Error handling can be improved further.)
The term “query” in the custom hook name useQueryPosts is inspired by GraphQL, where queries are used to fetch data. This custom hook encapsulates the logic for fetching posts and provides a clean interface for components to interact with the posts state.
Step 3: Update the Posts component to use the custom hook
Now, we’ll update the Posts component to use the useQueryPosts custom hook to fetch posts.
In this update:
We import the useQueryPosts custom hook.
Instead of reading the posts state directly from the store, we use the useQueryPosts custom hook to fetch and manage the posts state.
We removed unnecessary imports and code related to fetching posts (the useEffect hook) as this logic is now handled by the custom hook.
Step 4: Create a custom hook to mutate data
Create a new file use-mutation-posts.tsx in the web/src/hooks directory:
In this custom hook:
We define functions to delete a post, add a new post, and update an existing post.
Each function calls the corresponding API function and updates the store state accordingly.
We handle any errors that occur during the API calls.
We return the functions to be used in the components.
The term “mutation” in the custom hook name useMutationPosts is inspired by GraphQL, where mutations are used to modify data. This custom hook encapsulates the logic for mutating posts and provides a clean interface for components to interact with the posts state.
Step 5: Update the components to use the custom hook
Update the DeletePostDialog component
In this update:
We import the useMutationPosts custom hook.
Instead of calling the deletePost API function and updating the store directly, we use the deletePostById function from the custom hook to delete the post.
We removed the unnecessary imports and code related to deleting posts.
Update the AddPost component
In this update:
We import the useMutationPosts custom hook.
Instead of calling the createPost API function and updating the store directly, we use the addNewPost function from the custom hook to add a new post.
We removed the guard clause for content validation as it is now handled by the custom hook.
We removed the unnecessary imports and code related to creating posts.
Update the EditPost component
In this update:
We import the useMutationPosts custom hook.
Instead of calling the editPost API function and updating the store directly, we use the updatePost function from the custom hook to update the post content.
We removed the guard clause for content validation as it is now handled by the custom hook.
We removed the unnecessary imports and code related to editing posts.
Step 6: Error handling in custom hooks
We have added basic error handling in the custom hooks to log errors to the console. You can enhance the error handling further by providing feedback to the user when an error occurs. For example, you can display an error message in a toast notification or an alert dialog.
Add a Toast UI component
Let’s add the Toast component from Shadcn UI to display error messages:
Add a Toast Provider to the App component
Next, you’ll need to add the Toaster component to the App component to display error messages.
The Toaster component will display the error messages as toast notifications. It must be placed at the root level of the application to ensure that it is accessible from any component.
Update the useQueryPosts custom hook
Now update the useQueryPosts custom hook to display an error message in the toast component when an error occurs during the API call:
Update the useMutationPosts custom hook
Similarly, update the useMutationPosts custom hook to display an error message in the toast component when an error occurs during the API call:
Test the error handling
You can test the error handling by deliberately causing an error such as trying to add a new post with an empty content. You should see the error message displayed in the toast component.
Conclusion
In this task, we created custom hooks to fetch and mutate data in the Posts app. We refactored the components to use these custom hooks for data management, making our code cleaner and more maintainable. We also added basic error handling to the custom hooks and displayed error messages using toast notifications.