createObjectWithKeySpecificEqualityCheck
Creates an equality check for objects where each property has a different type and needs its own specific equality check. This is the most flexible object equality check.
When to Use
Use when deriving objects with properties of different types:
- ✅ Objects with mixed property types (strings, arrays, nested objects)
- ✅ Complex data structures with heterogeneous properties
- ✅ Type-safe equality checking for each property
- ❌ Objects with uniform property types (use
createUniformObjectEqualityCheck
) - ❌ Objects with only primitive properties (use
shallowObjectEqualityCheck
)
Signature
typescript
function createObjectWithKeySpecificEqualityCheck<T extends Record<string, any>>(equalityChecks: {[K in keyof T]: EqualityCheck<T[K]>}): EqualityCheck<T>
Parameters:
equalityChecks
- Object mapping each property key to its specific equality check
Returns: An equality check that compares each object property using its designated equality check.
When using createObjectWithKeySpecificEqualityCheck
with useFacetMap
or useFacetMemo
, you must create a stable reference to the equality check. Creating it inline on every render causes the facet to be recreated and internal state to be lost.
tsx
// ❌ WRONG - Creates new equality check on every renderconst Component = () => {const result = useFacetMap((data) => transform(data),[],[dataFacet],createObjectWithKeySpecificEqualityCheck({// New reference every render!name: strictEqualityCheck,items: shallowArrayEqualityCheck,}),)}// ✅ CORRECT - Define outside componentconst dataEqualityCheck = createObjectWithKeySpecificEqualityCheck({name: strictEqualityCheck,items: shallowArrayEqualityCheck,})const Component = () => {const result = useFacetMap((data) => transform(data), [], [dataFacet], dataEqualityCheck)}// ✅ ALSO CORRECT - Use useMemoconst Component = () => {const equalityCheck = useMemo(() =>createObjectWithKeySpecificEqualityCheck({name: strictEqualityCheck,items: shallowArrayEqualityCheck,}),[],)const result = useFacetMap((data) => transform(data), [], [dataFacet], equalityCheck)}
See Custom Equality Checks for more details.
Basic Usage
tsx
import {createObjectWithKeySpecificEqualityCheck ,shallowArrayEqualityCheck ,strictEqualityCheck ,} from '@react-facet/core'typePlayerData = {name : stringitems : number[]}constequalityCheck =createObjectWithKeySpecificEqualityCheck <PlayerData >({name :strictEqualityCheck ,items :shallowArrayEqualityCheck ,})()equalityCheck ({name : 'Steve',items : [1, 54, 97],})console .log (equalityCheck ({name : 'Steve',items : [1, 54, 97],}),) // true - both properties matchconsole .log (equalityCheck ({name : 'Alex',items : [1, 54, 97],}),) // false - name changedconsole .log (equalityCheck ({name : 'Alex',items : [1, 54, 97],}),) // true - same values again
Usage with Facets
Complex Form State
tsx
import {useFacetMap ,useFacetWrap ,createObjectWithKeySpecificEqualityCheck ,strictEqualityCheck ,shallowArrayEqualityCheck ,shallowObjectEqualityCheck ,} from '@react-facet/core'typeFormData = {username : stringage : numbertags : string[]settings : {theme : string;notifications : boolean }}constUserForm = () => {constformFacet =useFacetWrap <FormData >({username : 'Steve',age : 25,tags : ['minecraft', 'builder'],settings : {theme : 'dark',notifications : true },})constvalidatedFormFacet =useFacetMap ((form ) => ({username :form .username .trim (),age :Math .max (0,form .age ),tags :form .tags .filter ((t ) =>t .length > 0),settings :form .settings ,}),[],[formFacet ],createObjectWithKeySpecificEqualityCheck ({username :strictEqualityCheck ,age :strictEqualityCheck ,tags :shallowArrayEqualityCheck ,settings :shallowObjectEqualityCheck ,}),)return <div >Form</div >}
Mixed Data Structure
tsx
import {useFacetMap ,useFacetWrap ,createObjectWithKeySpecificEqualityCheck ,strictEqualityCheck ,shallowArrayEqualityCheck ,shallowObjectArrayEqualityCheck ,} from '@react-facet/core'typeGameState = {currentLevel : numberplayerIds : number[]items :Array <{id : number;type : string }>}constGame = () => {conststateFacet =useFacetWrap <GameState >({currentLevel : 1,playerIds : [1, 2, 3],items : [{id : 1,type : 'sword' },{id : 2,type : 'shield' },],})constprocessedStateFacet =useFacetMap ((state ) => ({currentLevel :state .currentLevel ,playerIds :state .playerIds .sort (),items :state .items .filter ((item ) =>item .type !== 'deprecated'),}),[],[stateFacet ],createObjectWithKeySpecificEqualityCheck ({currentLevel :strictEqualityCheck ,playerIds :shallowArrayEqualityCheck ,items :shallowObjectArrayEqualityCheck ,}),)return <div >Game</div >}
API Response Transformation
tsx
import {useFacetMap ,useFacetWrap ,createObjectWithKeySpecificEqualityCheck ,strictEqualityCheck ,shallowObjectEqualityCheck ,shallowArrayEqualityCheck ,} from '@react-facet/core'typeAPIResponse = {id : stringmetadata : {created : number;updated : number }tags : string[]}constDataDisplay = () => {constresponseFacet =useFacetWrap <APIResponse >({id : 'abc123',metadata : {created : 1000000,updated : 1000100 },tags : ['active', 'verified'],})consttransformedDataFacet =useFacetMap ((response ) => ({id :response .id .toUpperCase (),metadata :response .metadata ,tags :response .tags .map ((tag ) =>tag .toLowerCase ()),}),[],[responseFacet ],createObjectWithKeySpecificEqualityCheck ({id :strictEqualityCheck ,metadata :shallowObjectEqualityCheck ,tags :shallowArrayEqualityCheck ,}),)return <div >Data display</div >}
How It Works
The equality check:
- Iterates through each property key
- Uses the specified equality check for that property
- Returns
false
if any property comparison fails
tsx
import {createObjectWithKeySpecificEqualityCheck ,strictEqualityCheck ,shallowArrayEqualityCheck ,} from '@react-facet/core'typeData = {name : stringscores : number[]}constcheck =createObjectWithKeySpecificEqualityCheck <Data >({name :strictEqualityCheck ,scores :shallowArrayEqualityCheck ,})()check ({name : 'Player1',scores : [10, 20] })// Same valuesconsole .log (check ({name : 'Player1',scores : [10, 20] })) // true ✅// Name changedconsole .log (check ({name : 'Player2',scores : [10, 20] })) // false ❌// Scores changedcheck ({name : 'Player2',scores : [10, 20] })console .log (check ({name : 'Player2',scores : [10, 30] })) // false ❌
Nesting with Other Checks
You can combine with other factory functions for deeply nested structures:
tsx
import {createObjectWithKeySpecificEqualityCheck ,createUniformArrayEqualityCheck ,strictEqualityCheck ,shallowArrayEqualityCheck ,shallowObjectEqualityCheck ,} from '@react-facet/core'typeComplexData = {id : stringmatrix : number[][] // Array of arrayssettings : {theme : string;locale : string }}constcheck =createObjectWithKeySpecificEqualityCheck <ComplexData >({id :strictEqualityCheck ,matrix :createUniformArrayEqualityCheck (shallowArrayEqualityCheck ),settings :shallowObjectEqualityCheck ,})()check ({id : 'abc',matrix : [[1, 2],[3, 4],],settings : {theme : 'dark',locale : 'en' },})console .log (check ({id : 'abc',matrix : [[1, 2],[3, 4],],settings : {theme : 'dark',locale : 'en' },}),) // true ✅
Important Notes
Type Safety
TypeScript ensures you provide equality checks for all properties:
tsx
import {createObjectWithKeySpecificEqualityCheck ,strictEqualityCheck } from '@react-facet/core'typeData = {name : stringage : number}// @ts-expect-error - Missing 'age' propertyconstcheck1 =createObjectWithKeySpecificEqualityCheck <Data >({name :strictEqualityCheck ,})// ✅ Correct - all properties specifiedconstcheck2 =createObjectWithKeySpecificEqualityCheck <Data >({name :strictEqualityCheck ,age :strictEqualityCheck ,})
Property Order Doesn't Matter
tsx
import {createObjectWithKeySpecificEqualityCheck ,strictEqualityCheck } from '@react-facet/core'typeData = {a : number;b : number }constcheck =createObjectWithKeySpecificEqualityCheck <Data >({a :strictEqualityCheck ,b :strictEqualityCheck ,})()check ({a : 1,b : 2 })// Same values, different orderconsole .log (check ({b : 2,a : 1 })) // true ✅
Each Property Maintains Independent State
Each property's equality check maintains its own state independently. This is important because all property checks always run, even when one fails:
tsx
import {createObjectWithKeySpecificEqualityCheck ,strictEqualityCheck ,shallowArrayEqualityCheck ,} from '@react-facet/core'typeData = {name : stringitems : number[]}constcheck =createObjectWithKeySpecificEqualityCheck <Data >({name :strictEqualityCheck ,items :shallowArrayEqualityCheck ,})()check ({name : 'Steve',items : [1, 2] })// Name changed, but items stayed the same// Both checks run: name returns false, items returns true// Overall result is false (name changed)console .log (check ({name : 'Alex',items : [1, 2] })) // false ❌// Now both values are the same as the previous call// Both checks run: name returns true, items returns true// Overall result is true (nothing changed)console .log (check ({name : 'Alex',items : [1, 2] })) // true ✅
Key insight: Even though name
failed on the second call, the items
check still ran and updated its internal state to [1, 2]
. This ensures all checks stay synchronized with the current values.
Common Patterns
User Profile
tsx
import {useFacetMap,createObjectWithKeySpecificEqualityCheck,strictEqualityCheck,shallowArrayEqualityCheck,shallowObjectEqualityCheck,} from '@react-facet/core'type UserProfile = {id: stringdisplayName: stringroles: string[]preferences: { theme: string; language: string }}const profileFacet = useFacetMap((user) => ({id: user.id,displayName: `${user.firstName} ${user.lastName}`,roles: user.roles,preferences: user.preferences,}),[],[userFacet],createObjectWithKeySpecificEqualityCheck({id: strictEqualityCheck,displayName: strictEqualityCheck,roles: shallowArrayEqualityCheck,preferences: shallowObjectEqualityCheck,}),)
Dashboard State
tsx
import {useFacetMap,createObjectWithKeySpecificEqualityCheck,strictEqualityCheck,shallowArrayEqualityCheck,shallowObjectArrayEqualityCheck,} from '@react-facet/core'type DashboardState = {selectedView: stringfilterIds: number[]widgets: Array<{ id: number; visible: boolean }>}const dashboardFacet = useFacetMap((state, userId) => ({selectedView: state.views[userId] || 'default',filterIds: state.filters.filter((f) => f.userId === userId).map((f) => f.id),widgets: state.widgets.filter((w) => w.userId === userId),}),[],[stateFacet, userIdFacet],createObjectWithKeySpecificEqualityCheck({selectedView: strictEqualityCheck,filterIds: shallowArrayEqualityCheck,widgets: shallowObjectArrayEqualityCheck,}),)
Performance Considerations
Performance depends on the complexity of the individual equality checks:
- Simple checks (primitives) are fast
- Complex checks (nested arrays/objects) can be slower
- Each property is checked independently
Optimize by:
- Using simpler equality checks where possible
- Ordering checks to fail fast (put likely-to-change properties first in source code)
- Profiling before adding complex nested checks
See Also
- Equality Checks Overview - Guide to all equality checks
createUniformObjectEqualityCheck
- For objects with uniform property typesshallowObjectEqualityCheck
- For objects with only primitive properties- Custom Equality Checks - Create your own equality checks