npm package: @logic-joe/logicforms-react
Description: React component wrapper for LogicForms - lightweight form embedding
Author: logic-joe
Latest version: 0.0.35

# @logic-joe/logicforms-react

[![npm version](https://img.shields.io/npm/v/@logic-joe/logicforms-react.svg)](https://www.npmjs.com/package/@logic-joe/logicforms-react)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

React components for embedding LogicForms. Features Zod validation, React Hook Form integration, file uploads, localization, and full customization support.

## Installation

```bash
npm install @logic-joe/logicforms-react
# or
pnpm add @logic-joe/logicforms-react
# or
yarn add @logic-joe/logicforms-react
```

**Peer Dependencies:** React 18+ or 19+

## Quick Start

```tsx
import { Form } from "@logic-joe/logicforms-react";

function App() {
  return (
    <Form
      formId="your-form-id"
      baseUrl="https://logicforms.onrender.com"
      onSubmit={(data) => console.log("Submitted:", data)}
      onError={(error) => console.error("Error:", error)}
    />
  );
}
```

## Form Component Props

```typescript
interface FormProps {
  // Required
  formId: string;

  // Configuration
  baseUrl?: string; // Default: "https://logicforms.onrender.com"
  className?: string; // Additional CSS class
  theme?: "light" | "dark" | "system"; // Default: "light"

  // Localization
  locale?: string; // Explicit locale (e.g., "de", "fr")
  autoDetectLocale?: boolean; // Auto-detect from browser (default: true)
  onLocaleChange?: (locale: string, availableLocales: string[]) => void;

  // Callbacks
  onSubmit?: (data: Record<string, string>) => void;
  onError?: (error: Error) => void;

  // Cookie consent
  cookiesAccepted?: boolean; // When false, blocks third-party scripts and shows overlay
  onRequestCookieConsent?: () => void; // Called when user clicks consent button

  // Preview mode (for editor integration)
  previewMode?: boolean;
  previewData?: PreviewModeData;
  forceValidation?: boolean;
  hidePreviewBanner?: boolean;

  // Custom renderers (see Custom Rendering section)
  renderInput?: RenderInputFn;
  renderTextarea?: RenderTextareaFn;
  renderSelect?: RenderSelectFn;
  // ... and more
}
```

## Custom UI with useMaxForm Hook

For full control over form rendering, use the `useMaxForm` hook:

```tsx
import { useMaxForm } from "@logic-joe/logicforms-react";

function CustomForm() {
  const {
    form, // React Hook Form instance
    fields, // Form field definitions
    layout, // Layout blocks
    settings, // Form settings
    isLoading,
    isSubmitting,
    isSuccess,
    error,
    submitForm,
    resetForm,
    getFieldError,
    getFieldById,
  } = useMaxForm({
    formId: "your-form-id",
    baseUrl: "https://logicforms.onrender.com",
    mode: "onBlur", // Validation mode
  });

  if (isLoading) return <div>Loading...</div>;
  if (isSuccess) return <div>{settings.successMessage}</div>;

  return (
    <form onSubmit={form.handleSubmit(submitForm)}>
      {fields.map((field) => (
        <div key={field.id}>
          <label>{field.label}</label>
          <input {...form.register(field.name)} />
          {getFieldError(field.name) && (
            <span className="error">{getFieldError(field.name)}</span>
          )}
        </div>
      ))}
      <button type="submit" disabled={isSubmitting}>
        {settings.submitButtonText}
      </button>
    </form>
  );
}
```

## File Upload

The `FileUpload` component handles the complete upload flow including virus scanning:

```tsx
import { FileUpload } from "@logic-joe/logicforms-react";

function FileField({ field, formId }) {
  const [fileId, setFileId] = useState("");

  return (
    <FileUpload
      field={field}
      formId={formId}
      baseUrl="https://logicforms.onrender.com"
      value={fileId}
      onChange={setFileId}
    />
  );
}
```

The component automatically:

1. Gets a presigned upload URL
2. Uploads to the quarantine bucket
3. Triggers antivirus scanning
4. Polls for scan completion
5. Shows upload progress and status

## Localization

Forms support multiple languages with automatic browser detection:

```tsx
// Auto-detect browser locale (default behavior)
<Form formId="abc" autoDetectLocale={true} />

// Explicit locale
<Form formId="abc" locale="de" />

// React to locale changes
<Form
  formId="abc"
  onLocaleChange={(locale, availableLocales) => {
    console.log("Current:", locale);
    console.log("Available:", availableLocales);
  }}
/>
```

### useFormLocale Hook

For building custom locale selectors:

```tsx
import { useFormLocale } from "@logic-joe/logicforms-react";

function LocaleSelector() {
  const { locale, setLocale, availableLocales, isLoading } = useFormLocale({
    formId: "your-form-id",
    baseUrl: "https://logicforms.onrender.com",
  });

  return (
    <select value={locale} onChange={(e) => setLocale(e.target.value)}>
      {availableLocales.map((loc) => (
        <option key={loc} value={loc}>
          {loc}
        </option>
      ))}
    </select>
  );
}
```

## CAPTCHA Integration

### Google reCAPTCHA

Configure reCAPTCHA in your form settings (dashboard). The component handles v2 and v3 automatically:

```tsx
// reCAPTCHA is automatically rendered when enabled in form settings
<Form formId="abc" />
```

### Cloudflare Turnstile

Similarly configured via form settings:

```tsx
// Turnstile widget rendered automatically when enabled
<Form formId="abc" />
```

## Theming

### Theme Mode

```tsx
// Light theme (default)
<Form formId="abc" theme="light" />

// Dark theme
<Form formId="abc" theme="dark" />

// Follow system preference
<Form formId="abc" theme="system" />
```

### Custom Theme Colors

Theme colors can be configured in the form settings (dashboard). The component automatically applies them:

```typescript
interface ThemeColors {
  primary?: string;
  primaryForeground?: string;
  background?: string;
  foreground?: string;
  muted?: string;
  mutedForeground?: string;
  border?: string;
  input?: string;
  destructive?: string;
}
```

### Custom CSS

Forms support custom CSS defined in the dashboard. The CSS is automatically scoped to the form:

```css
/* Example custom CSS (defined in dashboard) */
.logicforms-{formId} input {
  border-radius: 8px;
}
```

## Custom Rendering

Override any part of the form with custom render functions:

### Field Renderers

```tsx
<Form
  formId="abc"
  renderInput={(ctx) => (
    <input
      {...ctx.register(ctx.name)}
      placeholder={ctx.field.placeholder}
      className={ctx.error ? "error" : ""}
    />
  )}
  renderTextarea={(ctx) => <textarea {...ctx.register(ctx.name)} rows={6} />}
  renderSelect={(ctx) => (
    <select value={ctx.value} onChange={(e) => ctx.onChange(e.target.value)}>
      {ctx.field.options.map((opt) => (
        <option key={opt.value} value={opt.value}>
          {opt.label}
        </option>
      ))}
    </select>
  )}
/>
```

### Field Render Context

```typescript
interface FieldRenderContext {
  field: FormField; // Field definition
  register: UseFormRegister; // React Hook Form register
  error?: FieldError; // Validation error
  value: string; // Current value
  onChange: (value: string) => void;
  onBlur: () => void;
  name: string;
  disabled?: boolean;
}
```

### Layout Block Renderers

```tsx
<Form
  formId="abc"
  renderHeading={(block) => <h2 style={{ color: "blue" }}>{block.content}</h2>}
  renderTextBlock={(block) => <p className="custom-text">{block.content}</p>}
  renderDivider={() => <hr className="my-divider" />}
  renderRow={(block, children) => <div className="custom-row">{children}</div>}
/>
```

### State Renderers

```tsx
<Form
  formId="abc"
  renderLoading={() => <Spinner />}
  renderSuccess={(message) => <div className="success-banner">{message}</div>}
  renderFormError={(error) => <div className="error-alert">{error}</div>}
  renderFieldError={(error) => (
    <span className="field-error">{error.message}</span>
  )}
  renderSubmit={({ isSubmitting, buttonText, hasErrors }) => (
    <button type="submit" disabled={isSubmitting || hasErrors}>
      {isSubmitting ? "Sending..." : buttonText}
    </button>
  )}
  renderFieldWrapper={(field, children, error) => (
    <div className={`field-group ${error ? "has-error" : ""}`}>{children}</div>
  )}
/>
```

## Cookie Consent

If your form uses third-party services (GA4, reCAPTCHA, Turnstile), you need user consent before loading their scripts (GDPR/ePrivacy). Use `cookiesAccepted` to control this:

```tsx
import { useState } from "react";
import { Form } from "@logic-joe/logicforms-react";

function MyForm() {
  const [cookiesAccepted, setCookiesAccepted] = useState(false);

  return (
    <Form
      formId="your-form-id"
      cookiesAccepted={cookiesAccepted}
      onRequestCookieConsent={() => {
        // Open your cookie banner/dialog
        myCookieBanner.show();
      }}
    />
  );

  // Later, when user accepts: setCookiesAccepted(true)
}
```

When `cookiesAccepted` is `false`, the form is blurred and a localized consent overlay is shown. First-party analytics (self-hosted, no cookies) continue to work. Omitting `cookiesAccepted` preserves backwards compatibility — all scripts load normally.

## Analytics (Google Analytics 4)

When enabled in form settings, the component automatically tracks:

- `form_view` - Form loaded
- `form_start` - First field interaction
- `form_submit` - Successful submission
- `form_error` - Submission error

### useAnalytics Hook

For custom analytics implementations:

```tsx
import { useAnalytics } from "@logic-joe/logicforms-react";

const { trackFormView, trackFormStart, trackFormSubmit, trackFormError } =
  useAnalytics({
    measurementId: "G-XXXXXXXXXX",
    formId: "your-form-id",
    formName: "Contact Form",
  });
```

## Validation Utilities

Build custom validation schemas:

```tsx
import {
  buildFieldSchema,
  buildFormSchema,
  validateFormData,
} from "@logic-joe/logicforms-react";
import type { FormField } from "@logic-joe/logicforms-api";

// Build schema for a single field
const fieldSchema = buildFieldSchema(field);

// Build schema for entire form
const formSchema = buildFormSchema(fields);

// Validate data
const result = validateFormData(formSchema, data);
```

## Block Components

For custom layouts, use the exported block components:

```tsx
import {
  HeadingBlock,
  TextBlock,
  DividerBlock,
  RowBlock,
} from "@logic-joe/logicforms-react";

<HeadingBlock block={{ content: "Title", level: 2 }} />
<TextBlock block={{ content: "Description text" }} />
<DividerBlock />
<RowBlock block={rowBlock} renderChild={renderBlock} />
```

## TypeScript Support

Full TypeScript support with exported types:

```typescript
import type {
  FormProps,
  FormData,
  FormSettingsData,
  ThemeMode,
  FieldRenderContext,
  FileFieldRenderContext,
  RenderInputFn,
  RenderTextareaFn,
  RenderSelectFn,
  RenderCheckboxFn,
  RenderRadioFn,
  RenderFileFn,
  RenderHeadingFn,
  RenderSubmitFn,
  SubmitRenderProps,
  UseMaxFormOptions,
  UseMaxFormReturn,
  UseFormLocaleOptions,
  UseFormLocaleReturn,
  UseAnalyticsOptions,
} from "@logic-joe/logicforms-react";
```

## UMD Build

For non-bundler usage, a UMD build is available:

```html
<script src="https://unpkg.com/@logic-joe/logicforms-react"></script>
<script>
  const { Form, useMaxForm, render } = window.LogicForms;

  // Or use the render helper
  LogicForms.render("#form-container", {
    formId: "your-form-id",
    baseUrl: "https://logicforms.onrender.com",
  });
</script>
```

## License

MIT