---
title: Reactivity in React
description: 'Use useState and useEffect with useHead() for reactive head tags. Integrates with React Query, SWR, and async data fetching.'
navigation:
title: 'Reactivity'
---
Pass React state directly to `useHead()`. A re-render patches the component's existing head entry.
## React Integration
`UnheadProvider` places one head instance in React context. `useHead()` reads that instance, creates an entry, and patches it after renders.
### Provide the Head Instance
Create the client instance once, then pass it to `UnheadProvider`:
```tsx
import { createHead, UnheadProvider } from '@unhead/react/client'
// Create head instance with custom options
const head = createHead()
function App() {
return (
)
}
```
## Reactive Head Tags
Pass state values in the head object:
```tsx
import { useHead } from '@unhead/react'
import { useState } from 'react'
function PageHead() {
const [title, setTitle] = useState('Welcome to My App')
// Head tags will update when title state changes
useHead({
title
})
return (
)
}
```
## Async Data
Update state when the request completes; the next render updates the entry:
```tsx
import { useHead } from '@unhead/react'
import { useEffect, useState } from 'react'
function PageHead() {
const [title, setTitle] = useState('Loading...')
useEffect(() => {
async function loadData() {
const response = await fetch('/api/page')
const data = await response.json()
setTitle(data.title)
}
loadData()
}, [])
useHead({
title
})
return null
}
```
## Group Related Tags
Keep related tags in the same entry when they come from the same data:
```tsx
function ProductHead({ id }) {
const [product, setProduct] = useState({
title: 'Loading...',
description: '',
image: '/placeholder.jpg',
price: ''
})
useEffect(() => {
fetchProduct(id).then(setProduct)
}, [id])
useHead({
title: product.title,
meta: [
{ name: 'description', content: product.description },
{ property: 'og:image', content: product.image },
{ property: 'product:price', content: product.price }
]
})
return null
}
```
## Data-Fetching Libraries
React Query and SWR require no Unhead-specific integration. Pass their result to `useHead()` as you would local state.
### React Query
```tsx
import { useQuery } from '@tanstack/react-query'
import { useHead } from '@unhead/react'
function PageHead({ id }) {
const { data = { title: 'Loading...', description: '' } } = useQuery({
queryKey: ['page', id],
queryFn: () => fetchPage(id)
})
useHead({
title: data.title,
meta: [
{ name: 'description', content: data.description }
]
})
return null
}
```
### SWR
```tsx
import { useHead } from '@unhead/react'
import useSWR from 'swr'
function PageHead({ slug }) {
const { data = { title: 'Loading...', description: '' } } = useSWR(
`/api/pages/${slug}`,
fetcher
)
useHead({
title: data.title,
meta: [
{ name: 'description', content: data.description }
]
})
return null
}
```
## Memoizing Large Inputs
### Memoize Complex Head Configurations
If building the head object is expensive, memoize it with the same dependencies as the page data:
```tsx
import { useHead } from '@unhead/react'
import { useMemo } from 'react'
function SEOHead({ title, description, image }) {
const headConfig = useMemo(() => ({
title,
meta: [
{ name: 'description', content: description },
{ property: 'og:title', content: title },
{ property: 'og:description', content: description },
{ property: 'og:image', content: image }
]
}), [title, description, image])
useHead(headConfig)
return null
}
```
## Entry Lifecycle
On the server, `useHead()` creates its entry during render. In the browser, it creates the entry in an effect, patches it when the input changes, and disposes it when the component unmounts.
React runs an effect's cleanup before rerunning the setup after a dependency changes and when the component unmounts. In development, Strict Mode also runs one extra setup-and-cleanup cycle; Unhead's disposal follows that [documented effect lifecycle](https://react.dev/reference/react/useEffect#caveats), so the rehearsal does not leave a duplicate entry behind.