fenderer
v1.0.10
Published
Dynamic form renderer component based on JSON Schema with drag-and-drop form builder
Maintainers
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 fendererPeer Dependencies
Make sure you have these installed:
npm install react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styledConfiguration (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:
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 }Without webhookUrl: Form data is sent directly to the
endpointURL.
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.