Skip to main content

useFacetMap

The lightweight hook for deriving facets. Best for simple transformations and few subscribers. This should be your default choice for most derivations.

When to Use

Use useFacetMap (lightweight) - THE DEFAULT CHOICE:

  • ✅ The derived facet has few subscribers (1-2 components)
  • ✅ The mapping function is lightweight (property access, string concatenation, simple math)
  • ✅ You want fast initialization (no overhead from internal facet creation)
  • ⚠️ Each subscriber recomputes the mapping function independently

Comparison with useFacetMemo

Both useFacetMap and useFacetMemo derive new facets from one or more source facets. They have identical APIs but different performance characteristics:

useFacetMap creates a simple facet that recomputes the mapping function for each subscriber independently. It's fast to initialize but computation runs N times for N subscribers.

useFacetMemo creates a full facet using createFacet internally. It's heavier to initialize but caches the result, so computation runs only once regardless of subscriber count.

typescript
// Scenario: derived facet used by 5 components
// useFacetMap: computation runs 5 times (once per subscriber)
const lightweightFacet = useFacetMap(expensiveFunc, [], [sourceFacet])
// useFacetMemo: computation runs 1 time (cached for all subscribers)
const cachedFacet = useFacetMemo(expensiveFunc, [], [sourceFacet])

Rule of thumb: Start with useFacetMap for everything. Switch to useFacetMemo only when profiling shows a performance issue or you know you'll have many subscribers to an expensive computation.

Facet Reference Stability

info

useFacetMap creates a new facet reference when any dependency changes. This includes:

  • Changes to the dependencies array (non-facet dependencies like props or local variables)
  • Changes to the facets array (different facet instances)
  • Changes to the equalityCheck function

When the returned facet reference changes, any component or hook that depends on it will re-run. This is expected behavior and mirrors how React's useMemo works.

Example:

tsx
const Component = ({ multiplier }: { multiplier: number }) => {
const [valueFacet] = useFacetState(10)
// derivedFacet is a NEW reference when multiplier changes
const derivedFacet = useFacetMap(
(val) => val * multiplier,
[multiplier], // ← When this changes, new facet is created
[valueFacet],
)
return <ChildComponent facet={derivedFacet} />
}

This is usually fine, but be aware when passing derived facets to components that might be sensitive to prop reference changes.

NO_VALUE Retention Behavior

Important: Returning NO_VALUE Retains Previous Value

When a mapping function returns NO_VALUE, the facet retains its previous value rather than updating to NO_VALUE. The observer does not notify listeners, so subscribers continue seeing the last emitted value.

This is useful for conditional updates where you want to "freeze" a facet's value under certain conditions:

tsx
import { useFacetState, useFacetMap, NO_VALUE } from '@react-facet/core'
 
const ClampedCounter = () => {
const [countFacet, setCount] = useFacetState(0)
 
// Once count reaches 5, the mapped facet stops updating and retains the value 5
const clampedFacet = useFacetMap((count) => (count < 5 ? count : NO_VALUE), [], [countFacet])
 
// clampedFacet will show: 0, 1, 2, 3, 4, 4, 4, 4... (stuck at 4)
// Even though countFacet continues: 0, 1, 2, 3, 4, 5, 6, 7...
 
return <button onClick={() => setCount((prev) => (prev === NO_VALUE ? 0 : prev + 1))}>Increment</button>
}

Key points:

  • Returning NO_VALUE from a mapping function does not set the facet's value to NO_VALUE
  • It prevents the facet from updating, keeping the last successfully mapped value
  • This applies to both useFacetMap and useFacetMemo
  • Useful for implementing conditional updates, clamping, or filtering unwanted values

Usage Examples

Basic Derivation

Use this to combine React component props with facet data and prepare facets to be passed to fast-* components.

tsx
import { useFacetState, useFacetMap } from '@react-facet/core'
 
const HealthBar = ({ lowHealthThreshold }: { lowHealthThreshold: number }) => {
const [playerFacet, setPlayerFacet] = useFacetState({
health: 80,
mana: 65,
})
 
const className = useFacetMap(
({ health }) => (health > lowHealthThreshold ? 'healthy' : 'hurt'),
[lowHealthThreshold],
[playerFacet],
)
 
return <fast-div className={className} />
}

Combining Multiple Facets

The useFacetMap hook supports passing in several facets to listen to, so you can merge the values of several facets into one using it.

tsx
import { useFacetMap, Facet } from '@react-facet/core'
 
type Props = {
placeholderFacet: Facet<string>
valueFacet: Facet<string>
}
 
const InputField = ({ placeholderFacet, valueFacet }: Props) => {
const valueToDisplay = useFacetMap(
(placeholder, value) => (value != null ? value : placeholder),
[],
[placeholderFacet, valueFacet],
)
 
return (
<span>
<fast-text text={valueToDisplay} />
</span>
)
}

Using Equality Checks

Optionally, you can pass an equality check function as the fourth argument to useFacetMap. This is particularly useful when grouping more than one facet together into a single array / object, since hard equality checks will not work on arrays / objects.

tsx
import { shallowArrayEqualityCheck, useFacetState, useFacetMap } from '@react-facet/core'
 
const WrapperComponent = () => {
const [facetA, setFacetA] = useFacetState('A')
const [facetB, setFacetB] = useFacetState('B')
 
const groupedFacet = useFacetMap((a, b) => [a, b], [], [facetA, facetB], shallowArrayEqualityCheck)
 
return <SubComponent facets={groupedFacet} />
}

See Also