useFacetUnwrap
This hook will negate all the performance benefits of using Facets for state management.
Every update to the facet will trigger a full React re-render of the component, defeating the primary purpose of React Facet. Use this hook sparingly and only when absolutely necessary.
The useFacetUnwrap hook extracts the plain value from a Facet and treats it as regular React component state. This creates a subscription that triggers component re-renders whenever the facet value changes.
Signature
typescriptfunction useFacetUnwrap<T>(prop: FacetProp<T>, equalityCheck?: EqualityCheck<T>): T | NoValue
Parameters:
prop: AFacet<T>or plain valueT(viaFacetProp<T>)equalityCheck: Optional equality check function to prevent unnecessary re-renders (default: reference equality)
Returns: The current value T or NO_VALUE if the facet is uninitialized
The return type of useFacetUnwrap is T | NO_VALUE. You must check for NO_VALUE before using the value, otherwise you'll get TypeScript errors or runtime issues.
tsxconst value = useFacetUnwrap(facet)// ❌ WRONG - TypeScript error! value might be NO_VALUEif (value > 50) { ... }// ✅ CORRECT - Check for NO_VALUE firstif (value !== NO_VALUE && value > 50) { ... }
When to Use
useFacetUnwrap should only be used in these specific scenarios:
1. Interfacing with Non-Facet-Aware Components
When you must pass a plain value to a third-party component that doesn't accept facets:
tsximport {useFacetState ,useFacetUnwrap ,NO_VALUE } from '@react-facet/core'// Third-party component that expects plain propsconstThirdPartyChart = ({data }: {data : number[] }) => <div >Chart</div >constChartWrapper = () => {const [dataFacet ,setDataFacet ] =useFacetState <number[]>([1, 2, 3])// ⚠️ Necessary here because ThirdPartyChart doesn't support facetsconstplainData =useFacetUnwrap (dataFacet )// Always check for NO_VALUE before usingif (plainData ===NO_VALUE ) return nullreturn <ThirdPartyChart data ={plainData } />}
Better alternative: Refactor the component to accept facets when possible:
tsximport {useFacetState ,Facet } from '@react-facet/core'// ✅ Better - refactor to accept facetsconstFacetAwareChart = ({dataFacet }: {dataFacet :Facet <number[]> }) => {// Implementation using facets directlyreturn <fast-div >Chart</fast-div >}constChartWrapper = () => {const [dataFacet ,setDataFacet ] =useFacetState <number[]>([1, 2, 3])// ✅ No unwrap needed - no re-renders!return <FacetAwareChart dataFacet ={dataFacet } />}
2. Complex Conditional Logic (Use Sparingly)
When you have complex conditional rendering logic that's difficult to express with Mount or With components:
tsximport {useFacetState ,useFacetUnwrap ,NO_VALUE } from '@react-facet/core'constComplexConditional = () => {const [statusFacet ] =useFacetState <'loading' | 'error' | 'success'>('loading')const [dataFacet ] =useFacetState <{items : string[] } | null>(null)// ⚠️ Complex logic that's hard to express otherwiseconststatus =useFacetUnwrap (statusFacet )constdata =useFacetUnwrap (dataFacet )if (status === 'loading') return <div >Loading...</div >if (status === 'error') return <div >Error!</div >if (data ===NO_VALUE ||data === null) return <div >No data</div >return (<div >{data .items .map ((item ,i ) => (<div key ={i }>{item }</div >))}</div >)}
When NOT to Use
These patterns should be avoided - use the suggested alternatives instead.
❌ Don't Use for Simple Conditional Rendering
tsximport {useFacetState ,useFacetUnwrap } from '@react-facet/core'constExpensiveComponent = () => <div >Expensive</div >// ❌ BAD - causes component re-renderconstBadExample = () => {const [isVisibleFacet ] =useFacetState (true)constisVisible =useFacetUnwrap (isVisibleFacet )if (!isVisible ) return nullreturn <ExpensiveComponent />}
tsximport {useFacetState ,Mount } from '@react-facet/core'constExpensiveComponent = () => <div >Expensive</div >// ✅ GOOD - scopes re-render to Mount componentconstGoodExample = () => {const [isVisibleFacet ] =useFacetState (true)return (<Mount when ={isVisibleFacet }><ExpensiveComponent /></Mount >)}
❌ Don't Use for Binding to DOM
tsximport {useFacetState ,useFacetUnwrap ,NO_VALUE } from '@react-facet/core'// ❌ BAD - causes re-renders on every update, requires NO_VALUE checksconstBadHealthBar = () => {const [healthFacet ] =useFacetState (100)consthealth =useFacetUnwrap (healthFacet )// Must check for NO_VALUE before using the value!if (health ===NO_VALUE ) return <div >Loading...</div >return <div className ={health > 50 ? 'healthy' : 'low-health'}>{health }</div >}
tsximport {useFacetState ,useFacetMap } from '@react-facet/core'// ✅ GOOD - no re-renders, uses fast-* componentsconstGoodHealthBar = () => {const [healthFacet ] =useFacetState (100)constclassName =useFacetMap ((health ) => (health > 50 ? 'healthy' : 'low-health'), [], [healthFacet ])return (<fast-div className ={className }><fast-text text ={healthFacet } /></fast-div >)}
❌ Don't Use for Accessing Values in Callbacks
tsximport {useFacetState ,useFacetUnwrap ,NO_VALUE } from '@react-facet/core'import {useCallback } from 'react'// ❌ BAD - unnecessary re-renders, must handle NO_VALUEconstBadForm = ({onSubmit }: {onSubmit : (value : string) => void }) => {const [valueFacet ] =useFacetState ('')constvalue =useFacetUnwrap (valueFacet )consthandleSubmit =useCallback (() => {// Must check for NO_VALUE before using!if (value !==NO_VALUE ) {onSubmit (value )}}, [value ,onSubmit ])return <button onClick ={handleSubmit }>Submit</button >}
tsximport {useFacetState ,useFacetCallback } from '@react-facet/core'// ✅ GOOD - use useFacetCallbackconstGoodForm = ({onSubmit }: {onSubmit : (value : string) => void }) => {const [valueFacet ] =useFacetState ('')consthandleSubmit =useFacetCallback ((currentValue ) => () => {onSubmit (currentValue )},[onSubmit ],[valueFacet ],)return <button onClick ={handleSubmit }>Submit</button >}
Using Equality Checks
For complex data types, provide an equality check function to prevent unnecessary re-renders:
tsximport {useFacetState ,useFacetUnwrap ,shallowObjectEqualityCheck ,NO_VALUE } from '@react-facet/core'constPlayerStats = () => {const [playerFacet ] =useFacetState ({health : 100,mana : 50 })// ✅ Use equality check for objectsconstplayer =useFacetUnwrap (playerFacet ,shallowObjectEqualityCheck )// Always check for NO_VALUE before using the valueif (player ===NO_VALUE ) {return <div >Loading...</div >}// Component will only re-render when health or mana values actually changereturn (<div >Health: {player .health }, Mana: {player .mana }</div >)}
Handling NO_VALUE
Always check for NO_VALUE before using unwrapped values. The return type is T | NO_VALUE, not just T. Failing to check will cause type errors and potential runtime bugs.
Every use of useFacetUnwrap requires a NO_VALUE check:
tsximport {useFacetState ,useFacetUnwrap ,NO_VALUE ,Facet } from '@react-facet/core'constDisplayValue = ({valueFacet }: {valueFacet :Facet <string> }) => {constvalue =useFacetUnwrap (valueFacet )// ✅ Always check before usingif (value ===NO_VALUE ) {return <div >Loading...</div >}// Now TypeScript knows value is T, not T | NO_VALUEreturn <div >{value }</div >}
Common patterns for handling NO_VALUE:
tsx// Early return patternconstComponent1 = ({facet }: {facet :Facet <number> }) => {constvalue =useFacetUnwrap (facet )if (value ===NO_VALUE ) return nullreturn <div >{value * 2}</div >}// Default value patternconstComponent2 = ({facet }: {facet :Facet <number> }) => {constvalue =useFacetUnwrap (facet )constdisplayValue =value ===NO_VALUE ? 0 :value return <div >{displayValue }</div >}// Conditional logic patternconstComponent3 = ({facet }: {facet :Facet <number> }) => {constvalue =useFacetUnwrap (facet )return <div >{value ===NO_VALUE ? <span >Loading...</span > : <span >{value }</span >}</div >}// Guard with && operatorconstComponent4 = ({facet }: {facet :Facet <string[]> }) => {constitems =useFacetUnwrap (facet )return <div >{items !==NO_VALUE &&items .map ((item ,i ) => <div key ={i }>{item }</div >)}</div >}
Remember: The whole point of React Facet is to avoid React reconciliation. Every use of useFacetUnwrap is a step backwards from that goal.
See Also
- Mount - For conditional mounting without unwrapping
- useFacetCallback - For accessing facet values in callbacks
- Equality Checks - Available equality check functions