As developers, we often need to write complex code to fulfill requirements. However, there’s always a chance that our code may run into errors that prevent it from working as expected.
Traditionally, we use debuggers with IDEs or browser dev tools to debug apps, but when it comes to custom React Hooks, these debugging methods are not very effective. Therefore, we need a different way of debugging custom React Hooks.
This article addresses how to debug custom React Hooks with the useDebugValue Hook.
What is useDebugValue?
useDebugValue is a React Hook that allows developers to effectively debug their custom Hooks by assigning custom formatted labels to them. Developers can observe these labels with the help of React Dev Tools, which provide insight into the internal logic of the custom Hook to understand its process.
Defining a custom Hook
Before using the useDebugValue Hook, let us first try debugging a custom Hook without anything else.
To do so, let us define a custom Hook (useFetch) that fetches data from a given endpoint.
The following code example explains the custom Hook.
import { useCallback, useEffect, useState } from "react"; export const useFetch = (url: string) => { const [response, setResponse] = useState<any>(); const [error, setError] = useState<any>(); const getData = useCallback(async () => { try { const resp = await fetch(url, { method: 'GET', mode: 'cors', headers: { 'Content-Type': 'application/json' }, }); const data = await resp.json(); setResponse(data); setStatus(data.success); } catch (err) { setError(err); } }, [url]); useEffect(() => { getData(); }, [getData, url]); return { response, error }; }
This example illustrates the custom React Hook that fetches data from a given endpoint. It has two state variables responsible for holding the response and the error returned from the API. Finally, it returns a JavaScript object to the consumer.
Further, we will consume this Hook in the App component to obtain COVID-19 statistics using the COVID-19 API. The following code example elaborates on the use of the custom useFetch Hook.
import React from 'react'; import logo from './logo.svg'; import './App.css'; import { useFetch } from './use-fetch'; function App() { const fetchedResponse = useFetch('https://www.hpb.health.gov.lk/api/get-current-statistical'); return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.tsx</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;
When this Hook is observed in React Dev Tools, the state variables and their flow during the Hook’s execution are shown.
In addition, there are a few other issues in monitoring Hooks with React DevTools:
- The content is not readable. To analyze the data variable (output provided by the Hook), we have to break the object tree.
- The content is not understandable at first glance. The Hook and its output data don’t have a label to provide meaningful information.
- The Fetch Hook has no description on it. This makes it hard to understand its purpose.
- React Dev Tools only provide meaningful information on the state variables used by the Hook, not the code written.
This is where useDebugValue comes into play. With its help, we can improve the visibility and overall understandability of the code executed within the Hook by providing custom formatted labels.
Using the useDebugValue Hook
Step 1: Importing the Hook
You can easily import the useDebugValue Hook from React. Refer to the following code.
import { useDebugValue } from "react";
Step 2: Calling the Hook
We can’t invoke the useDebugValue Hook inside another React Hook except within a custom Hook.
To start, observe the response state variable by calling the Hook initially and providing the formatting to it so that it is easily interpreted by anyone observing the logs for our custom Hook in React Dev Tools. To do so, invoke the Hook using useDebugValue(). The Hook requires an object of any type to be passed as the first parameter. Therefore, the response state variable is passed with a message.
Refer to the following code example.
import { useCallback, useDebugValue, useEffect, useState } from "react"; export const useFetch = (url: string) => { const [response, setResponse] = useState<any>(); const [status, setStatus] = useState<boolean>(false); const [error, setError] = useState<any>(); useDebugValue('Success State of API Call: ' + status); useDebugValue('Error State of API Call: ' + error); useDebugValue('Data Returned From API Call: ' + response); const getData = useCallback(async () => { try { const resp = await fetch(url, { method: 'GET', mode: 'cors', headers: { 'Content-Type': 'application/json' }, }); const data = await resp.json(); setResponse(data); setStatus(data.success); } catch (err) { setError(err); } }, [url]); useEffect(() => { getData(); }, [getData, url]); return { response, error }; }
We see a new state variable, status, introduced when observing the updated Hook. This is because React does not allow useDebugValue to be called inside another Hook like useEffect. So, to access the HTTP response status, another state variable must be used.
Step 3: Observing the results with the Hook
When the Hook is called and observed in React Dev Tools after setting up useDebugValue, the figure shown below can be observed.
There, we see a new DebugValue array. This array outputs all the data we wish to debug in a more readable manner.
Note: The indexes of the DebugValue array are printed in sequential order of the calls made to the useDebugValue Hook.
Optimizing the useDebugValue Hook
Now, the question is, do we need formatting all the time? Isn’t it costly to format data every time a Hook is called or a state within a Hook is changed? The answer to the latter is yes.
However, React has a solution for that, and it is easier than you think! The Hook accepts a second parameter. This parameter is a formatting function that accepts the log data and renders the data only when the Hook is inspected with React Dev Tools, ensuring that the costly formatting operation is not executed every time the Hook is executed.
The following code includes the formatting function in useDebugValue.
useDebugValue(response, (res) => { // formatting function if (res) { // format and return the data when hook is inspected. return \`Data returned from API that shows local cases for today: ${res.data.local\_new\_cases}\`; } return 'No Data Returned At The Moment'; });
That’s it! We have successfully applied useDebugValue in our custom Hook to improve the overall debugging experience.
However, it is always essential to identify when to use this Hook.
When should you use useDebugValue?
Although useDebugValue is really useful, it is not good to include it in all custom Hooks.
The perfect use case of useDebugValue is when you have extremely complex code executing in your custom Hook with multiple state variables. Incorporating the useDebugValue Hook in a case like this will help filter out unnecessary logging and prioritize your code.
GitHub reference
Also, refer to the React custom Hooks debugging with the useDebugValue demo on GitHub.
Conclusion
This article explored the use of the useDebugValue Hook in debugging custom React Hooks. Having the useDebugValue in your development arsenal will increase your debugging capabilities for your custom React Hooks.
I hope you have found this useful. Thank you for reading.
The Syncfusion React suite offers over 70 high-performance, lightweight, modular, and responsive UI components in a single package. It’s the only suite you’ll ever need to construct a complete application.
If you have questions, you can contact us through our support forum, support portal, or feedback portal. We are always happy to assist you!