Common Patterns

Idiomatic ways to combine Cloudillo APIs when building apps. Each pattern shows the minimal Cloudillo-specific code — wrap in your own components as needed. For full API details, see the linked reference pages.

Data loading

Paginated lists (infinite scroll)

Cloudillo list APIs use cursor-based pagination. The useInfiniteScroll hook from @cloudillo/react manages cursor tracking, accumulates items across pages, and triggers loading via IntersectionObserver when a sentinel element becomes visible.

import { useInfiniteScroll } from '@cloudillo/react'

const { items, isLoading, hasMore, sentinelRef, prepend } = useInfiniteScroll({
  fetchPage: async (cursor, limit) => {
    const result = await api.actions.listPaginated({ type: 'POST', cursor, limit })
    return {
      items: result.data,
      nextCursor: result.cursorPagination?.nextCursor ?? null,
      hasMore: result.cursorPagination?.hasMore ?? false
    }
  },
  pageSize: 20,
  deps: [filterType]  // resets when dependencies change
})

Attach sentinelRef to a <div> at the bottom of your list — it automatically triggers loadMore when scrolled into view. Use prepend() to insert real-time updates at the top without resetting the list.

See: @cloudillo/react | Actions API

The profiles API supports text search. Combine with your own debounce logic to reduce API calls:

const results = await api.profiles.list({ q: searchQuery, limit: 10 })

See: Profiles API

Real-time collaboration

Collaborative editing (CRDT)

The useCloudilloEditor hook sets up a Yjs document with WebSocket sync. It returns a Y.Doc and a WebsocketProvider — bind these to any Yjs-compatible editor (Quill, ProseMirror, CodeMirror, BlockNote). Wait for synced before initializing the editor binding.

import { useCloudilloEditor } from '@cloudillo/react'

const { yDoc, provider, synced, error, ownerTag, fileId } = useCloudilloEditor('my-app')

// Once synced, bind to your editor:
const yText = yDoc.getText('content')
const binding = new QuillBinding(yText, quill, provider.awareness)

The error field captures connection errors (e.g., 440x codes from the CRDT server) so you can show appropriate UI.

See: CRDT (Collaborative Editing) | @cloudillo/react

Presence awareness

The provider from useCloudilloEditor includes a Yjs awareness instance. Subscribe to it to show who is currently viewing or editing:

provider.awareness.on('change', () => {
  const states = provider.awareness.getStates()
  // Each state contains { user: { name, profilePic, ... } }
  // Filter out own clientID: provider.awareness.clientID
})

See: CRDT (Collaborative Editing)

Social features

Connection requests

Cloudillo connections are bidirectional. Send a request by creating a CONN action, and accept incoming requests with accept:

// Send a connection request
await api.actions.create({
  type: 'CONN',
  subject: profile.idTag,
  content: 'Would love to connect!'
})

// Accept an incoming request
await api.actions.accept(actionId)

Check profile.connected for the current connection state:

  • true — connected
  • 'R' — request pending
  • absent — not connected

See: Actions API

Role-based access

Community roles follow a numeric hierarchy defined in ROLE_LEVELS. Use useAuth() to get the current user’s roles and compare:

import { ROLE_LEVELS, type CommunityRole } from '@cloudillo/types'

function hasRole(userRoles: string[] | undefined, required: CommunityRole): boolean {
  if (!userRoles?.length) return false
  const level = Math.max(...userRoles.map(r => ROLE_LEVELS[r as CommunityRole] ?? 0))
  return level >= ROLE_LEVELS[required]
}

// Usage: hasRole(auth?.roles, 'moderator')

Role hierarchy: public < follower < supporter < contributor < moderator < leader.

See: @cloudillo/react | @cloudillo/types

File management

Uploading files

Use api.files.uploadBlob with a preset name. The preset determines what variants (thumbnail, standard definition) are automatically generated server-side:

const result = await api.files.uploadBlob(
  'gallery',    // preset: generates thumbnail + SD variants
  file.name,
  file,
  file.type
)
// result: { fileId, variantId }

See: Files API

General tips

  • Use useToast() from @cloudillo/react for user feedback after API calls — see components reference
  • Wrap route-level components in an error boundary for graceful failure handling — see error handling
  • For reactions and toggles, use optimistic UI updates: save previous state, update immediately, rollback in catch
  • All list APIs support cursor pagination — prefer useInfiniteScroll over manual fetch loops
  • Check api is not null (user is authenticated) before making API calls
  • CRDT and RTDB serve different use cases: CRDT for rich document editing, RTDB for structured data collections — see RTDB and CRDT

See also