Reactivity in React
Introduction
Unhead integrates with React's state management system through the React Context API. This guide explains how to effectively manage reactive head tags in your React applications.
How Unhead Works in React
Unhead uses React's Context API to provide head management throughout your component tree. The integration automatically tracks changes to your React state and updates the document head accordingly.
Using the UnheadProvider
Unlike React Helmet, Unhead uses the provider pattern for isolation - keeping head state contained and manageable rather than global:
import { createHead, UnheadProvider } from '@unhead/react'
// Create head instance with custom options
const head = createHead()
function App() {
return (
<UnheadProvider head={head}>
<YourApp />
</UnheadProvider>
)
}
This pattern prevents many hydration and testing headaches by keeping head state contained.
Basic Reactivity
React's state management system works naturally with Unhead. When you pass state to useHead()
, the head tags update whenever the state changes:
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 (
<button onClick={() => setTitle('Updated Title')}>
Update Title
</button>
)
}
Async Head Updates
It's common to update head tags based on asynchronous data:
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
}
Managing Complex Head Data
For pages with multiple meta tags, you can manage them together in a structured way:
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
}
Using with Data Libraries
Unhead works well with popular data fetching libraries:
React Query Example
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 Example
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
}
Performance Optimization
For optimal performance with Unhead in React:
Memoize Complex Head Configurations
Use useMemo
for complex head configurations to prevent unnecessary re-renders:
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
}
Code Splitting for Head Components
Leverage code-splitting for head components to reduce initial bundle size:
import { lazy } from 'react'
// Load head components only when needed
const ProductHead = lazy(() => import('./ProductHead'))
const BlogHead = lazy(() => import('./BlogHead'))
function App() {
return (
<Routes>
<Route
path="/products/:id"
element={(
<Suspense fallback={null}>
<ProductHead />
</Suspense>
)}
/>
<Route
path="/blog/:slug"
element={(
<Suspense fallback={null}>
<BlogHead />
</Suspense>
)}
/>
</Routes>
)
}
Implementation Details
Under the hood, Unhead in React:
- Uses React's Context API for head instance management
- Integrates with React's lifecycle using
useEffect
and state hooks - Creates head entries that update when input props change
- Automatically cleans up when components unmount
The implementation wraps head entries with React state management that:
- Patches the entry when input data changes
- Disposes of the entry when the component is unmounted
Best Practices
- Keep Head Components Focused: Create dedicated components for head management that are separate from UI components.
- Handle Loading States: Always provide fallback values for async data to prevent undefined errors.
- Use Component Composition: Compose multiple head components for complex pages:
function ProductPage({ id }) {
return (
<>
<GlobalHead />
{' '}
{/* Global site metadata */}
<ProductHead id={id} />
{' '}
{/* Product-specific metadata */}
<ProductSchema id={id} />
{' '}
{/* Structured data */}
{/* UI components */}
</>
)
}
- Cleanup Happens Automatically: Unhead handles cleanup when components unmount through React's effect cleanup system.