@dcl/hooks 1.2.1-20260218182647.commit-7c0554a → 1.2.2-20260218215717.commit-9afb22b
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 +39 -785
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,794 +10,48 @@ A collection of React hooks commonly used in Decentraland projects.
|
|
|
10
10
|
npm install @dcl/hooks
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `useAdvancedUserAgentData`: Enhanced user agent information
|
|
16
|
-
- `useAsyncState`: Async state management with dependencies
|
|
17
|
-
- `useAsyncTask`: Single async task management
|
|
18
|
-
- `useAsyncTasks`: Multiple async tasks management
|
|
19
|
-
- `usePatchState`: Partial state updates for complex objects
|
|
20
|
-
- `useAsyncEffect`: Async version of useEffect
|
|
21
|
-
- `useAsyncMemo`: Async version of useMemo
|
|
22
|
-
- `useInfiniteScroll`: Infinite scroll functionality for loading more content
|
|
23
|
-
- `useTranslation`: Simple and lightweight translation management
|
|
24
|
-
- `useNotifications`: Notification polling and modal state management
|
|
25
|
-
|
|
26
|
-
## Examples
|
|
27
|
-
|
|
28
|
-
### useAsyncState
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
import { useAsyncState } from '@dcl/hooks'
|
|
32
|
-
|
|
33
|
-
function Example() {
|
|
34
|
-
const [data, { loading, error }] = useAsyncState(
|
|
35
|
-
async () => {
|
|
36
|
-
const response = await fetch('https://api.example.com/data')
|
|
37
|
-
return response.json()
|
|
38
|
-
},
|
|
39
|
-
[] // dependencies
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
if (loading) return <div>Loading...</div>
|
|
43
|
-
if (error) return <div>Error: {error.message}</div>
|
|
44
|
-
return <div>{JSON.stringify(data)}</div>
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### useAdvancedUserAgentData
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
import { useAdvancedUserAgentData } from '@dcl/hooks'
|
|
52
|
-
|
|
53
|
-
function BrowserInfo() {
|
|
54
|
-
const [isLoading, data] = useAdvancedUserAgentData()
|
|
55
|
-
|
|
56
|
-
if (isLoading) return <div>Loading browser info...</div>
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<div>
|
|
60
|
-
<h3>Browser Information</h3>
|
|
61
|
-
<ul>
|
|
62
|
-
<li>Browser: {data?.browser.name} {data?.browser.version}</li>
|
|
63
|
-
<li>OS: {data?.os.name} {data?.os.version}</li>
|
|
64
|
-
<li>CPU Architecture: {data?.cpu.architecture}</li>
|
|
65
|
-
<li>Mobile Device: {data?.mobile ? 'Yes' : 'No'}</li>
|
|
66
|
-
</ul>
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### useInfiniteScroll
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { useInfiniteScroll } from '@dcl/hooks'
|
|
76
|
-
import { useState, useEffect } from 'react'
|
|
77
|
-
|
|
78
|
-
function InfiniteList() {
|
|
79
|
-
const [items, setItems] = useState<string[]>([])
|
|
80
|
-
const [hasMore, setHasMore] = useState(true)
|
|
81
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
82
|
-
|
|
83
|
-
const loadMore = async () => {
|
|
84
|
-
if (isLoading) return
|
|
85
|
-
|
|
86
|
-
setIsLoading(true)
|
|
87
|
-
try {
|
|
88
|
-
// Simulate API call
|
|
89
|
-
const newItems = await fetchMoreItems(items.length)
|
|
90
|
-
setItems((prev) => [...prev, ...newItems])
|
|
91
|
-
|
|
92
|
-
// Check if there's more data
|
|
93
|
-
setHasMore(newItems.length > 0)
|
|
94
|
-
} catch (error) {
|
|
95
|
-
console.error('Failed to load more items:', error)
|
|
96
|
-
} finally {
|
|
97
|
-
setIsLoading(false)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
useInfiniteScroll({
|
|
102
|
-
onLoadMore: loadMore,
|
|
103
|
-
hasMore,
|
|
104
|
-
isLoading,
|
|
105
|
-
threshold: 500, // Trigger when 500px from bottom
|
|
106
|
-
debounceMs: 500, // Minimum time between triggers (default: 500ms)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<div>
|
|
111
|
-
{items.map((item, index) => (
|
|
112
|
-
<div key={index}>{item}</div>
|
|
113
|
-
))}
|
|
114
|
-
{isLoading && <div>Loading more...</div>}
|
|
115
|
-
{!hasMore && <div>No more items</div>}
|
|
116
|
-
</div>
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Analytics Hooks
|
|
122
|
-
|
|
123
|
-
The package provides a set of hooks for analytics tracking using Segment. Here's how to use them:
|
|
124
|
-
|
|
125
|
-
#### Setting up the AnalyticsProvider
|
|
126
|
-
|
|
127
|
-
First, wrap your app with the AnalyticsProvider:
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
import { AnalyticsProvider } from '@dcl/hooks'
|
|
131
|
-
|
|
132
|
-
function App() {
|
|
133
|
-
return (
|
|
134
|
-
<AnalyticsProvider
|
|
135
|
-
writeKey="xyz1234"
|
|
136
|
-
userId="user-123" // Optional
|
|
137
|
-
traits={{ // Optional
|
|
138
|
-
name: 'John Doe',
|
|
139
|
-
email: 'john@example.com'
|
|
140
|
-
}}
|
|
141
|
-
>
|
|
142
|
-
<Main />
|
|
143
|
-
</AnalyticsProvider>
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
#### Using the useAnalytics Hook
|
|
149
|
-
|
|
150
|
-
The `useAnalytics` hook provides access to tracking functions:
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
import { useAnalytics } from '@dcl/hooks'
|
|
154
|
-
|
|
155
|
-
function MyComponent() {
|
|
156
|
-
const analytics = useAnalytics()
|
|
157
|
-
|
|
158
|
-
const handleButtonClick = () => {
|
|
159
|
-
if (analytics.isInitialized) {
|
|
160
|
-
// Track an event
|
|
161
|
-
analytics.track('Button Clicked', {
|
|
162
|
-
buttonId: 'submit',
|
|
163
|
-
timestamp: new Date().toISOString()
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
// Identify a user
|
|
167
|
-
analytics.identify('user-123', {
|
|
168
|
-
name: 'John Doe',
|
|
169
|
-
email: 'john@example.com'
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<button onClick={handleButtonClick}>
|
|
176
|
-
Click me
|
|
177
|
-
</button>
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
#### Using the usePageTracking Hook
|
|
183
|
-
|
|
184
|
-
The `usePageTracking` hook tracks page views when the pathname changes. You need to pass the current pathname as a parameter from your router:
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
import { usePageTracking } from '@dcl/hooks'
|
|
188
|
-
import { useLocation } from 'react-router-dom'
|
|
13
|
+
### Peer Dependencies
|
|
189
14
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// Tracks page view when pathname changes
|
|
194
|
-
usePageTracking(location.pathname)
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<div>
|
|
198
|
-
<h1>My Page</h1>
|
|
199
|
-
{/* Your page content */}
|
|
200
|
-
</div>
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
If you need to track additional page properties, you can use the `useAnalytics` hook directly:
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
import { useAnalytics } from '@dcl/hooks'
|
|
209
|
-
|
|
210
|
-
function MyPage() {
|
|
211
|
-
const analytics = useAnalytics()
|
|
212
|
-
|
|
213
|
-
useEffect(() => {
|
|
214
|
-
if (analytics.isInitialized) {
|
|
215
|
-
analytics.page('My Page', {
|
|
216
|
-
category: 'Content',
|
|
217
|
-
section: 'Main',
|
|
218
|
-
timestamp: new Date().toISOString()
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
}, [analytics])
|
|
222
|
-
|
|
223
|
-
return (
|
|
224
|
-
<div>
|
|
225
|
-
<h1>My Page</h1>
|
|
226
|
-
{/* Your page content */}
|
|
227
|
-
</div>
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
#### Complete Example
|
|
233
|
-
|
|
234
|
-
Here's a complete example showing how to use all analytics features together:
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
import { AnalyticsProvider, useAnalytics, usePageTracking } from '@dcl/hooks'
|
|
238
|
-
import { useLocation } from 'react-router-dom'
|
|
239
|
-
|
|
240
|
-
function MyPage() {
|
|
241
|
-
const location = useLocation()
|
|
242
|
-
|
|
243
|
-
// Track page views
|
|
244
|
-
usePageTracking(location.pathname)
|
|
245
|
-
|
|
246
|
-
return (
|
|
247
|
-
<div>
|
|
248
|
-
<h1>My Page</h1>
|
|
249
|
-
<UserProfile />
|
|
250
|
-
</div>
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function UserProfile() {
|
|
255
|
-
const analytics = useAnalytics()
|
|
256
|
-
|
|
257
|
-
const handleProfileUpdate = () => {
|
|
258
|
-
if (analytics.isInitialized) {
|
|
259
|
-
// Track profile update event
|
|
260
|
-
analytics.track('Profile Updated', {
|
|
261
|
-
timestamp: new Date().toISOString(),
|
|
262
|
-
updateType: 'information'
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
// Update user traits
|
|
266
|
-
analytics.identify('user-123', {
|
|
267
|
-
lastUpdated: new Date().toISOString()
|
|
268
|
-
})
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
<button onClick={handleProfileUpdate}>
|
|
274
|
-
Update Profile
|
|
275
|
-
</button>
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function App() {
|
|
280
|
-
return (
|
|
281
|
-
<AnalyticsProvider
|
|
282
|
-
writeKey="xyz1234"
|
|
283
|
-
userId="user-123"
|
|
284
|
-
traits={{
|
|
285
|
-
name: 'John Doe',
|
|
286
|
-
email: 'john@example.com'
|
|
287
|
-
}}
|
|
288
|
-
>
|
|
289
|
-
<MyPage />
|
|
290
|
-
</AnalyticsProvider>
|
|
291
|
-
)
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### Analytics Hooks
|
|
296
|
-
|
|
297
|
-
The package provides a set of hooks for analytics tracking using Segment. Here's how to use them:
|
|
298
|
-
|
|
299
|
-
#### Setting up the AnalyticsProvider
|
|
300
|
-
|
|
301
|
-
First, wrap your app with the AnalyticsProvider:
|
|
302
|
-
|
|
303
|
-
```typescript
|
|
304
|
-
import { AnalyticsProvider } from '@dcl/hooks'
|
|
305
|
-
|
|
306
|
-
function App() {
|
|
307
|
-
return (
|
|
308
|
-
<AnalyticsProvider
|
|
309
|
-
writeKey="xyz1234"
|
|
310
|
-
userId="user-123" // Optional
|
|
311
|
-
traits={{ // Optional
|
|
312
|
-
name: 'John Doe',
|
|
313
|
-
email: 'john@example.com'
|
|
314
|
-
}}
|
|
315
|
-
>
|
|
316
|
-
<Main />
|
|
317
|
-
</AnalyticsProvider>
|
|
318
|
-
)
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
#### Using the useAnalytics Hook
|
|
323
|
-
|
|
324
|
-
The `useAnalytics` hook provides access to tracking functions:
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
import { useAnalytics } from '@dcl/hooks'
|
|
328
|
-
|
|
329
|
-
function MyComponent() {
|
|
330
|
-
const analytics = useAnalytics()
|
|
331
|
-
|
|
332
|
-
const handleButtonClick = () => {
|
|
333
|
-
if (analytics.isInitialized) {
|
|
334
|
-
// Track an event
|
|
335
|
-
analytics.track('Button Clicked', {
|
|
336
|
-
buttonId: 'submit',
|
|
337
|
-
timestamp: new Date().toISOString()
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
// Identify a user
|
|
341
|
-
analytics.identify('user-123', {
|
|
342
|
-
name: 'John Doe',
|
|
343
|
-
email: 'john@example.com'
|
|
344
|
-
})
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return (
|
|
349
|
-
<button onClick={handleButtonClick}>
|
|
350
|
-
Click me
|
|
351
|
-
</button>
|
|
352
|
-
)
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
#### Using the usePageTracking Hook
|
|
357
|
-
|
|
358
|
-
The `usePageTracking` hook tracks page views when the pathname changes. You need to pass the current pathname as a parameter from your router:
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
import { usePageTracking } from '@dcl/hooks'
|
|
362
|
-
import { useLocation } from 'react-router-dom'
|
|
363
|
-
|
|
364
|
-
function MyPage() {
|
|
365
|
-
const location = useLocation()
|
|
366
|
-
|
|
367
|
-
// Tracks page view when pathname changes
|
|
368
|
-
usePageTracking(location.pathname)
|
|
369
|
-
|
|
370
|
-
return (
|
|
371
|
-
<div>
|
|
372
|
-
<h1>My Page</h1>
|
|
373
|
-
{/* Your page content */}
|
|
374
|
-
</div>
|
|
375
|
-
)
|
|
376
|
-
}
|
|
377
|
-
```
|
|
15
|
+
- `react` >= 18.0.0
|
|
16
|
+
- `decentraland-crypto-fetch` >= 2.0.1 (only needed for `useNotifications`)
|
|
378
17
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
```typescript
|
|
382
|
-
import { useAnalytics } from '@dcl/hooks'
|
|
383
|
-
|
|
384
|
-
function MyPage() {
|
|
385
|
-
const analytics = useAnalytics()
|
|
386
|
-
|
|
387
|
-
useEffect(() => {
|
|
388
|
-
if (analytics.isInitialized) {
|
|
389
|
-
analytics.page('My Page', {
|
|
390
|
-
category: 'Content',
|
|
391
|
-
section: 'Main',
|
|
392
|
-
timestamp: new Date().toISOString()
|
|
393
|
-
})
|
|
394
|
-
}
|
|
395
|
-
}, [analytics])
|
|
396
|
-
|
|
397
|
-
return (
|
|
398
|
-
<div>
|
|
399
|
-
<h1>My Page</h1>
|
|
400
|
-
{/* Your page content */}
|
|
401
|
-
</div>
|
|
402
|
-
)
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
#### Complete Example
|
|
407
|
-
|
|
408
|
-
Here's a complete example showing how to use all analytics features together:
|
|
409
|
-
|
|
410
|
-
```typescript
|
|
411
|
-
import { AnalyticsProvider, useAnalytics, usePageTracking } from '@dcl/hooks'
|
|
412
|
-
import { useLocation } from 'react-router-dom'
|
|
413
|
-
|
|
414
|
-
function MyPage() {
|
|
415
|
-
const location = useLocation()
|
|
416
|
-
|
|
417
|
-
// Track page views
|
|
418
|
-
usePageTracking(location.pathname)
|
|
419
|
-
|
|
420
|
-
return (
|
|
421
|
-
<div>
|
|
422
|
-
<h1>My Page</h1>
|
|
423
|
-
<UserProfile />
|
|
424
|
-
</div>
|
|
425
|
-
)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function UserProfile() {
|
|
429
|
-
const analytics = useAnalytics()
|
|
430
|
-
|
|
431
|
-
const handleProfileUpdate = () => {
|
|
432
|
-
if (analytics.isInitialized) {
|
|
433
|
-
// Track profile update event
|
|
434
|
-
analytics.track('Profile Updated', {
|
|
435
|
-
timestamp: new Date().toISOString(),
|
|
436
|
-
updateType: 'information'
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
// Update user traits
|
|
440
|
-
analytics.identify('user-123', {
|
|
441
|
-
lastUpdated: new Date().toISOString()
|
|
442
|
-
})
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return (
|
|
447
|
-
<button onClick={handleProfileUpdate}>
|
|
448
|
-
Update Profile
|
|
449
|
-
</button>
|
|
450
|
-
)
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function App() {
|
|
454
|
-
return (
|
|
455
|
-
<AnalyticsProvider
|
|
456
|
-
writeKey="xyz1234"
|
|
457
|
-
userId="user-123"
|
|
458
|
-
traits={{
|
|
459
|
-
name: 'John Doe',
|
|
460
|
-
email: 'john@example.com'
|
|
461
|
-
}}
|
|
462
|
-
>
|
|
463
|
-
<MyPage />
|
|
464
|
-
</AnalyticsProvider>
|
|
465
|
-
)
|
|
466
|
-
}
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
### useTranslation
|
|
470
|
-
|
|
471
|
-
The `useTranslation` hook provides i18n capabilities powered by `@formatjs/intl`, giving you access to advanced formatting functions for numbers, dates, currencies, and more.
|
|
472
|
-
|
|
473
|
-
Basic usage:
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
import { useTranslation } from '@dcl/hooks'
|
|
477
|
-
|
|
478
|
-
const translations = {
|
|
479
|
-
en: {
|
|
480
|
-
greeting: 'Hello, {name}!',
|
|
481
|
-
welcome: 'Welcome to our app',
|
|
482
|
-
items: '{count, plural, =0 {No items} one {# item} other {# items}}'
|
|
483
|
-
},
|
|
484
|
-
es: {
|
|
485
|
-
greeting: 'Hola, {name}!',
|
|
486
|
-
welcome: 'Bienvenido a nuestra aplicación',
|
|
487
|
-
items: '{count, plural, =0 {Sin elementos} one {# elemento} other {# elementos}}'
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function MyComponent() {
|
|
492
|
-
const { t, intl, locale, setLocale } = useTranslation({
|
|
493
|
-
locale: 'en',
|
|
494
|
-
translations
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
return (
|
|
498
|
-
<div>
|
|
499
|
-
<p>{t('greeting', { name: 'John' })}</p>
|
|
500
|
-
<p>{t('items', { count: 5 })}</p>
|
|
501
|
-
<button onClick={() => setLocale('es')}>
|
|
502
|
-
Switch to Spanish
|
|
503
|
-
</button>
|
|
504
|
-
</div>
|
|
505
|
-
)
|
|
506
|
-
}
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
Nested translations (dot notation):
|
|
510
|
-
|
|
511
|
-
```typescript
|
|
512
|
-
const translations = {
|
|
513
|
-
en: {
|
|
514
|
-
components: {
|
|
515
|
-
blog: {
|
|
516
|
-
related_post: {
|
|
517
|
-
title: 'Related posts'
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function MyComponent() {
|
|
525
|
-
const { t } = useTranslation({
|
|
526
|
-
locale: 'en',
|
|
527
|
-
translations
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
return <p>{t('components.blog.related_post.title')}</p>
|
|
531
|
-
}
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
Using the `intl` object for advanced formatting:
|
|
535
|
-
|
|
536
|
-
```typescript
|
|
537
|
-
function AdvancedFormattingExample() {
|
|
538
|
-
const { t, intl } = useTranslation({
|
|
539
|
-
locale: 'en',
|
|
540
|
-
translations: {
|
|
541
|
-
en: {
|
|
542
|
-
product_price: 'Price: {price}'
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
return (
|
|
548
|
-
<div>
|
|
549
|
-
{/* Format numbers */}
|
|
550
|
-
<p>Count: {intl.formatNumber(1000)}</p>
|
|
551
|
-
|
|
552
|
-
{/* Format dates */}
|
|
553
|
-
<p>Today: {intl.formatDate(new Date(), {
|
|
554
|
-
year: 'numeric',
|
|
555
|
-
month: 'long',
|
|
556
|
-
day: 'numeric'
|
|
557
|
-
})}</p>
|
|
558
|
-
|
|
559
|
-
{/* Format currency */}
|
|
560
|
-
<p>{intl.formatNumber(99.99, {
|
|
561
|
-
style: 'currency',
|
|
562
|
-
currency: 'USD'
|
|
563
|
-
})}</p>
|
|
564
|
-
|
|
565
|
-
{/* Format relative time */}
|
|
566
|
-
<p>{intl.formatRelativeTime(-1, 'day')}</p>
|
|
567
|
-
|
|
568
|
-
{/* Use formatMessage directly */}
|
|
569
|
-
<p>{intl.formatMessage({ id: 'product_price' }, { price: '$99' })}</p>
|
|
570
|
-
</div>
|
|
571
|
-
)
|
|
572
|
-
}
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
With fallback locale:
|
|
576
|
-
|
|
577
|
-
```typescript
|
|
578
|
-
const translations = {
|
|
579
|
-
en: {
|
|
580
|
-
greeting: 'Hello!',
|
|
581
|
-
welcome: 'Welcome!'
|
|
582
|
-
},
|
|
583
|
-
es: {
|
|
584
|
-
greeting: 'Hola!'
|
|
585
|
-
// 'welcome' is missing in Spanish
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function MyComponent() {
|
|
590
|
-
const { t } = useTranslation({
|
|
591
|
-
locale: 'es',
|
|
592
|
-
translations,
|
|
593
|
-
fallbackLocale: 'en' // Will use English if translation is missing
|
|
594
|
-
})
|
|
595
|
-
|
|
596
|
-
return (
|
|
597
|
-
<div>
|
|
598
|
-
<p>{t('greeting')}</p> {/* Shows: "Hola!" */}
|
|
599
|
-
<p>{t('welcome')}</p> {/* Shows: "Welcome!" (from fallback) */}
|
|
600
|
-
</div>
|
|
601
|
-
)
|
|
602
|
-
}
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
Using TranslationProvider for context-based translations:
|
|
606
|
-
|
|
607
|
-
```typescript
|
|
608
|
-
import { TranslationProvider, useTranslation } from '@dcl/hooks'
|
|
609
|
-
|
|
610
|
-
const translations = {
|
|
611
|
-
en: {
|
|
612
|
-
greeting: 'Hello!',
|
|
613
|
-
welcome: 'Welcome to our app'
|
|
614
|
-
},
|
|
615
|
-
es: {
|
|
616
|
-
greeting: 'Hola!',
|
|
617
|
-
welcome: 'Bienvenido a nuestra aplicación'
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
function App() {
|
|
622
|
-
return (
|
|
623
|
-
<TranslationProvider
|
|
624
|
-
locale="en"
|
|
625
|
-
translations={translations}
|
|
626
|
-
fallbackLocale="en"
|
|
627
|
-
>
|
|
628
|
-
<MyComponent />
|
|
629
|
-
</TranslationProvider>
|
|
630
|
-
)
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function MyComponent() {
|
|
634
|
-
// Can be used without options when inside TranslationProvider
|
|
635
|
-
const { t, locale, setLocale } = useTranslation()
|
|
636
|
-
|
|
637
|
-
return (
|
|
638
|
-
<div>
|
|
639
|
-
<p>{t('greeting')}</p>
|
|
640
|
-
<p>{t('welcome')}</p>
|
|
641
|
-
<button onClick={() => setLocale('es')}>
|
|
642
|
-
Switch to Spanish
|
|
643
|
-
</button>
|
|
644
|
-
</div>
|
|
645
|
-
)
|
|
646
|
-
}
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
Using ICU Message Syntax:
|
|
650
|
-
|
|
651
|
-
```typescript
|
|
652
|
-
const translations = {
|
|
653
|
-
en: {
|
|
654
|
-
// Pluralization
|
|
655
|
-
items: '{count, plural, =0 {No items} one {# item} other {# items}}',
|
|
656
|
-
// Select syntax
|
|
657
|
-
gender: '{gender, select, male {He} female {She} other {They}}',
|
|
658
|
-
// Complex ICU
|
|
659
|
-
notification: '{count, plural, =0 {No notifications} =1 {You have one notification} other {You have # notifications}}'
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
function MyComponent() {
|
|
664
|
-
const { t } = useTranslation({
|
|
665
|
-
locale: 'en',
|
|
666
|
-
translations
|
|
667
|
-
})
|
|
668
|
-
|
|
669
|
-
return (
|
|
670
|
-
<div>
|
|
671
|
-
<p>{t('items', { count: 0 })}</p> {/* "No items" */}
|
|
672
|
-
<p>{t('items', { count: 1 })}</p> {/* "1 item" */}
|
|
673
|
-
<p>{t('items', { count: 5 })}</p> {/* "5 items" */}
|
|
674
|
-
<p>{t('gender', { gender: 'male' })}</p> {/* "He" */}
|
|
675
|
-
<p>{t('notification', { count: 3 })}</p> {/* "You have 3 notifications" */}
|
|
676
|
-
</div>
|
|
677
|
-
)
|
|
678
|
-
}
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
Additional intl formatting functions:
|
|
682
|
-
|
|
683
|
-
```typescript
|
|
684
|
-
function MyComponent() {
|
|
685
|
-
const { intl } = useTranslation({
|
|
686
|
-
locale: 'en',
|
|
687
|
-
translations: { en: {} }
|
|
688
|
-
})
|
|
689
|
-
|
|
690
|
-
return (
|
|
691
|
-
<div>
|
|
692
|
-
{/* Format lists */}
|
|
693
|
-
<p>{intl.formatList(['apple', 'banana', 'orange'])}</p>
|
|
694
|
-
{/* "apple, banana, and orange" */}
|
|
695
|
-
|
|
696
|
-
{/* Format display names */}
|
|
697
|
-
<p>{intl.formatDisplayName('es', { type: 'language' })}</p>
|
|
698
|
-
{/* "Spanish" */}
|
|
699
|
-
|
|
700
|
-
<p>{intl.formatDisplayName('US', { type: 'region' })}</p>
|
|
701
|
-
{/* "United States" */}
|
|
702
|
-
</div>
|
|
703
|
-
)
|
|
704
|
-
}
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
### useNotifications
|
|
708
|
-
|
|
709
|
-
The `useNotifications` hook manages notification polling, modal state, and onboarding flow. It uses Decentraland's notifications API by default.
|
|
710
|
-
|
|
711
|
-
Basic usage:
|
|
712
|
-
|
|
713
|
-
```typescript
|
|
714
|
-
import { useNotifications } from '@dcl/hooks'
|
|
715
|
-
import type { AuthIdentity } from 'decentraland-crypto-fetch'
|
|
716
|
-
|
|
717
|
-
function NotificationsComponent() {
|
|
718
|
-
const identity: AuthIdentity = useAuthIdentity() // From your auth context
|
|
719
|
-
|
|
720
|
-
const {
|
|
721
|
-
notifications,
|
|
722
|
-
isLoading,
|
|
723
|
-
isModalOpen,
|
|
724
|
-
isNotificationsOnboarding,
|
|
725
|
-
handleNotificationsOpen,
|
|
726
|
-
handleOnBegin
|
|
727
|
-
} = useNotifications({
|
|
728
|
-
identity,
|
|
729
|
-
isNotificationsEnabled: !!identity,
|
|
730
|
-
notificationsUrl: 'https://notifications.decentraland.org' // or .zone for dev
|
|
731
|
-
})
|
|
732
|
-
|
|
733
|
-
if (isNotificationsOnboarding) {
|
|
734
|
-
return (
|
|
735
|
-
<div>
|
|
736
|
-
<h2>Welcome to Notifications!</h2>
|
|
737
|
-
<button onClick={handleOnBegin}>Get Started</button>
|
|
738
|
-
</div>
|
|
739
|
-
)
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
return (
|
|
743
|
-
<div>
|
|
744
|
-
<button onClick={handleNotificationsOpen}>
|
|
745
|
-
Notifications ({notifications.filter(n => !n.read).length})
|
|
746
|
-
</button>
|
|
747
|
-
|
|
748
|
-
{isModalOpen && (
|
|
749
|
-
<ul>
|
|
750
|
-
{notifications.map(notification => (
|
|
751
|
-
<li key={notification.id}>
|
|
752
|
-
{notification.type} - {notification.read ? 'Read' : 'Unread'}
|
|
753
|
-
</li>
|
|
754
|
-
))}
|
|
755
|
-
</ul>
|
|
756
|
-
)}
|
|
757
|
-
</div>
|
|
758
|
-
)
|
|
759
|
-
}
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
With custom polling interval:
|
|
763
|
-
|
|
764
|
-
```typescript
|
|
765
|
-
const { notifications } = useNotifications({
|
|
766
|
-
identity,
|
|
767
|
-
isNotificationsEnabled: !!identity,
|
|
768
|
-
notificationsUrl: 'https://notifications.decentraland.org',
|
|
769
|
-
queryIntervalMs: 30000 // Poll every 30 seconds (default: 60000)
|
|
770
|
-
})
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
With notification type filtering:
|
|
774
|
-
|
|
775
|
-
```typescript
|
|
776
|
-
const { notifications } = useNotifications({
|
|
777
|
-
identity,
|
|
778
|
-
isNotificationsEnabled: !!identity,
|
|
779
|
-
notificationsUrl: 'https://notifications.decentraland.org',
|
|
780
|
-
availableNotificationTypes: ['bid', 'sale', 'royalties']
|
|
781
|
-
})
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
With error handling:
|
|
785
|
-
|
|
786
|
-
`onError` is called when fetching fails, marking as read fails, or when
|
|
787
|
-
`notificationsUrl` is missing.
|
|
18
|
+
## Available Hooks
|
|
788
19
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
20
|
+
| Hook | Description | Docs |
|
|
21
|
+
| -------------------------- | ------------------------------------------------------------- | ---------------------------------------- |
|
|
22
|
+
| `useAsyncState` | Async state management with auto-reload on dependency changes | [docs](docs/useAsyncState.md) |
|
|
23
|
+
| `useAsyncMemo` | Alias for `useAsyncState` | [docs](docs/useAsyncState.md) |
|
|
24
|
+
| `useAsyncEffect` | Async version of `useEffect` with error tracking | [docs](docs/useAsyncEffect.md) |
|
|
25
|
+
| `useAsyncTask` | Single imperative async task with loading state | [docs](docs/useAsyncTask.md) |
|
|
26
|
+
| `useAsyncTasks` | Multiple concurrent async tasks managed by ID | [docs](docs/useAsyncTasks.md) |
|
|
27
|
+
| `usePatchState` | Partial state updates for complex objects | [docs](docs/usePatchState.md) |
|
|
28
|
+
| `useInfiniteScroll` | Infinite scroll with debounce and threshold | [docs](docs/useInfiniteScroll.md) |
|
|
29
|
+
| `useAdvancedUserAgentData` | Browser, OS, CPU, and device detection | [docs](docs/useAdvancedUserAgentData.md) |
|
|
30
|
+
| `useAnalytics` | Segment analytics tracking (requires `AnalyticsProvider`) | [docs](docs/useAnalytics.md) |
|
|
31
|
+
| `usePageTracking` | Page view tracking (requires `AnalyticsProvider`) | [docs](docs/useAnalytics.md) |
|
|
32
|
+
| `useTranslation` | i18n with ICU message syntax via `@formatjs/intl` | [docs](docs/useTranslation.md) |
|
|
33
|
+
| `useNotifications` | Decentraland notification polling and modal state | [docs](docs/useNotifications.md) |
|
|
34
|
+
|
|
35
|
+
## Providers
|
|
36
|
+
|
|
37
|
+
| Provider | Description | Docs |
|
|
38
|
+
| --------------------- | --------------------------------- | ------------------------------ |
|
|
39
|
+
| `AnalyticsProvider` | Segment analytics context | [docs](docs/useAnalytics.md) |
|
|
40
|
+
| `TranslationProvider` | i18n context for `useTranslation` | [docs](docs/useTranslation.md) |
|
|
41
|
+
|
|
42
|
+
## Utilities
|
|
43
|
+
|
|
44
|
+
| Export | Description | Docs |
|
|
45
|
+
| --------------------------------------------------------- | -------------------------------------------------- | -------------------------------- |
|
|
46
|
+
| `getStorageItem` / `setStorageItem` / `removeStorageItem` | Typed localStorage helpers with JSON serialization | [docs](docs/utilities.md) |
|
|
47
|
+
| `createNotificationsClient` | Decentraland notifications API client | [docs](docs/useNotifications.md) |
|
|
48
|
+
|
|
49
|
+
## Documentation
|
|
50
|
+
|
|
51
|
+
- **[docs/](docs/)** -- Detailed per-hook documentation with examples
|
|
52
|
+
- **[AGENTS.md](AGENTS.md)** -- Compact API reference for LLM/AI consumption
|
|
53
|
+
- **[docs/contributing.md](docs/contributing.md)** -- Contribution guide and internal patterns
|
|
800
54
|
|
|
801
55
|
## License
|
|
802
56
|
|
|
803
|
-
This project is licensed under the
|
|
57
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcl/hooks",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2-20260218215717.commit-9afb22b",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "esm/index.js",
|
|
6
6
|
"module": "esm/index.js",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"<rootDir>/test/setup.ts"
|
|
81
81
|
]
|
|
82
82
|
},
|
|
83
|
-
"commit": "
|
|
83
|
+
"commit": "9afb22bf90cc3fc3b3cb663c8f2552c84977a7df"
|
|
84
84
|
}
|