--- 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.