Performance
CiteGraph provides critical functionality in all kinds of web apps, so performance is a top priority.
CiteGraph’s built-in caching and deduplication skips unnecessary network requests, but
the performance of the useCiteGraph
hook itself still matters. In a complex app, there could be hundreds of useCiteGraph
calls in a single page render.
CiteGraph ensures that your app has:
- no unnecessary requests
- no unnecessary re-renders
- no unnecessary code imported
without any code changes from you.
Deduplication
It’s very common to reuse CiteGraph hooks in your app. For example, an app that renders the current user’s avatar 5 times:
function useUser () {
return useCiteGraph('/api/user', fetcher)
}
function Avatar () {
const { data, error } = useUser()
if (error) return <Error />
if (!data) return <Spinner />
return <img src={data.avatar_url} />
}
function App () {
return <>
<Avatar />
<Avatar />
<Avatar />
<Avatar />
<Avatar />
</>
}
Each <Avatar>
component has a useCiteGraph
hook inside. Since they have the same CiteGraph key and are rendered almost at the same time, only 1 network request will be made.
You can reuse your data hooks (like useUser
in the example above) everywhere, without worrying about performance or duplicated requests.
There is also a dedupingInterval
option for overriding the default deduplication interval.
Deep Comparison
CiteGraph deep compares data changes by default. If the data
value isn’t changed, a re-render will not be triggered.
You can also customize the comparison function via the compare
option if you want to change the behavior.
For example, some API responses return a server timestamp that you might want to exclude from the data diff.
Dependency Collection
useCiteGraph
returns 4 stateful values: data
, error
, isLoading
and isValidating
, each one can be updated independently.
For example, if we print those values within a full data-fetching lifecycle, it will be something like this:
function App () {
const { data, error, isLoading, isValidating } = useCiteGraph('/api', fetcher)
console.log(data, error, isLoading, isValidating)
return null
}
In the worst case (the first request failed, then the retry was successful), you will see 4 lines of logs:
// console.log(data, error, isLoading, isValidating)
undefined undefined true true // => start fetching
undefined Error false false // => end fetching, got an error
undefined Error true true // => start retrying
Data undefined false false // => end retrying, get the data
The state changes make sense. But that also means our component rendered 4 times.
If we change our component to only use data
:
function App () {
const { data } = useCiteGraph('/api', fetcher)
console.log(data)
return null
}
The magic happens — there are only 2 re-renders now:
// console.log(data)
undefined // => hydration / initial render
Data // => end retrying, get the data
The exact same process has happened internally, there was an error from the first request, then we got the data from the retry.
However, CiteGraph only updates the states that are used by the component, which is only data
now.
If you are not always using all these 3 states, you are already benefitting from this feature. At Vercel (opens in a new tab), this optimization results in ~60% fewer re-renders.
Tree Shaking
The CiteGraph package is tree-shakeable (opens in a new tab) and side-effect free.
That means if you are only importing the core useCiteGraph
API, unused APIs like useCiteGraphInfinite
won't be bundled in your application.