Back to Lowdefy

@lowdefy/client

code-docs/packages/client.md

5.2.09.3 KB
Original Source

@lowdefy/client

React client for rendering Lowdefy pages. Orchestrates block rendering, context management, and user interactions.

Purpose

This package provides:

  • The main Client React component that renders pages
  • Page context initialization and management
  • Block component mounting and lifecycle
  • API communication (requests, endpoints)
  • Navigation and routing integration

Key Export

javascript
import Client from '@lowdefy/client';

// Used by the server to render pages
<Client
  auth={authSession}
  Components={componentMap}
  config={pageConfig}
  jsMap={customJsFunctions}
  lowdefy={lowdefyContext}
  router={nextRouter}
  stage={buildStage}
  types={typeDefinitions}
  window={windowObject}
/>;

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                           Client                                 │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  │
│  │ProgressBarCtrl  │  │ DisplayMessage  │  │    Context      │  │
│  │ (loading state) │  │ (notifications) │  │  (page state)   │  │
│  └─────────────────┘  └─────────────────┘  └────────┬────────┘  │
│                                                      │          │
│                                              ┌───────▼───────┐  │
│                                              │     Head      │  │
│                                              │ (meta tags)   │  │
│                                              └───────────────┘  │
│                                              ┌───────▼───────┐  │
│                                              │     Block     │  │
│                                              │ (root block)  │  │
│                                              └───────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Key Modules

Core Components

ModulePurpose
Client.jsMain entry component, initializes lowdefy context
Context.jsPage context provider, manages state and requests
Head.jsHTML head management (title, meta)
DisplayMessage.jsToast notifications and messages
ProgressBarController.jsLoading progress indicator
useDarkMode.jsDark mode hook (config → localStorage → OS pref)

Block Rendering (/block/)

ModulePurpose
Block.jsRenders individual blocks with their components
(in engine)Block state, events, and lifecycle

API Communication

ModulePurpose
createCallRequest.jsCreates function to call data requests
createCallAPI.jsCreates function to call custom endpoints
request.jsHTTP request utilities

Keyboard Shortcuts

ModulePurpose
createShortcutManager.jsGlobal keyboard shortcut listener lifecycle (tinykeys)
createShortcutBadge.jsShortcutBadge React component for visual key indicators

ShortcutManager lifecycle: Initialized on page context creation → walks block tree to collect all shortcuts → registers a single global keydown listener via tinykeys → checks block visibility lazily per handler → destroyed on context change/unmount.

ShortcutBadge is registered in initLowdefyContext.js as a component (alongside Icon and Link). Blocks receive it via props and render it next to titles/labels. It detects the platform (Mac vs Windows/Linux) and renders modifier symbols accordingly (⌘/⇧/⌥ on Mac, Ctrl/Shift/Alt elsewhere).

See keyboard-shortcuts.md for the full data flow.

Context Initialization

ModulePurpose
initLowdefyContext.jsSets up the global lowdefy context
createHandleError.jsCreates error handler with dedup, server round-trip, display
setupLink.jsConfigures navigation links
createLinkComponent.jsCreates the Link component for navigation
createIcon.jsCreates icon rendering function

Authentication (/auth/)

Handles auth state and session management on the client.

The Lowdefy Context

The lowdefy object is the central context passed through the app:

javascript
lowdefy = {
  // User session
  user: { id, email, roles, ... },

  // Navigation
  router: nextRouter,
  Link: LinkComponent,

  // Configuration
  home: { pageId, configured },
  menus: [...],
  urlQuery: { ... },

  // Internal
  _internal: {
    blockComponents: { ... },    // Loaded block components
    displayMessage: fn,          // Show toast messages
    callRequest: fn,             // Call data requests
    callEndpoint: fn,            // Call API endpoints
    handleError: fn,             // Error handler with dedup, server round-trip, display
    logger: browserLogger,       // Shared browser logger (createBrowserLogger)
    ...
  }
}

Page Context

Each page has its own context (from @lowdefy/engine):

javascript
context = {
  // State containers
  state: { ... },              // Form/input values
  requests: { ... },           // Request responses

  // Internals
  _internal: {
    RootAreas: { ... },        // Block tree
    onInitDone: boolean,       // Initialization complete
    ...
  }
}

Client Rendering Flow

1. Client receives props from server
         │
         ▼
2. initLowdefyContext()
   - Set up auth, router, components
   - Initialize display message handler
         │
         ▼
3. Render ProgressBarController
   - Shows loading state during transitions
         │
         ▼
4. Render DisplayMessage
   - Toast notification container
         │
         ▼
5. Render Context (page context provider)
   - Creates page context via @lowdefy/engine
   - Handles onInit events
   - Manages state
         │
         ▼
6. Wait for onInitDone
         │
         ▼
7. Render Head (meta tags)
         │
         ▼
8. Render root Block
   - Recursively renders block tree
   - Each block gets its component and props

Design Decisions

Why Separate Client and Engine?

Client handles React-specific concerns:

  • Component rendering
  • DOM interactions
  • Router integration
  • HTTP requests

Engine handles framework-agnostic logic:

  • State management
  • Operator evaluation
  • Action execution
  • Event handling

This separation allows potential non-React implementations.

Why Context Per Page?

Each page gets isolated context because:

  • State doesn't leak between pages
  • Clean slate on navigation
  • Memory efficient (old context garbage collected)
  • Predictable behavior

Why Global Lowdefy Object?

The lowdefy object provides:

  • Shared configuration across all blocks
  • Single source for auth state
  • Centralized navigation
  • Common utilities

Blocks receive it as a prop, not via React context, for performance.

Integration Points

  • @lowdefy/engine: Provides state management and actions
  • @lowdefy/layout: Provides layout components. Container.js, InputContainer.js, etc. pass classNames.block/styles.block to BlockLayout, and layout components accept className/style props instead of the deprecated makeCssClass/blockStyle/areaStyle pattern.
  • @lowdefy/block-utils: Block helper utilities
  • Block plugins: Actual UI components
  • Next.js: Router and head management

Event Flow Example

User clicks button
       │
       ▼
Block fires onClick event
       │
       ▼
@lowdefy/engine executes actions
       │
       ├──► SetState action
       │    Updates context.state
       │    Triggers re-render
       │
       ├──► Request action
       │    Calls createCallRequest()
       │    HTTP to /api/request
       │    Response stored in state
       │
       └──► Navigate action
            Calls lowdefy.router.push()
            Client unmounts, new page loads