@aweebit/react-essentials 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,113 +1,221 @@
1
1
  # @aweebit/react-essentials
2
2
 
3
- ## RestrictedContext
3
+ [![NPM Version](https://img.shields.io/npm/v/%40aweebit%2Freact-essentials)](https://www.npmjs.com/package/@aweebit/react-essentials)
4
4
 
5
- ```ts
6
- type RestrictedContext<T> =
7
- Context<T> extends Provider<T>
8
- ? {
9
- Provider: Provider<T>;
10
- displayName: string;
11
- } & Provider<T>
12
- : {
13
- Provider: Provider<T>;
14
- displayName: string;
15
- };
16
- ```
5
+ - [useEventListener()](#useeventlistener)
6
+ - [useForceUpdate()](#useforceupdate)
7
+ - [useReducerWithDeps()](#usereducerwithdeps)
8
+ - [useStateWithDeps()](#usestatewithdeps)
9
+ - [createSafeContext()](#createsafecontext)
10
+ - [RestrictedContext](#restrictedcontext)
11
+ - [SafeContext](#safecontext)
17
12
 
18
- Defined in: [misc/createSafeContext.ts:17](https://github.com/aweebit/react-essentials/blob/v0.6.0/src/misc/createSafeContext.ts#L17)
13
+ ## useEventListener()
19
14
 
20
- A React context with a required `displayName` and the obsolete `Consumer`
21
- property purposefully omitted so that it is impossible to pass the context
22
- as an argument to `useContext` or `use` (the hook produced with
23
- [`createSafeContext`](#createsafecontext) should be used instead)
15
+ Adds `handler` as a listener for the event `eventName` of `target` with the
16
+ provided `options` applied
24
17
 
25
- ### Type Parameters
18
+ If `target` is not provided, `window` is used instead.
26
19
 
27
- | Type Parameter |
28
- | -------------- |
29
- | `T` |
20
+ If `target` is `null`, no event listener is added. This is useful when
21
+ working with DOM element refs, or when the event listener needs to be removed
22
+ temporarily.
30
23
 
31
- ### See
24
+ ### Example
32
25
 
33
- [`createSafeContext`](#createsafecontext)
26
+ ```tsx
27
+ useEventListener('resize', () => {
28
+ console.log(window.innerWidth, window.innerHeight);
29
+ });
34
30
 
35
- ---
31
+ useEventListener(document, 'visibilitychange', () => {
32
+ console.log(document.visibilityState);
33
+ });
36
34
 
37
- ## SafeContext
35
+ const buttonRef = useRef<HTMLButtonElement>(null);
36
+ useEventListener(buttonRef.current, 'click', () => console.log('click'));
37
+ ```
38
+
39
+ ### Call Signature
38
40
 
39
41
  ```ts
40
- type SafeContext<DisplayName, T> = {
41
- [K in `${DisplayName}Context`]: RestrictedContext<T>;
42
- } & { [K in `use${DisplayName}`]: () => T };
42
+ function useEventListener<K>(eventName, handler, options?): void;
43
43
  ```
44
44
 
45
- Defined in: [misc/createSafeContext.ts:25](https://github.com/aweebit/react-essentials/blob/v0.6.0/src/misc/createSafeContext.ts#L25)
45
+ Defined in: [hooks/useEventListener.ts:99](https://github.com/aweebit/react-essentials/blob/v0.7.0/src/hooks/useEventListener.ts#L99)
46
46
 
47
- ### Type Parameters
47
+ #### Type Parameters
48
48
 
49
- | Type Parameter |
50
- | -------------------------------- |
51
- | `DisplayName` _extends_ `string` |
52
- | `T` |
49
+ <table>
50
+ <thead>
51
+ <tr>
52
+ <th>Type Parameter</th>
53
+ </tr>
54
+ </thead>
55
+ <tbody>
56
+ <tr>
57
+ <td>
53
58
 
54
- ### See
59
+ `K` _extends_ keyof `WindowEventMap`
55
60
 
56
- [`createSafeContext`](#createsafecontext)
61
+ </td>
62
+ </tr>
63
+ </tbody>
64
+ </table>
57
65
 
58
- ---
66
+ #### Parameters
59
67
 
60
- ## useEventListener()
68
+ <table>
69
+ <thead>
70
+ <tr>
71
+ <th>Parameter</th>
72
+ <th>Type</th>
73
+ </tr>
74
+ </thead>
75
+ <tbody>
76
+ <tr>
77
+ <td>
78
+
79
+ `eventName`
80
+
81
+ </td>
82
+ <td>
83
+
84
+ `K`
85
+
86
+ </td>
87
+ </tr>
88
+ <tr>
89
+ <td>
90
+
91
+ `handler`
92
+
93
+ </td>
94
+ <td>
95
+
96
+ (`this`, `event`) => `void`
97
+
98
+ </td>
99
+ </tr>
100
+ <tr>
101
+ <td>
102
+
103
+ `options?`
104
+
105
+ </td>
106
+ <td>
107
+
108
+ `boolean` \| `AddEventListenerOptions`
109
+
110
+ </td>
111
+ </tr>
112
+ </tbody>
113
+ </table>
114
+
115
+ #### Returns
116
+
117
+ `void`
118
+
119
+ #### See
120
+
121
+ [`useEventListener`](#useeventlistener)
122
+
123
+ ### Call Signature
61
124
 
62
125
  ```ts
63
- function useEventListener<T>(eventName, handler, element?, options?): void;
126
+ function useEventListener<T>(target, eventName, handler, options?): void;
64
127
  ```
65
128
 
66
- Defined in: [hooks/useEventListener.ts:120](https://github.com/aweebit/react-essentials/blob/v0.6.0/src/hooks/useEventListener.ts#L120)
129
+ Defined in: [hooks/useEventListener.ts:108](https://github.com/aweebit/react-essentials/blob/v0.7.0/src/hooks/useEventListener.ts#L108)
67
130
 
68
- Adds `handler` as a listener for the event `eventName` of `element` with the
69
- provided `options` applied
131
+ #### Type Parameters
70
132
 
71
- If `element` is `undefined`, `window` is used instead.
133
+ <table>
134
+ <thead>
135
+ <tr>
136
+ <th>Type Parameter</th>
137
+ </tr>
138
+ </thead>
139
+ <tbody>
140
+ <tr>
141
+ <td>
72
142
 
73
- If `element` is `null`, no event listener is added.
143
+ `T` _extends_ `EventTarget`
74
144
 
75
- ### Example
145
+ </td>
146
+ </tr>
147
+ </tbody>
148
+ </table>
76
149
 
77
- ```tsx
78
- useEventListener('resize', () => {
79
- console.log(window.innerWidth, window.innerHeight);
80
- });
150
+ #### Parameters
81
151
 
82
- useEventListener(
83
- 'visibilitychange',
84
- () => console.log(document.visibilityState),
85
- document,
86
- );
152
+ <table>
153
+ <thead>
154
+ <tr>
155
+ <th>Parameter</th>
156
+ <th>Type</th>
157
+ </tr>
158
+ </thead>
159
+ <tbody>
160
+ <tr>
161
+ <td>
87
162
 
88
- const buttonRef = useRef<HTMLButtonElement>(null);
89
- useEventListener('click', () => console.log('click'), buttonRef.current);
90
- ```
163
+ `target`
91
164
 
92
- ### Type Parameters
165
+ </td>
166
+ <td>
93
167
 
94
- | Type Parameter |
95
- | --------------------------- |
96
- | `T` _extends_ `EventTarget` |
168
+ `null` \| `T`
97
169
 
98
- ### Parameters
170
+ </td>
171
+ </tr>
172
+ <tr>
173
+ <td>
99
174
 
100
- | Parameter | Type |
101
- | ----------- | -------------------------------------- |
102
- | `eventName` | `string` |
103
- | `handler` | (`this`, `event`) => `void` |
104
- | `element?` | `null` \| `T` |
105
- | `options?` | `boolean` \| `AddEventListenerOptions` |
175
+ `eventName`
106
176
 
107
- ### Returns
177
+ </td>
178
+ <td>
179
+
180
+ `string`
181
+
182
+ </td>
183
+ </tr>
184
+ <tr>
185
+ <td>
186
+
187
+ `handler`
188
+
189
+ </td>
190
+ <td>
191
+
192
+ (`this`, `event`) => `void`
193
+
194
+ </td>
195
+ </tr>
196
+ <tr>
197
+ <td>
198
+
199
+ `options?`
200
+
201
+ </td>
202
+ <td>
203
+
204
+ `boolean` \| `AddEventListenerOptions`
205
+
206
+ </td>
207
+ </tr>
208
+ </tbody>
209
+ </table>
210
+
211
+ #### Returns
108
212
 
109
213
  `void`
110
214
 
215
+ #### See
216
+
217
+ [`useEventListener`](#useeventlistener)
218
+
111
219
  ---
112
220
 
113
221
  ## useForceUpdate()
@@ -116,18 +224,104 @@ useEventListener('click', () => console.log('click'), buttonRef.current);
116
224
  function useForceUpdate(callback?): [() => void, bigint];
117
225
  ```
118
226
 
119
- Defined in: [hooks/useForceUpdate.ts:32](https://github.com/aweebit/react-essentials/blob/v0.6.0/src/hooks/useForceUpdate.ts#L32)
227
+ Defined in: [hooks/useForceUpdate.ts:81](https://github.com/aweebit/react-essentials/blob/v0.7.0/src/hooks/useForceUpdate.ts#L81)
120
228
 
121
229
  Enables you to imperatively trigger re-rendering of components
122
230
 
123
231
  This hook is designed in the most general way possible in order to cover all
124
232
  imaginable use cases.
125
233
 
234
+ ### Example
235
+
236
+ Sometimes, React's immutability constraints mean too much unnecessary copying
237
+ of data when new data arrives at a high frequency. In such cases, it might be
238
+ desirable to ignore the constraints by embracing imperative patterns.
239
+ Here is an example of a scenario where that can make sense:
240
+
241
+ ```tsx
242
+ type SensorData = { timestamp: number; value: number };
243
+ const sensorDataRef = useRef<SensorData[]>([]);
244
+ const mostRecentSensorDataTimestampRef = useRef<number>(0);
245
+
246
+ const [forceUpdate, updateCount] = useForceUpdate();
247
+ // Limiting the frequency of forced re-renders with some throttle function:
248
+ const throttledForceUpdateRef = useRef(throttle(forceUpdate));
249
+
250
+ useEffect(() => {
251
+ return sensorDataObservable.subscribe((data: SensorData) => {
252
+ // Imagine new sensor data arrives every 1 millisecond. If we were following
253
+ // React's immutability rules by creating a new array every time, the data
254
+ // that's already there would have to be copied many times before the new
255
+ // data would even get a chance to be reflected in the UI for the first time
256
+ // because it typically takes much longer than 1 millisecond for a new frame
257
+ // to be displayed. To prevent the waste of computational resources, we just
258
+ // mutate the existing array every time instead:
259
+ sensorDataRef.current.push(data);
260
+ if (data.timestamp > mostRecentSensorDataTimestampRef.current) {
261
+ mostRecentSensorDataTimestampRef.current = data.timestamp;
262
+ }
263
+ throttledForceUpdateRef.current();
264
+ });
265
+ }, []);
266
+
267
+ const [timeWindow, setTimeWindow] = useState(1000);
268
+ const selectedSensorData = useMemo(
269
+ () => {
270
+ // Keep this line if you don't want to disable the
271
+ // react-hooks/exhaustive-deps ESLint rule:
272
+ updateCount;
273
+ const threshold = mostRecentSensorDataTimestampRef.current - timeWindow;
274
+ return sensorDataRef.current.filter(
275
+ ({ timestamp }) => timestamp >= threshold,
276
+ );
277
+ },
278
+ // sensorDataRef.current always references the same array, so listing it as a
279
+ // dependency is pointless. Instead, updateCount should be used:
280
+ [updateCount, timeWindow],
281
+ );
282
+ ```
283
+
126
284
  ### Parameters
127
285
 
128
- | Parameter | Type | Description |
129
- | ----------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
130
- | `callback?` | () => `void` | An optional callback function to call during renders that were triggered with `forceUpdate()` Can be used for conditionally calling state setters when state needs to be reset. That is legal and better than using effects (see [You Might Not Need an Effect \> Adjusting some state when a prop changes](https://react.dev/learn/-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes)), but can often be avoided by using [`useStateWithDeps`](#usestatewithdeps) or [`useReducerWithDeps`](#usereducerwithdeps). Important: the callback function is called once per render, not once per `forceUpdate` call! If React batches `forceUpdate` calls, then it will only be called once. |
286
+ <table>
287
+ <thead>
288
+ <tr>
289
+ <th>Parameter</th>
290
+ <th>Type</th>
291
+ <th>Description</th>
292
+ </tr>
293
+ </thead>
294
+ <tbody>
295
+ <tr>
296
+ <td>
297
+
298
+ `callback?`
299
+
300
+ </td>
301
+ <td>
302
+
303
+ () => `void`
304
+
305
+ </td>
306
+ <td>
307
+
308
+ An optional callback function to call during renders that
309
+ were triggered with `forceUpdate()`
310
+
311
+ Can be used for conditionally calling state setters when state needs to be
312
+ reset. That is legal and better than using effects (see
313
+ [You Might Not Need an Effect \> Adjusting some state when a prop changes](https://react.dev/learn/-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes)),
314
+ but can often be avoided by using [`useStateWithDeps`](#usestatewithdeps) or
315
+ [`useReducerWithDeps`](#usereducerwithdeps).
316
+
317
+ Important: the callback function is called once per render, not once per
318
+ `forceUpdate` call! If React batches `forceUpdate` calls, then it will only
319
+ be called once.
320
+
321
+ </td>
322
+ </tr>
323
+ </tbody>
324
+ </table>
131
325
 
132
326
  ### Returns
133
327
 
@@ -150,11 +344,14 @@ function useReducerWithDeps<S, A>(
150
344
  ): [S, ActionDispatch<A>];
151
345
  ```
152
346
 
153
- Defined in: [hooks/useReducerWithDeps.ts:49](https://github.com/aweebit/react-essentials/blob/v0.6.0/src/hooks/useReducerWithDeps.ts#L49)
347
+ Defined in: [hooks/useReducerWithDeps.ts:52](https://github.com/aweebit/react-essentials/blob/v0.7.0/src/hooks/useReducerWithDeps.ts#L52)
154
348
 
155
349
  `useReducer` hook with an additional dependency array `deps` that resets the
156
350
  state to `initialState` when dependencies change
157
351
 
352
+ For motivation and examples, see
353
+ https://github.com/facebook/react/issues/33041.
354
+
158
355
  ### On linter support
159
356
 
160
357
  The `react-hooks/exhaustive-deps` ESLint rule doesn't support hooks where
@@ -163,7 +360,7 @@ However, as we would like to keep the hook as compatible with `useReducer` as
163
360
  possible, we don't want to artificially change the parameter's position.
164
361
  Therefore, there will be no warnings about missing dependencies.
165
362
  Because of that, additional caution is advised!
166
- Be sure to check no dependencies are missing from the `deps` array.
363
+ Be sure to check that no dependencies are missing from the `deps` array.
167
364
 
168
365
  Related issue: [https://github.com/facebook/react/issues/25443](https://github.com/facebook/react/issues/25443).
169
366
 
@@ -174,18 +371,99 @@ does actually have support for dependency arrays at other positions, see
174
371
 
175
372
  ### Type Parameters
176
373
 
177
- | Type Parameter |
178
- | ---------------------------- |
179
- | `S` |
180
- | `A` _extends_ `AnyActionArg` |
374
+ <table>
375
+ <thead>
376
+ <tr>
377
+ <th>Type Parameter</th>
378
+ </tr>
379
+ </thead>
380
+ <tbody>
381
+ <tr>
382
+ <td>
383
+
384
+ `S`
385
+
386
+ </td>
387
+ </tr>
388
+ <tr>
389
+ <td>
390
+
391
+ `A` _extends_ `AnyActionArg`
392
+
393
+ </td>
394
+ </tr>
395
+ </tbody>
396
+ </table>
181
397
 
182
398
  ### Parameters
183
399
 
184
- | Parameter | Type | Description |
185
- | -------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
186
- | `reducer` | (`prevState`, ...`args`) => `S` | The reducer function that specifies how the state gets updated |
187
- | `initialState` | `S` \| (`previousState?`) => `S` | The value to which the state is set when the component is mounted or dependencies change It can also be a function that returns a state value. If the state is reset due to a change of dependencies, this function will be passed the previous state as its argument (will be `undefined` in the first call upon mount). |
188
- | `deps` | `DependencyList` | Dependencies that reset the state to `initialState` |
400
+ <table>
401
+ <thead>
402
+ <tr>
403
+ <th>Parameter</th>
404
+ <th>Type</th>
405
+ <th>Description</th>
406
+ </tr>
407
+ </thead>
408
+ <tbody>
409
+ <tr>
410
+ <td>
411
+
412
+ `reducer`
413
+
414
+ </td>
415
+ <td>
416
+
417
+ (`prevState`, ...`args`) => `S`
418
+
419
+ </td>
420
+ <td>
421
+
422
+ The reducer function that specifies how the state gets updated
423
+
424
+ </td>
425
+ </tr>
426
+ <tr>
427
+ <td>
428
+
429
+ `initialState`
430
+
431
+ </td>
432
+ <td>
433
+
434
+ `S` \| (`previousState?`) => `S`
435
+
436
+ </td>
437
+ <td>
438
+
439
+ The value to which the state is set when the component is
440
+ mounted or dependencies change
441
+
442
+ It can also be a function that returns a state value. If the state is reset
443
+ due to a change of dependencies, this function will be passed the previous
444
+ state as its argument (will be `undefined` in the first call upon mount).
445
+
446
+ </td>
447
+ </tr>
448
+ <tr>
449
+ <td>
450
+
451
+ `deps`
452
+
453
+ </td>
454
+ <td>
455
+
456
+ `DependencyList`
457
+
458
+ </td>
459
+ <td>
460
+
461
+ Dependencies that reset the state to `initialState`
462
+
463
+ </td>
464
+ </tr>
465
+ </tbody>
466
+ </table>
189
467
 
190
468
  ### Returns
191
469
 
@@ -202,23 +480,118 @@ function useStateWithDeps<S>(
202
480
  ): [S, Dispatch<SetStateAction<S>>];
203
481
  ```
204
482
 
205
- Defined in: [hooks/useStateWithDeps.ts:31](https://github.com/aweebit/react-essentials/blob/v0.6.0/src/hooks/useStateWithDeps.ts#L31)
483
+ Defined in: [hooks/useStateWithDeps.ts:66](https://github.com/aweebit/react-essentials/blob/v0.7.0/src/hooks/useStateWithDeps.ts#L66)
206
484
 
207
485
  `useState` hook with an additional dependency array `deps` that resets the
208
486
  state to `initialState` when dependencies change
209
487
 
488
+ For motivation and more examples, see
489
+ https://github.com/facebook/react/issues/33041.
490
+
491
+ ### Example
492
+
493
+ ```tsx
494
+ type Activity = 'breakfast' | 'exercise' | 'swim' | 'board games' | 'dinner';
495
+
496
+ const timeOfDayOptions = ['morning', 'afternoon', 'evening'] as const;
497
+ type TimeOfDay = (typeof timeOfDayOptions)[number];
498
+
499
+ const activityOptionsByTimeOfDay: {
500
+ [K in TimeOfDay]: [Activity, ...Activity[]];
501
+ } = {
502
+ morning: ['breakfast', 'exercise', 'swim'],
503
+ afternoon: ['exercise', 'swim', 'board games'],
504
+ evening: ['board games', 'dinner'],
505
+ };
506
+
507
+ export function Example() {
508
+ const [timeOfDay, setTimeOfDay] = useState<TimeOfDay>('morning');
509
+
510
+ const activityOptions = activityOptionsByTimeOfDay[timeOfDay];
511
+ const [activity, setActivity] = useStateWithDeps<Activity>(
512
+ (prev) => {
513
+ // Make sure activity is always valid for the current timeOfDay value,
514
+ // but also don't reset it unless necessary:
515
+ return prev && activityOptions.includes(prev) ? prev : activityOptions[0];
516
+ },
517
+ [activityOptions],
518
+ );
519
+
520
+ return '...';
521
+ }
522
+ ```
523
+
210
524
  ### Type Parameters
211
525
 
212
- | Type Parameter |
213
- | -------------- |
214
- | `S` |
526
+ <table>
527
+ <thead>
528
+ <tr>
529
+ <th>Type Parameter</th>
530
+ </tr>
531
+ </thead>
532
+ <tbody>
533
+ <tr>
534
+ <td>
535
+
536
+ `S`
537
+
538
+ </td>
539
+ </tr>
540
+ </tbody>
541
+ </table>
215
542
 
216
543
  ### Parameters
217
544
 
218
- | Parameter | Type | Description |
219
- | -------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
220
- | `initialState` | `S` \| (`previousState?`) => `S` | The value to which the state is set when the component is mounted or dependencies change It can also be a function that returns a state value. If the state is reset due to a change of dependencies, this function will be passed the previous state as its argument (will be `undefined` in the first call upon mount). |
221
- | `deps` | `DependencyList` | Dependencies that reset the state to `initialState` |
545
+ <table>
546
+ <thead>
547
+ <tr>
548
+ <th>Parameter</th>
549
+ <th>Type</th>
550
+ <th>Description</th>
551
+ </tr>
552
+ </thead>
553
+ <tbody>
554
+ <tr>
555
+ <td>
556
+
557
+ `initialState`
558
+
559
+ </td>
560
+ <td>
561
+
562
+ `S` \| (`previousState?`) => `S`
563
+
564
+ </td>
565
+ <td>
566
+
567
+ The value to which the state is set when the component is
568
+ mounted or dependencies change
569
+
570
+ It can also be a function that returns a state value. If the state is reset
571
+ due to a change of dependencies, this function will be passed the previous
572
+ state as its argument (will be `undefined` in the first call upon mount).
573
+
574
+ </td>
575
+ </tr>
576
+ <tr>
577
+ <td>
578
+
579
+ `deps`
580
+
581
+ </td>
582
+ <td>
583
+
584
+ `DependencyList`
585
+
586
+ </td>
587
+ <td>
588
+
589
+ Dependencies that reset the state to `initialState`
590
+
591
+ </td>
592
+ </tr>
593
+ </tbody>
594
+ </table>
222
595
 
223
596
  ### Returns
224
597
 
@@ -234,39 +607,89 @@ function createSafeContext<T>(): <DisplayName>(
234
607
  ) => SafeContext<DisplayName, T>;
235
608
  ```
236
609
 
237
- Defined in: [misc/createSafeContext.ts:56](https://github.com/aweebit/react-essentials/blob/v0.6.0/src/misc/createSafeContext.ts#L56)
610
+ Defined in: [misc/createSafeContext.ts:89](https://github.com/aweebit/react-essentials/blob/v0.7.0/src/misc/createSafeContext.ts#L89)
238
611
 
239
612
  For a given type `T`, returns a function that produces both a context of that
240
613
  type and a hook that returns the current context value if one was provided,
241
614
  or throws an error otherwise
242
615
 
616
+ The advantages over vanilla `createContext` are that no default value has to
617
+ be provided, and that a meaningful context name is displayed in dev tools
618
+ instead of generic `Context.Provider`.
619
+
243
620
  ### Example
244
621
 
245
622
  ```tsx
246
- const { ItemsContext, useItems } = createSafeContext<string[]>()('Items');
623
+ enum Direction {
624
+ Up,
625
+ Down,
626
+ Left,
627
+ Right,
628
+ }
629
+
630
+ // Before
631
+ const DirectionContext = createContext<Direction | undefined>(undefined);
632
+ DirectionContext.displayName = 'DirectionContext';
633
+
634
+ const useDirection = () => {
635
+ const direction = useContext(DirectionContext);
636
+ if (direction === undefined) {
637
+ // Called outside of a <DirectionContext.Provider> boundary!
638
+ // Or maybe undefined was explicitly provided as the context value
639
+ // (ideally that shouldn't be allowed, but it is because we had to include
640
+ // undefined in the context type so as to provide a meaningful default)
641
+ throw new Error('No DirectionContext value was provided');
642
+ }
643
+ // Thanks to the undefined check, the type is now narrowed down to Direction
644
+ return direction;
645
+ };
646
+
647
+ // After
648
+ const { DirectionContext, useDirection } =
649
+ createSafeContext<Direction>()('Direction'); // That's it :)
247
650
 
248
651
  const Parent = () => (
249
- <ItemsContext value={['compass', 'newspaper', 'banana']}>
652
+ // Providing undefined as the value is not allowed 👍
653
+ <Direction.Provider value={Direction.Up}>
250
654
  <Child />
251
- </ItemsContext>
655
+ </Direction.Provider>
252
656
  );
253
657
 
254
- const Child = () => useItems().join(', ');
658
+ const Child = () => `Current direction: ${Direction[useDirection()]}`;
255
659
  ```
256
660
 
257
661
  ### Type Parameters
258
662
 
259
- | Type Parameter | Default type |
260
- | -------------- | ------------ |
261
- | `T` | `never` |
663
+ <table>
664
+ <thead>
665
+ <tr>
666
+ <th>Type Parameter</th>
667
+ <th>Default type</th>
668
+ </tr>
669
+ </thead>
670
+ <tbody>
671
+ <tr>
672
+ <td>
673
+
674
+ `T`
675
+
676
+ </td>
677
+ <td>
678
+
679
+ `never`
680
+
681
+ </td>
682
+ </tr>
683
+ </tbody>
684
+ </table>
262
685
 
263
686
  ### Returns
264
687
 
265
688
  A function that accepts a single string argument `displayName` (e.g.
266
- `"Items"`) and returns an object with the following properties:
689
+ `"Direction"`) and returns an object with the following properties:
267
690
 
268
- - `` `${displayName}Context` `` (e.g. `ItemsContext`): the context
269
- - `` `use${displayName}` `` (e.g. `useItems`): a hook that returns the
691
+ - `` `${displayName}Context` `` (e.g. `DirectionContext`): the context
692
+ - `` `use${displayName}` `` (e.g. `useDirection`): a hook that returns the
270
693
  current context value if one was provided, or throws an error otherwise
271
694
 
272
695
  ```ts
@@ -275,16 +698,139 @@ A function that accepts a single string argument `displayName` (e.g.
275
698
 
276
699
  #### Type Parameters
277
700
 
278
- | Type Parameter |
279
- | -------------------------------- |
280
- | `DisplayName` _extends_ `string` |
701
+ <table>
702
+ <thead>
703
+ <tr>
704
+ <th>Type Parameter</th>
705
+ </tr>
706
+ </thead>
707
+ <tbody>
708
+ <tr>
709
+ <td>
710
+
711
+ `DisplayName` _extends_ `string`
712
+
713
+ </td>
714
+ </tr>
715
+ </tbody>
716
+ </table>
281
717
 
282
718
  #### Parameters
283
719
 
284
- | Parameter | Type |
285
- | ------------- | ------------------------------------------------------------------------------------------------ |
286
- | `displayName` | \[`T`\] _extends_ \[`never`\] ? `never` : `ArgumentFallback`\<`DisplayName`, `never`, `string`\> |
720
+ <table>
721
+ <thead>
722
+ <tr>
723
+ <th>Parameter</th>
724
+ <th>Type</th>
725
+ </tr>
726
+ </thead>
727
+ <tbody>
728
+ <tr>
729
+ <td>
730
+
731
+ `displayName`
732
+
733
+ </td>
734
+ <td>
735
+
736
+ \[`T`\] _extends_ \[`never`\] ? `never` : `ArgumentFallback`\<`DisplayName`, `never`, `string`\>
737
+
738
+ </td>
739
+ </tr>
740
+ </tbody>
741
+ </table>
287
742
 
288
743
  #### Returns
289
744
 
290
745
  [`SafeContext`](#safecontext)\<`DisplayName`, `T`\>
746
+
747
+ ---
748
+
749
+ ## RestrictedContext
750
+
751
+ ```ts
752
+ type RestrictedContext<T> =
753
+ Context<T> extends Provider<T>
754
+ ? {
755
+ Provider: Provider<T>;
756
+ displayName: string;
757
+ } & Provider<T>
758
+ : {
759
+ Provider: Provider<T>;
760
+ displayName: string;
761
+ };
762
+ ```
763
+
764
+ Defined in: [misc/createSafeContext.ts:17](https://github.com/aweebit/react-essentials/blob/v0.7.0/src/misc/createSafeContext.ts#L17)
765
+
766
+ A React context with a required `displayName` and the obsolete `Consumer`
767
+ property purposefully omitted so that it is impossible to pass the context
768
+ as an argument to `useContext` or `use` (the hook produced with
769
+ [`createSafeContext`](#createsafecontext) should be used instead)
770
+
771
+ ### Type Parameters
772
+
773
+ <table>
774
+ <thead>
775
+ <tr>
776
+ <th>Type Parameter</th>
777
+ </tr>
778
+ </thead>
779
+ <tbody>
780
+ <tr>
781
+ <td>
782
+
783
+ `T`
784
+
785
+ </td>
786
+ </tr>
787
+ </tbody>
788
+ </table>
789
+
790
+ ### See
791
+
792
+ [`createSafeContext`](#createsafecontext)
793
+
794
+ ---
795
+
796
+ ## SafeContext
797
+
798
+ ```ts
799
+ type SafeContext<DisplayName, T> = {
800
+ [K in `${DisplayName}Context`]: RestrictedContext<T>;
801
+ } & { [K in `use${DisplayName}`]: () => T };
802
+ ```
803
+
804
+ Defined in: [misc/createSafeContext.ts:27](https://github.com/aweebit/react-essentials/blob/v0.7.0/src/misc/createSafeContext.ts#L27)
805
+
806
+ The return type of [`createSafeContext`](#createsafecontext)
807
+
808
+ ### Type Parameters
809
+
810
+ <table>
811
+ <thead>
812
+ <tr>
813
+ <th>Type Parameter</th>
814
+ </tr>
815
+ </thead>
816
+ <tbody>
817
+ <tr>
818
+ <td>
819
+
820
+ `DisplayName` _extends_ `string`
821
+
822
+ </td>
823
+ </tr>
824
+ <tr>
825
+ <td>
826
+
827
+ `T`
828
+
829
+ </td>
830
+ </tr>
831
+ </tbody>
832
+ </table>
833
+
834
+ ### See
835
+
836
+ [`createSafeContext`](#createsafecontext)