You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Schedules a retry operation with automatic coordination and lifecycle management:
135
+
136
+
```tsx
137
+
scheduleCoordinatedRetry(
138
+
retryManager,
139
+
'link-swapping', // Operation type
140
+
() => {
141
+
// Retry callback — return value is not used by the manager
142
+
processLinks();
143
+
},
144
+
isMountedRef,
145
+
retriesRemaining,
146
+
delay,
147
+
);
148
+
```
149
+
150
+
This function:
151
+
152
+
- Returns `null` immediately if `retriesRemaining <= 0`
153
+
- Creates a `shouldRetry` callback that checks mount state
154
+
- Schedules the retry with coordination (cancels existing retries for the same operation type)
155
+
- Returns a cleanup function to cancel the retry, or `null` if the retry was not scheduled
156
+
157
+
**Important:** The manager is a single-shot scheduler — each call schedules exactly one deferred callback. It does not auto-retry. The callback itself is responsible for calling `scheduleCoordinatedRetry` again with a decremented retry count if the operation still fails. This recursive pattern is how multi-attempt retry sequences are built.
158
+
159
+
### `markComponentMounted()`
160
+
161
+
Marks a component as mounted and notifies the retry manager:
162
+
163
+
```tsx
164
+
markComponentMounted(isMountedRef, retryManager);
165
+
```
166
+
167
+
This sets `isMountedRef.current = true` and calls `retryManager.setMounted()`.
168
+
169
+
### `markComponentUnmounted()`
170
+
171
+
Marks a component as unmounted and cancels all active retries:
This sets `isMountedRef.current = false` and calls `retryManager.setUnmounted()`, which cancels all active retries.
178
+
179
+
### Operation Types
180
+
181
+
Retries are categorized by operation type to enable coordination. Only one retry per operation type can be active at a time. Scheduling a new retry for an operation type will cancel any existing retry for that type.
182
+
183
+
-**`'style-injection'`** - Used by `useZendeskIframeStyles` when injecting CSS styles
184
+
-**`'link-swapping'`** - Used by `useZendeskSwapArticleLinks` when swapping article links
185
+
-**`'iframe-access'`** - Available for iframe access operations (not currently used)
186
+
-**`'observer-setup'`** - Defined but not currently used; observer setup retries are handled internally by `setupZendeskObserver` via its `retryOnNotReady` option
187
+
188
+
---
189
+
119
190
## Common Patterns
120
191
121
192
### Mounted State Tracking
122
193
123
-
All Zendesk hooks use `isMountedRef` to prevent race conditions and memory leaks:
194
+
All Zendesk hooks use `isMountedRef` to prevent race conditions and memory leaks. Hooks that use the retry manager (`useZendeskIframeStyles`, `useZendeskSwapArticleLinks`) also use the `markComponentMounted`/`markComponentUnmounted` helpers to keep the retry manager in sync. `useZendeskClickHandlers` manages `isMountedRef` directly since it does not use the retry manager. This solves several critical problems:
195
+
196
+
**Why Track Unmounting in a Single Page Application?**
197
+
198
+
In a typical single page application, the main page component shouldn't normally unmount. However, mount tracking is still essential because:
199
+
200
+
-**React Strict Mode**: In development, React Strict Mode intentionally mounts, unmounts, and remounts components to help detect side effects. Without mount tracking, retries scheduled during the first mount could execute after the remount, causing duplicate operations.
201
+
202
+
-**Hot Module Reloading (HMR)**: During development, HMR can cause components to remount when code changes, potentially leaving stale retries from the previous mount active.
203
+
204
+
-**Error Recovery**: If an error boundary catches an error and remounts the component, or if the Zendesk widget is reset (via the "Clear conversation" feature), the component may remount, leaving previous retries active.
205
+
206
+
-**Future-Proofing**: Even if the current architecture doesn't involve unmounting, future changes (route restructuring, component splitting, etc.) could introduce unmounting scenarios. Mount tracking ensures the code remains robust.
207
+
208
+
-**Widget Reset Scenarios**: When the Zendesk widget is reset (burn animation feature), the component state is reset but the component itself may not unmount. Mount tracking helps ensure retries from before the reset don't interfere with new operations.
209
+
210
+
**Problems Solved:**
211
+
212
+
1.**Race Conditions with Async Operations**: When a component unmounts (or resets), there may be pending timeouts, retries, or async operations (like accessing iframes) that could still execute. Without mount tracking, these operations could:
213
+
- Access DOM elements that no longer exist (causing errors)
214
+
- Update state on unmounted components (React warnings)
215
+
- Execute callbacks that reference stale closures
216
+
217
+
2.**Memory Leaks**: Without proper cleanup, timeouts and retries could continue running indefinitely after unmount, holding references to components and preventing garbage collection.
218
+
219
+
3.**Stale Closures**: Callbacks scheduled via `setTimeout` or retry mechanisms might execute after the component has unmounted, accessing stale state or trying to update unmounted components.
220
+
221
+
4.**Multiple Retry Attempts**: Without coordination, multiple retry attempts could be scheduled and execute even after the component unmounts, wasting resources and potentially causing errors.
Hooks use retry mechanisms to handle timing issues where the iframe or DOM elements may not be ready:
247
+
- Before any async operation (DOM access, retry callbacks, timeouts), hooks check `isMountedRef.current`
248
+
- If the component has unmounted, operations return early without executing
249
+
- The retry manager is notified of mount/unmount state and cancels all active retries on unmount
250
+
- This prevents operations from executing after component cleanup, eliminating race conditions and memory leaks
142
251
143
-
-`processArticleLinks` retries if links aren't found
144
-
-`injectStyles` retries if iframe isn't available
145
-
-`setupZendeskObserver` retries if iframe isn't ready (when `retryOnNotReady: true`)
252
+
### Coordinated Retry System
253
+
254
+
The retry manager coordinates retries across hooks to prevent race conditions by:
255
+
256
+
- Preventing conflicts: Only one retry per operation type can be active at a time
257
+
- Tracking by operation type: Avoids duplicate work across hooks
258
+
- Handling lifecycle: Automatically cancels retries when components unmount
259
+
- Debouncing: Cancels existing retries when new ones are scheduled for the same operation type
146
260
147
261
### MutationObserver Usage
148
262
@@ -151,3 +265,5 @@ Hooks use `setupZendeskObserver` utility to watch for DOM changes:
151
265
-`useZendeskClickHandlers` - Re-attaches handlers on mutations
152
266
-`useZendeskIframeStyles` - Re-injects styles when iframe reloads
153
267
-`useZendeskSwapArticleLinks` - Not used (relies on `unreadMessages` callback)
268
+
269
+
**Note**: `setupZendeskObserver` has its own retry logic (via `retryOnNotReady` option) that is separate from the coordinated retry manager system. The retry manager handles retries for style injection and link swapping operations.
0 commit comments