Skip to Content

React Hooks

Gluonic provides React hooks for accessing synced data in your components.

Core Hooks

useModel()

Get a single model by ID:

import { observer, useModel } from '@gluonic/client' import { Post } from '../models' const PostDetail = observer(({ postId }: { postId: string }) => { const post = useModel<Post>('post', postId) if (!post) { return <div>Loading...</div> } return ( <div> <h1>{post.title}</h1> <p>{post.content}</p> </div> ) })

Returns: Model instance or undefined if not loaded.

API Reference: useModel - Complete hook documentation

useCollectionModels()

Get all models of a type, optionally filtered:

const PostList = observer(() => { // All posts const allPosts = useCollectionModels<Post>('post') // Filtered posts const published = useCollectionModels<Post>( 'post', post => post.published === true ) return ( <div> {published.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> ) })

Returns: Array of model instances.

Filter function:

(model: T) => boolean // Examples: post => post.published user => user.email.includes('@gmail.com') comment => comment.createdAt > Date.now() - 86400000 // Last 24h

API Reference: useCollectionModels - Filtering and performance

useStore()

Access the store directly:

const PostEditor = observer(() => { const store = useStore() const handleSave = async () => { await store.save('post', postId, { title, content }) } return <button onClick={handleSave}>Save</button> })

API Reference: useStore - Store methods

useConnectionState()

Monitor sync and network state:

const SyncStatus = observer(() => { const { isOnline, // Network available isSyncing, // Currently syncing queueLength, // Pending mutations lastSync // Last sync timestamp } = useConnectionState() return ( <div> {!isOnline && <Badge>Offline</Badge>} {isSyncing && <Spinner />} {queueLength > 0 && <Badge>{queueLength} pending</Badge>} </div> ) })

API Reference: useConnectionState - All connection state properties

useGraphBridge()

Access GraphBridge for advanced operations:

const bridge = useGraphBridge() // Get model const post = bridge.getModel<Post>('post', '123') // Invalidate cache bridge.invalidate('post', '123') // Ensure loaded await bridge.ensureLoaded('post', '123')

State Hooks

useInitializingSession()

Check if initial sync is in progress:

const App = observer(() => { const initializing = useInitializingSession() if (initializing) { return <SplashScreen /> } return <MainApp /> })

Returns: true during bootstrap + queue replay.

useSyncState()

Get detailed sync state:

const { bootstrapping, fastForwarding, replayingQueue, catchingUp } = useSyncState()

Observer Pattern

Required: observer() HOC

Always wrap components that read model data:

// ✅ Correct const Component = observer(() => { const post = useModel<Post>('post', id) return <h1>{post.title}</h1> }) // ❌ Wrong - won't update! const Component = () => { const post = useModel<Post>('post', id) return <h1>{post.title}</h1> }

Why?

observer() makes components reactive to MobX changes:

// Without observer const Component = () => { const post = useModel<Post>('post', '123') return <h1>{post.title}</h1> // Reads post.title once // Never re-renders when title changes 💥 } // With observer const Component = observer(() => { const post = useModel<Post>('post', '123') return <h1>{post.title}</h1> // MobX tracks: "Component depends on post:123.title" // Re-renders when title changes ✓ })

Common Patterns

Loading State

const PostDetail = observer(({ postId }) => { const post = useModel<Post>('post', postId) if (!post) { return <ActivityIndicator /> } return <PostContent post={post} /> })

Empty State

const PostList = observer(() => { const posts = useCollectionModels<Post>('post') if (posts.length === 0) { return <EmptyState /> } return <FlatList data={posts} ... /> })

Filtered Collections

const PublishedPosts = observer(() => { const posts = useCollectionModels<Post>( 'post', p => p.published && !p.archived ) return <PostList posts={posts} /> })

Multiple Filters

const [searchQuery, setSearchQuery] = useState('') const [category, setCategory] = useState('all') const filteredPosts = useCollectionModels<Post>( 'post', post => { const matchesSearch = post.title.toLowerCase().includes(searchQuery.toLowerCase()) const matchesCategory = category === 'all' || post.category === category return matchesSearch && matchesCategory } )

Lazy Relationships

const PostWithAuthor = observer(({ post }) => { const author = post.author.value if (!author) { return <Text>Loading author...</Text> } return <Text>By: {author.name}</Text> })

Nested Data

const PostWithComments = observer(({ post }) => { return ( <div> <h1>{post.title}</h1> {post.comments.map(comment => ( <div key={comment.id}> <p>{comment.text}</p> <small>By: {comment.author.value?.name}</small> </div> ))} </div> ) })

Performance Tips

Memoization

const PostAnalysis = observer(({ post }) => { // Only recompute if post instance changes const analysis = useMemo(() => { return analyzePost(post) // Expensive operation }, [post]) return <div>{analysis.summary}</div> })

Selective Observation

// Observe only specific fields import { observer } from '@gluonic/client' import { useLocalObservable } from 'mobx-react-lite' // Advanced MobX utils const Component = observer(() => { const post = useModel<Post>('post', id) // Only re-render when title changes (not content) return <h1>{post.title}</h1> })

Virtualized Lists

import { FlashList } from '@shopify/flash-list' const PostList = observer(() => { const posts = useCollectionModels<Post>('post') return ( <FlashList data={posts} renderItem={({ item }) => <PostItem post={item} />} estimatedItemSize={100} /> ) })

Troubleshooting

Component doesn’t update

Problem: Data changes but UI doesn’t update.

Solution: Wrap with observer():

const Component = observer(() => { ... })

“Can’t find model” errors

Problem: useModel() returns undefined.

Solutions:

  • Ensure model is in bootstrap
  • Check spelling of type name
  • Verify model is registered: warmUpModelRegistry([User, Post])

Infinite re-renders

Problem: Component renders continuously.

Solutions:

  • Don’t mutate data during render
  • Don’t call store.save() during render
  • Use useEffect for side effects

Next Steps

Last updated on