Skip to content

Commit 3df77fa

Browse files
committed
more docs
1 parent 51eb7ef commit 3df77fa

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed

rescript-relay-documentation/docs/interacting-with-the-store.md

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,30 @@ RescriptRelay.commitLocalUpdate(~environment, ~updater=store => {
248248
})
249249
```
250250

251+
#### Selective invalidation with `excludedIds`
252+
253+
Sometimes you want to invalidate most records in a list but exclude specific ones. The `excludedIds` parameter allows you to do this efficiently:
254+
255+
```rescript
256+
RescriptRelay.commitLocalUpdate(~environment, ~updater=store => {
257+
let allConnectionIds = environment->RescriptRelay.Environment.findAllConnectionIds(
258+
~connectionKey=UserProfile_user_postsConnection_graphql.connectionKey,
259+
~parentId=userId
260+
)
261+
262+
// Exclude the connection that was just updated from invalidation
263+
let recentlyUpdatedConnectionId = /* ... get the specific connection ID ... */
264+
265+
// Invalidate all connections except the one we just updated
266+
store->RescriptRelay.RecordSourceSelectorProxy.invalidateRecordsByIds(
267+
allConnectionIds,
268+
~excludedIds=[recentlyUpdatedConnectionId]
269+
)
270+
})
271+
```
272+
273+
This is useful when you've just updated specific connection instances and don't want to invalidate them immediately, but want to invalidate all other instances.
274+
251275
### Common patterns
252276

253277
These functions work great together for common scenarios:
@@ -285,6 +309,262 @@ CreatePostMutation.commitMutation(
285309
)
286310
```
287311

312+
## Listening to invalidation events
313+
314+
### Subscribing to invalidation state with `useSubscribeToInvalidationState`
315+
316+
When you invalidate records in the Relay store, queries won't automatically refetch until the next time they render. However, sometimes you need to take immediate action when specific records are invalidated, such as triggering an immediate refetch or updating some UI state.
317+
318+
For these scenarios, RescriptRelay provides `useSubscribeToInvalidationState` - a hook that lets you listen to invalidation events and react to them immediately.
319+
320+
#### Basic usage
321+
322+
```rescript
323+
module UserProfile = %relay(`
324+
fragment UserProfile_user on User {
325+
__id
326+
name
327+
email
328+
isOnline
329+
}
330+
`)
331+
332+
@react.component
333+
let make = (~userRef) => {
334+
let user = UserProfile.use(userRef)
335+
let environment = RescriptRelay.useEnvironmentFromContext()
336+
337+
// Subscribe to invalidation of this specific user record
338+
let _ = RescriptRelay.useSubscribeToInvalidationState([user.__id], () => {
339+
// This callback will fire whenever the user record is invalidated
340+
Console.log("User record was invalidated, taking action...")
341+
342+
// You could trigger a refetch, show a notification, etc.
343+
})
344+
345+
<div>
346+
<h1>{React.string(user.name)}</h1>
347+
<p>{React.string(user.email->Belt.Option.getWithDefault("No email"))}</p>
348+
</div>
349+
}
350+
```
351+
352+
#### Triggering immediate refetches on invalidation
353+
354+
One common pattern is to automatically refetch data when it becomes invalidated:
355+
356+
```rescript
357+
module UserProfile = %relay(`
358+
fragment UserProfile_user on User
359+
@refetchable(queryName: "UserProfileRefetchQuery") {
360+
__id
361+
name
362+
email
363+
profile {
364+
bio
365+
lastActive
366+
}
367+
}
368+
`)
369+
370+
@react.component
371+
let make = (~userRef) => {
372+
let (user, refetch) = UserProfile.useRefetchable(userRef)
373+
374+
// Automatically refetch when this user record is invalidated
375+
let _ = RescriptRelay.useSubscribeToInvalidationState([user.__id], () => {
376+
refetch(~variables=())->RescriptRelay.Disposable.ignore
377+
})
378+
379+
<div>
380+
<h1>{React.string(user.name)}</h1>
381+
{switch user.profile {
382+
| Some(profile) =>
383+
<div>
384+
<p>{React.string(profile.bio->Belt.Option.getWithDefault("No bio"))}</p>
385+
<small>{React.string("Last active: " ++ profile.lastActive)}</small>
386+
</div>
387+
| None => React.null
388+
}}
389+
</div>
390+
}
391+
```
392+
393+
#### Listening to multiple records
394+
395+
You can listen to invalidation of multiple records at once:
396+
397+
```rescript
398+
@react.component
399+
let make = (~userIds: array<RescriptRelay.dataId>) => {
400+
let environment = RescriptRelay.useEnvironmentFromContext()
401+
402+
// Listen to invalidation of any of these user records
403+
let _ = RescriptRelay.useSubscribeToInvalidationState(userIds, () => {
404+
// This will fire if any of the user records become invalidated
405+
Console.log("One or more user records were invalidated")
406+
407+
// You might want to refetch a list or update some global state
408+
})
409+
410+
// ... rest of component
411+
}
412+
```
413+
414+
#### When to use `useSubscribeToInvalidationState`
415+
416+
- **Real-time data updates**: When you need to immediately reflect that data has changed
417+
- **Cache warming**: Preemptively fetching updated data before it's needed
418+
- **UI state synchronization**: Updating component state that depends on the validity of cached data and where waiting for a re-render is not enough for the UX
419+
- **Analytics/logging**: Tracking when specific data becomes stale
420+
421+
#### Integration with bulk invalidation
422+
423+
You can combine this with bulk invalidation patterns:
424+
425+
```rescript
426+
// In a mutation updater
427+
let invalidateUserConnections = (environment, userId, store) => {
428+
let connectionIds = environment->RescriptRelay.Environment.findAllConnectionIds(
429+
~connectionKey=UserPosts_user_postsConnection_graphql.connectionKey,
430+
~parentId=userId
431+
)
432+
store->RescriptRelay.RecordSourceSelectorProxy.invalidateRecordsByIds(connectionIds)
433+
}
434+
435+
// In a component that cares about these connections
436+
@react.component
437+
let make = (~userId) => {
438+
let environment = RescriptRelay.useEnvironmentFromContext()
439+
440+
React.useEffect(() => {
441+
let connectionIds = environment->RescriptRelay.Environment.findAllConnectionIds(
442+
~connectionKey=UserPosts_user_postsConnection_graphql.connectionKey,
443+
~parentId=userId
444+
)
445+
446+
let unsubscribe = RescriptRelay.useSubscribeToInvalidationState(connectionIds, () => {
447+
// React to any of the user's post connections being invalidated
448+
Console.log("User's post connections were invalidated")
449+
})
450+
451+
Some(unsubscribe)
452+
}, [userId])
453+
454+
// ... rest of component
455+
}
456+
```
457+
458+
## Invalidation strategies and best practices
459+
460+
Understanding when and how to invalidate data is crucial for maintaining an optimal user experience. Here's a comprehensive guide to help you choose the right invalidation strategy.
461+
462+
### Choosing the right invalidation approach
463+
464+
#### 1. **Individual record invalidation** (`RecordProxy.invalidateRecord`)
465+
466+
**When to use:**
467+
468+
- Invalidating a specific entity after an update
469+
- Simple scenarios where you know exactly which record changed
470+
- When you want other cached data to remain valid
471+
472+
**Example:**
473+
474+
```rescript
475+
// After updating a user's profile
476+
UserUpdateMutation.commitMutation(
477+
~environment,
478+
~variables={userId, newName},
479+
~updater=(store, response) => {
480+
// Only invalidate the specific user that was updated
481+
switch store->RescriptRelay.RecordSourceSelectorProxy.get(~dataId=userId) {
482+
| Some(userRecord) => userRecord->RescriptRelay.RecordProxy.invalidateRecord
483+
| None => ()
484+
}
485+
}
486+
)
487+
```
488+
489+
#### 2. **Bulk record invalidation** (`invalidateRecordsByIds`)
490+
491+
**When to use:**
492+
493+
- Invalidating multiple related records efficiently
494+
- Connection invalidation scenarios
495+
- When you have a list of specific records that need invalidation
496+
497+
**Example:**
498+
499+
```rescript
500+
// After deleting a post, invalidate all comment connections for that post
501+
DeletePostMutation.commitMutation(
502+
~environment,
503+
~variables={postId},
504+
~updater=(store, _response) => {
505+
let connectionIds = environment->RescriptRelay.Environment.findAllConnectionIds(
506+
~connectionKey=PostComments_post_commentsConnection_graphql.connectionKey,
507+
~parentId=postId
508+
)
509+
store->RescriptRelay.RecordSourceSelectorProxy.invalidateRecordsByIds(connectionIds)
510+
}
511+
)
512+
```
513+
514+
#### 3. **Store-wide invalidation** (`RecordSourceSelectorProxy.invalidateStore`)
515+
516+
**When to use:**
517+
518+
- Major data changes that affect many parts of your app
519+
- User authentication state changes
520+
- App-wide cache clearing scenarios
521+
- When in doubt and you need to ensure data freshness
522+
523+
**Example:**
524+
525+
```rescript
526+
// After user logs out
527+
LogoutMutation.commitMutation(
528+
~environment,
529+
~variables=(),
530+
~updater=(store, _response) => {
531+
// Invalidate everything since user context has changed
532+
store->RescriptRelay.RecordSourceSelectorProxy.invalidateStore
533+
}
534+
)
535+
```
536+
537+
### Invalidation timing and performance considerations
538+
539+
#### Lazy invalidation (default behavior)
540+
541+
By default, invalidated data only triggers refetches when the component next renders. This is usually the most efficient approach:
542+
543+
```rescript
544+
// Invalidation happens in mutation updater
545+
SomeDataMutation.commitMutation(~environment, ~variables, ~updater=(store, _) => {
546+
// Record is marked as invalid
547+
someRecord->RescriptRelay.RecordProxy.invalidateRecord
548+
})
549+
550+
// Component will refetch when it next renders
551+
// This could be immediately if the component is already rendered and a rerender is triggered,
552+
// or later when the user navigates to a view that uses this data
553+
```
554+
555+
#### Immediate invalidation with refetch
556+
557+
Use `useSubscribeToInvalidationState` when you need immediate reactions:
558+
559+
```rescript
560+
// Component automatically refetches when data is invalidated
561+
let (data, refetch) = SomeFragment.useRefetchable(fragmentRef)
562+
563+
RescriptRelay.useSubscribeToInvalidationState([data.__id], () => {
564+
refetch(~variables=())->RescriptRelay.Disposable.ignore
565+
})
566+
```
567+
288568
## Imperative updates
289569

290570
> Using imperative store updates has its place, but avoid it for as long as you can.

0 commit comments

Comments
 (0)