
npm install @dcl/hooksA collection of React hooks commonly used in Decentraland projects.
``bash`
npm install @dcl/hooks
- useAdvancedUserAgentData: Enhanced user agent informationuseAsyncState
- : Async state management with dependenciesuseAsyncTask
- : Single async task managementuseAsyncTasks
- : Multiple async tasks managementusePatchState
- : Partial state updates for complex objectsuseAsyncEffect
- : Async version of useEffectuseAsyncMemo
- : Async version of useMemouseInfiniteScroll
- : Infinite scroll functionality for loading more contentuseTranslation
- : Simple and lightweight translation managementuseNotifications
- : Notification polling and modal state management
`typescript
import { useAsyncState } from '@dcl/hooks'
function Example() {
const [data, { loading, error }] = useAsyncState(
async () => {
const response = await fetch('https://api.example.com/data')
return response.json()
},
[] // dependencies
)
if (loading) return
$3
`typescript
import { useAdvancedUserAgentData } from '@dcl/hooks'function BrowserInfo() {
const [isLoading, data] = useAdvancedUserAgentData()
if (isLoading) return
Loading browser info... return (
Browser Information
- Browser: {data?.browser.name} {data?.browser.version}
- OS: {data?.os.name} {data?.os.version}
- CPU Architecture: {data?.cpu.architecture}
- Mobile Device: {data?.mobile ? 'Yes' : 'No'}
)
}
`$3
`typescript
import { useInfiniteScroll } from '@dcl/hooks'
import { useState, useEffect } from 'react'function InfiniteList() {
const [items, setItems] = useState([])
const [hasMore, setHasMore] = useState(true)
const [isLoading, setIsLoading] = useState(false)
const loadMore = async () => {
if (isLoading) return
setIsLoading(true)
try {
// Simulate API call
const newItems = await fetchMoreItems(items.length)
setItems((prev) => [...prev, ...newItems])
// Check if there's more data
setHasMore(newItems.length > 0)
} catch (error) {
console.error('Failed to load more items:', error)
} finally {
setIsLoading(false)
}
}
useInfiniteScroll({
onLoadMore: loadMore,
hasMore,
isLoading,
threshold: 500, // Trigger when 500px from bottom
debounceMs: 500, // Minimum time between triggers (default: 500ms)
})
return (
{items.map((item, index) => (
{item}
))}
{isLoading && Loading more...}
{!hasMore && No more items}
)
}
`$3
The package provides a set of hooks for analytics tracking using Segment. Here's how to use them:
#### Setting up the AnalyticsProvider
First, wrap your app with the AnalyticsProvider:
`typescript
import { AnalyticsProvider } from '@dcl/hooks'function App() {
return (
writeKey="xyz1234"
userId="user-123" // Optional
traits={{ // Optional
name: 'John Doe',
email: 'john@example.com'
}}
>
)
}
`#### Using the useAnalytics Hook
The
useAnalytics hook provides access to tracking functions:`typescript
import { useAnalytics } from '@dcl/hooks'function MyComponent() {
const analytics = useAnalytics()
const handleButtonClick = () => {
if (analytics.isInitialized) {
// Track an event
analytics.track('Button Clicked', {
buttonId: 'submit',
timestamp: new Date().toISOString()
})
// Identify a user
analytics.identify('user-123', {
name: 'John Doe',
email: 'john@example.com'
})
}
}
return (
)
}
`#### Using the usePageTracking Hook
The
usePageTracking hook tracks page views when the pathname changes. You need to pass the current pathname as a parameter from your router:`typescript
import { usePageTracking } from '@dcl/hooks'
import { useLocation } from 'react-router-dom'function MyPage() {
const location = useLocation()
// Tracks page view when pathname changes
usePageTracking(location.pathname)
return (
My Page
{/ Your page content /}
)
}
`If you need to track additional page properties, you can use the
useAnalytics hook directly:`typescript
import { useAnalytics } from '@dcl/hooks'function MyPage() {
const analytics = useAnalytics()
useEffect(() => {
if (analytics.isInitialized) {
analytics.page('My Page', {
category: 'Content',
section: 'Main',
timestamp: new Date().toISOString()
})
}
}, [analytics])
return (
My Page
{/ Your page content /}
)
}
`#### Complete Example
Here's a complete example showing how to use all analytics features together:
`typescript
import { AnalyticsProvider, useAnalytics, usePageTracking } from '@dcl/hooks'
import { useLocation } from 'react-router-dom'function MyPage() {
const location = useLocation()
// Track page views
usePageTracking(location.pathname)
return (
My Page
)
}function UserProfile() {
const analytics = useAnalytics()
const handleProfileUpdate = () => {
if (analytics.isInitialized) {
// Track profile update event
analytics.track('Profile Updated', {
timestamp: new Date().toISOString(),
updateType: 'information'
})
// Update user traits
analytics.identify('user-123', {
lastUpdated: new Date().toISOString()
})
}
}
return (
)
}
function App() {
return (
writeKey="xyz1234"
userId="user-123"
traits={{
name: 'John Doe',
email: 'john@example.com'
}}
>
)
}
`$3
The package provides a set of hooks for analytics tracking using Segment. Here's how to use them:
#### Setting up the AnalyticsProvider
First, wrap your app with the AnalyticsProvider:
`typescript
import { AnalyticsProvider } from '@dcl/hooks'function App() {
return (
writeKey="xyz1234"
userId="user-123" // Optional
traits={{ // Optional
name: 'John Doe',
email: 'john@example.com'
}}
>
)
}
`#### Using the useAnalytics Hook
The
useAnalytics hook provides access to tracking functions:`typescript
import { useAnalytics } from '@dcl/hooks'function MyComponent() {
const analytics = useAnalytics()
const handleButtonClick = () => {
if (analytics.isInitialized) {
// Track an event
analytics.track('Button Clicked', {
buttonId: 'submit',
timestamp: new Date().toISOString()
})
// Identify a user
analytics.identify('user-123', {
name: 'John Doe',
email: 'john@example.com'
})
}
}
return (
)
}
`#### Using the usePageTracking Hook
The
usePageTracking hook tracks page views when the pathname changes. You need to pass the current pathname as a parameter from your router:`typescript
import { usePageTracking } from '@dcl/hooks'
import { useLocation } from 'react-router-dom'function MyPage() {
const location = useLocation()
// Tracks page view when pathname changes
usePageTracking(location.pathname)
return (
My Page
{/ Your page content /}
)
}
`If you need to track additional page properties, you can use the
useAnalytics hook directly:`typescript
import { useAnalytics } from '@dcl/hooks'function MyPage() {
const analytics = useAnalytics()
useEffect(() => {
if (analytics.isInitialized) {
analytics.page('My Page', {
category: 'Content',
section: 'Main',
timestamp: new Date().toISOString()
})
}
}, [analytics])
return (
My Page
{/ Your page content /}
)
}
`#### Complete Example
Here's a complete example showing how to use all analytics features together:
`typescript
import { AnalyticsProvider, useAnalytics, usePageTracking } from '@dcl/hooks'
import { useLocation } from 'react-router-dom'function MyPage() {
const location = useLocation()
// Track page views
usePageTracking(location.pathname)
return (
My Page
)
}function UserProfile() {
const analytics = useAnalytics()
const handleProfileUpdate = () => {
if (analytics.isInitialized) {
// Track profile update event
analytics.track('Profile Updated', {
timestamp: new Date().toISOString(),
updateType: 'information'
})
// Update user traits
analytics.identify('user-123', {
lastUpdated: new Date().toISOString()
})
}
}
return (
)
}
function App() {
return (
writeKey="xyz1234"
userId="user-123"
traits={{
name: 'John Doe',
email: 'john@example.com'
}}
>
)
}
`$3
The
useTranslation hook provides i18n capabilities powered by @formatjs/intl, giving you access to advanced formatting functions for numbers, dates, currencies, and more.Basic usage:
`typescript
import { useTranslation } from '@dcl/hooks'const translations = {
en: {
greeting: 'Hello, {name}!',
welcome: 'Welcome to our app',
items: '{count, plural, =0 {No items} one {# item} other {# items}}'
},
es: {
greeting: 'Hola, {name}!',
welcome: 'Bienvenido a nuestra aplicación',
items: '{count, plural, =0 {Sin elementos} one {# elemento} other {# elementos}}'
}
}
function MyComponent() {
const { t, intl, locale, setLocale } = useTranslation({
locale: 'en',
translations
})
return (
{t('greeting', { name: 'John' })}
{t('items', { count: 5 })}
)
}
`Nested translations (dot notation):
`typescript
const translations = {
en: {
components: {
blog: {
related_post: {
title: 'Related posts'
}
}
}
}
}function MyComponent() {
const { t } = useTranslation({
locale: 'en',
translations
})
return
{t('components.blog.related_post.title')}
}
`Using the
intl object for advanced formatting:`typescript
function AdvancedFormattingExample() {
const { t, intl } = useTranslation({
locale: 'en',
translations: {
en: {
product_price: 'Price: {price}'
}
}
}) return (
{/ Format numbers /}
Count: {intl.formatNumber(1000)}
{/ Format dates /}
Today: {intl.formatDate(new Date(), {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
{/ Format currency /}
{intl.formatNumber(99.99, {
style: 'currency',
currency: 'USD'
})}
{/ Format relative time /}
{intl.formatRelativeTime(-1, 'day')}
{/ Use formatMessage directly /}
{intl.formatMessage({ id: 'product_price' }, { price: '$99' })}
)
}
`With fallback locale:
`typescript
const translations = {
en: {
greeting: 'Hello!',
welcome: 'Welcome!'
},
es: {
greeting: 'Hola!'
// 'welcome' is missing in Spanish
}
}function MyComponent() {
const { t } = useTranslation({
locale: 'es',
translations,
fallbackLocale: 'en' // Will use English if translation is missing
})
return (
{t('greeting')}
{/ Shows: "Hola!" /}
{t('welcome')}
{/ Shows: "Welcome!" (from fallback) /}
)
}
`Using TranslationProvider for context-based translations:
`typescript
import { TranslationProvider, useTranslation } from '@dcl/hooks'const translations = {
en: {
greeting: 'Hello!',
welcome: 'Welcome to our app'
},
es: {
greeting: 'Hola!',
welcome: 'Bienvenido a nuestra aplicación'
}
}
function App() {
return (
locale="en"
translations={translations}
fallbackLocale="en"
>
)
}
function MyComponent() {
// Can be used without options when inside TranslationProvider
const { t, locale, setLocale } = useTranslation()
return (
{t('greeting')}
{t('welcome')}
)
}
`Using ICU Message Syntax:
`typescript
const translations = {
en: {
// Pluralization
items: '{count, plural, =0 {No items} one {# item} other {# items}}',
// Select syntax
gender: '{gender, select, male {He} female {She} other {They}}',
// Complex ICU
notification: '{count, plural, =0 {No notifications} =1 {You have one notification} other {You have # notifications}}'
}
}function MyComponent() {
const { t } = useTranslation({
locale: 'en',
translations
})
return (
{t('items', { count: 0 })}
{/ "No items" /}
{t('items', { count: 1 })}
{/ "1 item" /}
{t('items', { count: 5 })}
{/ "5 items" /}
{t('gender', { gender: 'male' })}
{/ "He" /}
{t('notification', { count: 3 })}
{/ "You have 3 notifications" /}
)
}
`Additional intl formatting functions:
`typescript
function MyComponent() {
const { intl } = useTranslation({
locale: 'en',
translations: { en: {} }
}) return (
{/ Format lists /}
{intl.formatList(['apple', 'banana', 'orange'])}
{/ "apple, banana, and orange" /} {/ Format display names /}
{intl.formatDisplayName('es', { type: 'language' })}
{/ "Spanish" /}
{intl.formatDisplayName('US', { type: 'region' })}
{/ "United States" /}
)
}
`$3
The
useNotifications hook manages notification polling, modal state, and onboarding flow. It uses Decentraland's notifications API by default.Basic usage:
`typescript
import { useNotifications } from '@dcl/hooks'
import type { AuthIdentity } from 'decentraland-crypto-fetch'function NotificationsComponent() {
const identity: AuthIdentity = useAuthIdentity() // From your auth context
const {
notifications,
isLoading,
isModalOpen,
isNotificationsOnboarding,
handleNotificationsOpen,
handleOnBegin
} = useNotifications({
identity,
isNotificationsEnabled: !!identity,
notificationsUrl: 'https://notifications.decentraland.org' // or .zone for dev
})
if (isNotificationsOnboarding) {
return (
Welcome to Notifications!
)
} return (
{isModalOpen && (
{notifications.map(notification => (
{notification.type} - {notification.read ? 'Read' : 'Unread'}
))}
)}
)
}
`With custom polling interval:
`typescript
const { notifications } = useNotifications({
identity,
isNotificationsEnabled: !!identity,
notificationsUrl: 'https://notifications.decentraland.org',
queryIntervalMs: 30000 // Poll every 30 seconds (default: 60000)
})
`With notification type filtering:
`typescript
const { notifications } = useNotifications({
identity,
isNotificationsEnabled: !!identity,
notificationsUrl: 'https://notifications.decentraland.org',
availableNotificationTypes: ['bid', 'sale', 'royalties']
})
`With error handling:
onError is called when fetching fails, marking as read fails, or when
notificationsUrl is missing.`typescript
const { notifications } = useNotifications({
identity,
isNotificationsEnabled: !!identity,
notificationsUrl: 'https://notifications.decentraland.org',
onError: (error) => {
console.error('Notification error:', error)
Sentry.captureException(error)
}
})
``This project is licensed under the MIT License - see the LICENSE file for details.