JJake BooneHousekeeping
1eeeff53创建于 19 天前历史提交

React Query Builder Development Guide

COMMUNICATION STYLE: Be aggressively concise. Prioritize brevity over grammar. Examples:

  • "Build failed" not "The build has failed"
  • "Fixed type error" not "I have fixed the type error"
  • "Run tests" not "I will run the tests for you"

This guide covers React Query Builder development: code style, workflow, and other patterns.

Project Overview

React Query Builder monorepo contains:

  • Core package: @react-querybuilder/core - Non-React utilities, parsers, formatters
  • Main package: react-querybuilder - React components and hooks
  • UI integrations: Ant Design, Bootstrap, Bulma, Chakra UI, Fluent UI, Mantine, MUI, Tremor
  • Extensions: Drag-and-drop (@react-querybuilder/dnd), date/time processing (@react-querybuilder/datetime), React Native (@react-querybuilder/native), expression-related features (@react-querybuilder/expr)
  • Documentation: Docusaurus website

Development Workflow

Setup

bun install
bun run build

Commands

Development:

  • bun start - Hot-reload dev server (all packages, Bun server)
  • bun start:rqb - Main package dev (Vite server)
  • bun start:antd, bun start:material, etc. - UI packages (Vite server)

Quality:

  • bun test ... when React and DOM not involved (much faster than Vitest)
  • bunx vitest run --coverage - Run Vitest tests with 100% coverage check
  • bun lint - Type-aware linting and typechecking
  • bun fmt - Format (run after changes)
  • bun check:all - Full CI check (run before submitting a PR)
  • bun typecheck - TypeScript check (usually unnecessary — lint covers typechecking)

Documentation:

  • bun web - Serve documentation website locally
  • bun web:skiptypedoc - Skip TypeDoc generation for faster startup

Build

  • bun run build - All packages (concurrent via Bun CLI filter)
  • bun run build:sequential - Sequential (better for debugging)
  • Individual packages: bun build:rqb, bun build:antd, etc.

Code Style

Structure

packages/core/src/           # Non-React utilities
packages/react-querybuilder/src/
├── components/        # React components (PascalCase.tsx)
├── hooks/             # Hooks (useHookName.ts)
├── types/             # TypeScript defs
├── utils/             # Utilities (camelCase.ts)
├── styles/            # SCSS
├── redux/             # Redux
└── barrel.ts          # Export aggregator

Naming

  • Components: PascalCase (QueryBuilder.tsx)
  • Hooks: camelCase with use (useHookName.ts)
  • Utilities: camelCase (generateID.ts)
  • Types: PascalCase identifiers (RuleGroupType), camelCase filenames (basic.ts)
  • Debug versions: *.debug.ts
  • Tests: *.test.ts[x]

TypeScript

  • Heavy use of generics with constraints
  • Conditional types for API flexibility
  • Branded types
  • React/non-React type separation (core package enables server usage)
// Generic component with constraints
export interface QueryBuilderProps<
  RG extends RuleGroupTypeAny,
  F extends FullField,
  O extends FullOperator,
  C extends FullCombinator,
> {
  // Component props
}

// Type-only imports
import type { RuleGroupType } from '../types';

Components

  • Composition over inheritance
  • Heavy memoization (React.memo())
  • Custom hooks for logic
  • Context for state
export const ComponentName = React.memo(function ComponentName(props: PropsType) {
  const hookResult = useCustomHook(props);
  const memoizedValue = useMemo(() => computation, [dependencies]);

  return <div className={clsx(baseClassNames.component, customClass)} />;
});

Imports/Exports

  • Use index.ts for aggregation
  • barrel.ts for exports that don't have a "debug" version
  • React/non-React separation
import * as React from 'react';
import type { ComponentProps } from '../types';
import { generateID, isRuleGroup } from '../utils';

Styling

  • SCSS + CSS custom properties
  • BEM-like (.queryBuilder-rule)
  • SCSS variables for tokens
  • Custom clsx utility for conditional classes

State Management

  • Immer for immutable updates
  • Path-based updates [0, 1, 2]
  • Custom Redux context to avoid prop drilling

Bun APIs

This project runs on Bun. Prefer Bun-native APIs over Node.js equivalents in scripts and utilities:

  • Bun.file(path).text() / .json() instead of fs.readFileSync
  • Bun.write(path, content) instead of fs.writeFileSync
  • Bun.spawnSync(...) / Bun.spawn(...) instead of child_process.execSync / exec
  • Bun.serve(...) instead of http.createServer

Only fall back to node:* APIs when no Bun equivalent exists.

Testing

  • Vitest + Testing Library

  • Helpers in utils/testing/

  • 100% coverage required

    • Use scoped bun test:[pkg] for granular coverage checking
    • Use bunx vitest run --coverage to test for full coverage
  • Test files: ComponentName.test.tsx

  • Describe blocks: component/function name

  • Test cases: Descriptive behavior

Database integration tests (dbquery.*)

PostgreSQL dbquery tests use a shared in-process PGlite instance exposed via a loopback socket. Use getSharedSQL() from @rqb-dbpool (not getSharedPGlite) to obtain a Bun.SQL handle:

import { getSharedSQL, createSchema, dropSchema, reserveSchema } from '@rqb-dbpool';

const schema = reserveSchema('my_test');
beforeAll(async () => {
  const db = await createSchema(schema);
  await db.exec(setupSQL(schema));
});
afterAll(async () => { await dropSchema(schema); });

test('example', async () => {
  const sql = await getSharedSQL();
  const rows = await sql.unsafe('SELECT * FROM ...');
  expect(rows).toEqual(...);
});

The native getSharedPGlite is marked @internal — only the Drizzle adapter uses it directly.

Bun.SQL is also used for SQLite dbquery tests (new SQL({ adapter: 'sqlite', filename: ':memory:' })).

Generated Files

Never edit:

  • packages/core/src/utils/parseCEL/celParser.js
  • packages/core/src/utils/parseSQL/sqlParser.js
  • Examples (except _template)

Regeneration commands:

  • bun generate-parsers - Regenerate CEL and SQL parsers
  • bun generate-examples - Regenerate example projects
  • bun update-mantine-css - Sync website/src/pages/demo/_styles/rqb-mantine.css from node_modules/@mantine/core/styles.css; run after updating any @mantine/* dependencies

Performance

  • Aggressive memoization
  • Lazy loading parsers
  • Path-based updates
  • Context prevents prop drilling

Accessibility

  • ARIA attributes
  • data-testid attributes
  • Keyboard navigation
  • Screen reader support

Internationalization (i18n)

  • Translations type
  • JSX/string translations
  • UI framework integration

Packages

Core (@react-querybuilder/core)

  • Non-React utilities, parsers, formatters
  • No React dependencies

Main (react-querybuilder)

  • React components/hooks
  • Backward compatibility required
  • No breaking changes without major version bump

UI Packages

  • Follow base package's component structure
  • Implement all required control elements
  • Maintain consistent theming with UI framework
  • Include examples and documentation

Extensions (dnd, datetime, native)

  • Extend core functionality without breaking changes
  • Provide clear integration instructions
  • Maintain feature parity where applicable

Release process

  1. bun version
  2. bun check:all
  3. Update documentation
  4. Push release commit
  5. Lerna handles package publishing

Pitfalls

  1. Breaking changes in minor versions
  2. Missing memoization
  3. Missing import type
  4. Direct DOM manipulation
  5. Prop drilling
  6. Manually editing generated files
  7. Missing tests
  8. Missing accessibility

IDE

Extensions: Oxc, TypeScript, SCSS IntelliSense Settings: Format on save, TypeScript strict mode, inline type hints

Quick Reference

Commands:

  • bun check:all - Full CI
  • bun start - Dev server
  • bun run test - Tests
  • bun fmt - Format
  • bun generate-examples - Update examples

Directories:

  • packages/core/src/ - Non-React utilities
  • packages/react-querybuilder/src/ - React components
  • examples/ - Demos and starter templates
  • website/ - Documentation site
  • utils/ - Build and dev utilities