npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fenderer

v1.0.10

Published

Dynamic form renderer component based on JSON Schema with drag-and-drop form builder

Readme

Fenderer

A powerful React component library for rendering dynamic forms based on JSON Schema with built-in submission handling, webhook proxy support, and a drag-and-drop form builder.

Installation

npm install fenderer

Peer Dependencies

Make sure you have these installed:

npm install react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styled

Configuration (Required)

Before using any form components, you must configure the library with your backend URL:

import { configureOrchaForm } from "fenderer";

// Configure once in your app's entry point (e.g., index.tsx or App.tsx)
configureOrchaForm({
  backendUrl: "https://your-backend-api.com/api",
  defaultHeaders: {
    Authorization: "Bearer your-api-token",
  },
  timeout: 30000, // Optional: request timeout in ms
});

How Webhook Proxy Works

When a form has a webhookUrl configured:

  1. With webhookUrl: Form data is sent to:

    • ${backendUrl}/webhook/proxy/{formName} (if formName is provided)
    • ${backendUrl}/webhook/proxy (if no formName)

    With payload:

    {
      "webhookUrl": "https://webhook.site/your-url",
      "method": "POST",
      "data": { "form": "data" },
      "headers": {},
      "originalEndpoint": "https://api.example.com/fallback",
      "formName": "contact-form" // included if formName is set
    }
  2. Without webhookUrl: Form data is sent directly to the endpoint URL.

Your backend should handle the /webhook/proxy and /webhook/proxy/:formName endpoints to forward requests to the actual webhook.

Quick Start

import { FormRenderer, configureOrchaForm } from "fenderer";

// Configure first
configureOrchaForm({
  backendUrl: "https://your-backend.com/api",
});

## Components

### FormBuilder - Drag & Drop Form Builder

Create forms visually with drag-and-drop interface:

```tsx
import { FormBuilder, configureOrchaForm } from 'fenderer';

// Configure first
configureOrchaForm({
  backendUrl: 'https://your-backend.com/api'
});

function App() {
  return (
    <FormBuilder
      initialFormTitle="Contact Form"
      initialDescription="Please fill out this form"
      initialTargetUrl="https://api.example.com/contact"
      initialWebhookUrl="https://webhook.site/your-unique-url"
      initialFormName="contact-form" // For webhook routing: /webhook/proxy/contact-form
      initialRedirectOnSuccess="https://example.com/thank-you"
      initialRedirectOnFailed="https://example.com/error"
      onSchemaChange={(schema) => {
        console.log('Generated schema:', schema);
      }}
      onFormSubmit={(data, schema) => {
        console.log('Form submitted:', data);
      }}
    />
  );
}
```

#### Edit Existing Forms with initialSchema

The `initialSchema` prop allows you to pre-populate the FormBuilder with an existing form schema for editing. This is perfect for loading saved forms, templates, or when you want to modify an existing form.

```tsx
const existingFormSchema = {
  schema: {
    title: "Customer Feedback Form",
    description: "Help us improve our service",
    type: "object",
    properties: {
      customerName: { type: "string", title: "Customer Name" },
      email: { type: "string", title: "Email Address", format: "email" },
      serviceRating: {
        type: "integer",
        title: "Service Rating",
        minimum: 1,
        maximum: 5,
      },
      feedbackType: {
        type: "string",
        title: "Feedback Type",
        enum: ["Compliment", "Complaint", "Suggestion", "Question"],
      },
      message: { type: "string", title: "Your Message" },
      newsletter: { type: "boolean", title: "Subscribe to Newsletter" },
    },
    required: ["customerName", "email", "serviceRating", "message"],
  },
  uiSchema: {
    serviceRating: { "ui:widget": "range" },
    feedbackType: { "ui:widget": "radio" },
    message: { "ui:widget": "textarea", "ui:options": { rows: 4 } },
    newsletter: { "ui:widget": "checkbox" },
  },
  submission: {
    endpoint: "https://api.company.com/feedback",
    method: "POST",
    webhookUrl: "https://webhook.site/feedback-processor",
    formName: "customer-feedback",
  },
};

<FormBuilder
  initialSchema={existingFormSchema}
  onSchemaChange={(updatedSchema) => {
    console.log('Form updated:', updatedSchema);
    // Save the updated schema to your backend/database
  }}
  onFormSubmit={(data, schema) => {
    console.log('Test submission:', data);
  }}
/>
```

**What happens when using initialSchema:**

1. **Form Configuration Pre-filled**: All form settings (title, description, target URL, webhook URL, form name, HTTP method) are automatically loaded from the schema
2. **Fields Converted**: Schema properties are converted back to editable field objects in the builder
3. **Live Preview Ready**: The form is immediately editable in the live preview with appropriate default values
4. **Full Editability**: You can modify any aspect:
   - ✅ Form title, description, and configuration
   - ✅ Target URL, webhook URL, form name, HTTP method
   - ✅ Add, remove, or edit existing fields
   - ✅ Change field types, labels, validation, and UI widgets
   - ✅ Test the form in live preview

**Field Type Detection**: The FormBuilder automatically detects field types from the schema:
- `string` with `format: "email"` → Email field
- `string` with `enum` → Selection dropdown/radio
- `array` with `items.enum` → Multi-select checkboxes
- `boolean` → Checkbox
- `integer`/`number` → Number input
- `string` with `ui:widget: "textarea"` → Textarea
- `string` (default) → Text input

#### FormBuilder Props

- `initialFormTitle?`: Initial form title
- `initialDescription?`: Initial form description
- `initialTargetUrl?`: Initial target URL for submissions
- `initialWebhookUrl?`: Initial webhook URL (optional)
- `initialHttpMethod?`: Initial HTTP method (default: "POST")
- `initialFormName?`: Initial form name for webhook routing
- `initialRedirectOnSuccess?`: Initial redirect URL for successful submissions
- `initialRedirectOnFailed?`: Initial redirect URL for failed submissions
- `initialSchema?`: Pre-populate form builder with existing FormSchema
- `onSchemaChange?`: Callback when schema changes
- `onFormSubmit?`: Callback when form is submitted from live preview
- `showLivePreview?`: Show live form preview (default: true)
- `showExportButton?`: Show export schema button (default: true)
- `containerStyle?`: Custom styles for main container
- `paletteStyle?`: Custom styles for field palette
- `dropAreaStyle?`: Custom styles for drop area
- `previewStyle?`: Custom styles for live preview`

### FormRenderer

Main form component for rendering JSON Schema forms:

```tsx
import { FormRenderer, configureOrchaForm } from "fenderer";

// Configure first
configureOrchaForm({
  backendUrl: "https://your-backend.com/api",
});

const formSchema = {
  schema: {
    title: "Contact Form",
    type: "object",
    properties: {
      name: { type: "string", title: "Full Name" },
      email: { type: "string", title: "Email", format: "email" },
    },
    required: ["name", "email"],
  },
  uiSchema: {
    email: { "ui:placeholder": "Enter your email" },
  },
  submission: {
    endpoint: "https://api.example.com/contact",
    method: "POST",
    webhookUrl: "https://webhook.site/your-unique-url", // Optional: will be proxied
    redirectOnSuccess: "https://example.com/thank-you", // Optional: redirect after success
    redirectOnFailed: "https://example.com/error", // Optional: redirect after error
  },
};

function App() {
  return (
    <FormRenderer
      formSchema={formSchema}
      onSubmitSuccess={(data) => console.log("Success:", data)}
      onSubmitError={(error) => console.log("Error:", error)}
    />
  );
}
```

## API Reference

### FormRenderer Props

- `formSchema`: FormSchema - The complete form configuration
- `onSubmitSuccess?`: Function called on successful submission
- `onSubmitError?`: Function called on submission error
- `onDataChange?`: Function called when form data changes
- `initialData?`: Initial form data
- `showSuccessAlert?`: Show success SweetAlert (default: true)
- `showErrorAlert?`: Show error SweetAlert (default: true)
- `customSuccessMessage?`: Custom success message
- `customErrorMessage?`: Custom error message
- `disabled?`: Disable the entire form

### FormSchema Structure

```tsx
interface FormSchema {
  schema: JSONSchema7; // JSON Schema for form validation
  uiSchema: Record<string, any>; // UI customization
  submission: {
    endpoint: string; // Target URL for form submission
    method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
    webhookUrl?: string; // Optional webhook URL (takes priority)
    headers?: Record<string, string>;
    transformData?: (data: any) => any;
    formName?: string; // Optional form name for webhook routing (/webhook/proxy/{formName})
    redirectOnSuccess?: string; // Optional URL to redirect to after successful submission
    redirectOnFailed?: string; // Optional URL to redirect to after failed submission
  };
}
```

### SimpleFormRenderer

Load form schema from a URL:

```tsx
import { SimpleFormRenderer } from "orcha-form-renderer";

<SimpleFormRenderer
  schemaUrl="/api/forms/contact"
  onSubmitSuccess={(data) => console.log("Success:", data)}
  loadingComponent={<div>Loading your form...</div>}
  errorComponent={(error) => <div>Error: {error}</div>}
/>;
```

### useFormRenderer Hook

For more control over form behavior:

```tsx
import { useFormRenderer } from "orcha-form-renderer";

function MyForm() {
  const {
    formData,
    submitFormData,
    isSubmitting,
    resetForm,
    lastSubmissionResult,
  } = useFormRenderer(formSchema, {
    onSubmitSuccess: (data) => console.log("Success:", data),
    onSubmitError: (error) => console.log("Error:", error),
    onDataChange: (data) => console.log("Data changed:", data),
  });

  return (
    <div>
      <button onClick={() => submitFormData()} disabled={isSubmitting}>
        {isSubmitting ? "Submitting..." : "Submit Form"}
      </button>
      <button onClick={resetForm}>Reset Form</button>

      {lastSubmissionResult && (
        <div>
          Last submission: {lastSubmissionResult.success ? "Success" : "Failed"}
        </div>
      )}
    </div>
  );
}
```

## Backend Implementation Example

Your backend should implement the webhook proxy endpoints:

```javascript
// Express.js example
app.post("/webhook/proxy", handleWebhookProxy);
app.post("/webhook/proxy/:formName", handleWebhookProxy);

async function handleWebhookProxy(req, res) {
  const { webhookUrl, method, data, headers, originalEndpoint, formName } = req.body;
  const formNameFromPath = req.params.formName;
  const actualFormName = formNameFromPath || formName;

  // You can use actualFormName for logging, routing, or processing logic
  console.log(`Processing form: ${actualFormName || 'unnamed'}`);

  try {
    const response = await fetch(webhookUrl, {
      method: method,
      headers: {
        "Content-Type": "application/json",
        ...headers,
      },
      body: JSON.stringify({
        ...data,
        _meta: {
          formName: actualFormName,
          submittedAt: new Date().toISOString(),
        }
      }),
    });

    if (response.ok) {
      const responseData = await response.text();
      res.json({
        success: true,
        status: response.status,
        data: responseData,
        formName: actualFormName,
      });
    } else {
      // Optionally try originalEndpoint as fallback
      throw new Error(`Webhook failed: ${response.status}`);
    }
  } catch (error) {
    console.error(`Webhook proxy error for form ${actualFormName}:`, error);
    res.status(500).json({
      success: false,
      error: error.message,
      formName: actualFormName,
    });
  }
}
```

```python
# FastAPI example
from fastapi import FastAPI, HTTPException
import httpx
from datetime import datetime

app = FastAPI()

@app.post("/webhook/proxy")
@app.post("/webhook/proxy/{form_name}")
async def webhook_proxy(payload: dict, form_name: str = None):
    webhook_url = payload.get("webhookUrl")
    method = payload.get("method", "POST")
    data = payload.get("data", {})
    headers = payload.get("headers", {})
    original_endpoint = payload.get("originalEndpoint")
    form_name_from_body = payload.get("formName")

    # Use form name from path or body
    actual_form_name = form_name or form_name_from_body

    # Log or process based on form name
    print(f"Processing form: {actual_form_name or 'unnamed'}")

    try:
        # Add metadata to the payload
        enhanced_data = {
            **data,
            "_meta": {
                "formName": actual_form_name,
                "submittedAt": datetime.now().isoformat()
            }
        }

        async with httpx.AsyncClient() as client:
            response = await client.request(
                method=method,
                url=webhook_url,
                json=enhanced_data,
                headers={"Content-Type": "application/json", **headers}
            )

            if response.status_code < 400:
                return {
                    "success": True,
                    "status": response.status_code,
                    "data": response.text,
                    "formName": actual_form_name
                }
            else:
                raise HTTPException(
                    status_code=500,
                    detail=f"Webhook failed: {response.status_code}"
                )
    except Exception as e:
        print(f"Webhook proxy error for form {actual_form_name}: {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))
```

## Advanced Usage

### Form Redirections

Configure automatic redirects after form submission success or failure:

```tsx
const formSchema = {
  schema: {
    title: "Contact Form",
    type: "object",
    properties: {
      name: { type: "string", title: "Full Name" },
      email: { type: "string", title: "Email", format: "email" },
    },
    required: ["name", "email"],
  },
  uiSchema: {
    email: { "ui:placeholder": "Enter your email" },
  },
  submission: {
    endpoint: "https://api.example.com/contact",
    method: "POST",
    webhookUrl: "https://webhook.site/your-unique-url",
    formName: "contact-form",
    redirectOnSuccess: "https://example.com/thank-you",
    redirectOnFailed: "https://example.com/error-page",
  },
};
```

#### Redirect Behavior

**Success Redirects:**
- **With Webhook**: Only redirects if webhook status is `200`
- **Without Webhook**: Redirects normally after successful submission
- **Timing**: Redirects after success alert closes (or immediately if alerts are disabled)

**Error Redirects:**
- **Always Available**: Redirects regardless of webhook configuration
- **Timing**: Redirects after error alert closes (or immediately if alerts are disabled)

**Alert Integration:**
```tsx
<FormRenderer
  formSchema={formSchema}
  showSuccessAlert={true}  // User sees success message, then redirects
  showErrorAlert={false}   // Redirects immediately on error
/>
```

**Use Cases:**
- Thank you pages after form completion
- Error pages with additional help or contact information
- Landing pages with next steps or related content
- Analytics tracking pages

### Custom Submission Logic

```tsx
const formSchema = {
  schema: {
    /* ... */
  },
  uiSchema: {
    /* ... */
  },
  submission: {
    endpoint: "https://api.example.com/submit",
    method: "POST",
    headers: {
      Authorization: "Bearer your-token",
      "X-Custom-Header": "value",
    },
    transformData: (data) => ({
      ...data,
      timestamp: new Date().toISOString(),
      source: "form-renderer",
    }),
  },
};
```

### Webhook Integration

```tsx
const formSchema = {
  schema: {
    /* ... */
  },
  uiSchema: {
    /* ... */
  },
  submission: {
    endpoint: "https://api.example.com/fallback", // Fallback URL
    webhookUrl: "https://webhook.site/unique-url", // Takes priority
    method: "POST",
  },
};
```

### Error Handling

```tsx
<FormRenderer
  formSchema={formSchema}
  showErrorAlert={false} // Disable SweetAlert errors
  onSubmitError={(error, data) => {
    // Custom error handling
    console.error("Submission failed:", error);
    // Show your own error UI
    showCustomErrorMessage(error.message);
    // Maybe save draft data
    saveDraftToLocalStorage(data);
  }}
/>
```

## Integration with Form Builder

This package works seamlessly with the Orcha Form Builder:

1. **Design** your form using the drag-and-drop builder
2. **Export** the FormSchema JSON
3. **Use** FormRenderer to render and handle submissions

```tsx
// Copy FormSchema from drag-and-drop builder
const exportedFormSchema = {
  /* ... exported JSON ... */
};

// Render it immediately
<FormRenderer formSchema={exportedFormSchema} />;
```

## Features

- ✅ **JSON Schema Support** - Full JSON Schema v7 support
- ✅ **Material-UI Integration** - Beautiful forms with MUI components
- ✅ **SweetAlert2 Integration** - User-friendly success/error messages
- ✅ **Webhook Support** - Direct webhook submission capability
- ✅ **TypeScript Support** - Full type safety and IntelliSense
- ✅ **Customizable** - Override styles, messages, and behavior
- ✅ **Hook Support** - Use as component or hook for flexibility
- ✅ **Error Handling** - Comprehensive error handling and reporting
- ✅ **Data Transformation** - Transform data before submission

## License

MIT

## Contributing

Contributions welcome! Please open an issue or submit a pull request.

## Support

For issues and questions, please use the GitHub issues page.