@jguerena15/form-builder
v1.0.12
Published
Headless form builder which exposes client side and admin side hooks for multi-step form validation, and multi-step form creation.
Readme
Form Builder
A headless, type-safe React form builder package that provides comprehensive form functionality while allowing complete UI flexibility. Build complex forms with conditional logic, validation, and external data sources - all without being locked into a specific UI framework.
Features
- 🎯 Headless Architecture - Pure business logic with no UI components
- 🔧 Builder & Renderer Hooks - Separate hooks for form creation and form filling
- 📝 Rich Question Types - Text, number, select, multi-select, date/time, slider, Likert scale, and more
- 🔀 Conditional Logic - Show/hide questions based on previous answers or external conditions
- ✅ Built-in Validation - Zod-powered runtime validation with custom rules
- 🔌 External Data Sources - Load dropdown options from APIs or databases
- 💾 Persistence Support - Save and resume form progress
- 📊 Progress Tracking - Track completion percentage and navigation state
- 🚀 TypeScript First - Full type safety and IntelliSense support
- ⚡ Performance Optimized - Memoization and efficient re-renders
Installation
npm install @jguerena15/form-builder
# or
yarn add @jguerena15/form-builder
# or
pnpm add @jguerena15/form-builderQuick Start
Building Forms (Admin/Builder Interface)
import { useFormBuilder } from '@jguerena15/form-builder';
function FormBuilderInterface() {
const {
form,
addQuestion,
updateQuestion,
deleteQuestion,
addConditional,
save
} = useFormBuilder();
// Add a text input question
const handleAddTextQuestion = () => {
addQuestion({
id: 'name',
type: 'text-input',
label: 'What is your name?',
required: true,
order: 0
});
};
// Add conditional logic
const handleAddConditional = () => {
addConditional({
questionId: 'followup',
showIf: {
logic: 'AND',
conditions: [{
sourceType: 'question',
sourceId: 'name',
operator: 'not_equals',
value: ''
}]
}
});
};
// Your UI implementation
return (
<div>
{/* Render your form builder UI */}
</div>
);
}Rendering Forms (Client/User Interface)
import { useFormRenderer } from '@jguerena15/form-builder';
function FormRenderer({ formDefinition }) {
const {
form,
currentQuestion,
handleNext,
handlePrevious,
handleSubmit,
canProceed,
progress,
isLastStep
} = useFormRenderer({
formDefinition,
onComplete: async (response) => {
console.log('Form completed!', response);
// Submit to your backend
}
});
// Your UI implementation
return (
<div>
<div>Progress: {progress}%</div>
{currentQuestion && (
<div>
<label>{currentQuestion.label}</label>
{/* Render input based on currentQuestion.type */}
<input {...form.register(currentQuestion.id)} />
</div>
)}
<button onClick={handlePrevious} disabled={!canGoBack}>
Previous
</button>
<button
onClick={isLastStep ? handleSubmit : handleNext}
disabled={!canProceed}
>
{isLastStep ? 'Submit' : 'Next'}
</button>
</div>
);
}Advanced Features
External Data Sources
Load dropdown options from APIs or other external sources:
const countryDataSource = {
id: 'countries',
name: 'Country List',
fetchOptions: async () => {
const response = await fetch('/api/countries');
const countries = await response.json();
return countries.map(country => ({
id: country.code,
label: country.name,
value: country.code
}));
}
};
const { form } = useFormBuilder({
externalDataSources: [countryDataSource]
});
// Add a question that uses the data source
addQuestion({
id: 'country',
type: 'single-select',
label: 'Select your country',
dataSourceId: 'countries',
required: true
});External Conditions
Control question visibility based on external state:
const isAuthenticatedCondition = {
id: 'is-authenticated',
name: 'User Authentication Status',
getValue: () => userStore.isAuthenticated
};
const { form } = useFormRenderer({
formDefinition,
externalConditions: [isAuthenticatedCondition]
});Persistence
Save and resume form progress:
const { form } = useFormRenderer({
formDefinition,
persistence: {
save: async (response) => {
localStorage.setItem('form-progress', JSON.stringify(response));
},
load: async (formId) => {
const saved = localStorage.getItem('form-progress');
return saved ? JSON.parse(saved) : null;
}
},
autoSave: true // Auto-save after each question
});Complex Conditional Logic
Create sophisticated show/hide rules:
addConditional({
questionId: 'premium-features',
showIf: {
logic: 'OR',
conditions: [
{
sourceType: 'question',
sourceId: 'plan',
operator: 'equals',
value: 'premium'
},
{
sourceType: 'external',
sourceId: 'is-beta-user',
operator: 'equals',
value: true
}
]
}
});Question Types
Text Input
{
type: 'text-input',
minLength?: number,
maxLength?: number,
pattern?: string,
placeholder?: string
}Number Input
{
type: 'number-input',
min?: number,
max?: number,
step?: number
}Single Select
{
type: 'single-select',
options?: AnswerOption[],
dataSourceId?: string
}Multi Select
{
type: 'multi-select',
options?: AnswerOption[],
dataSourceId?: string,
minSelections?: number,
maxSelections?: number
}Date/Time Picker
{
type: 'date-time-picker',
minDate?: string,
maxDate?: string,
includeTime?: boolean
}Slider
{
type: 'slider',
min: number,
max: number,
step?: number,
showValue?: boolean
}Likert Scale
{
type: 'likert-scale',
minLabel?: string,
maxLabel?: string
}Text Area
{
type: 'text-area',
minLength?: number,
maxLength?: number,
placeholder?: string
}API Reference
useFormBuilder Hook
const {
form, // Current form definition
validationErrors, // Array of validation errors
updateMetadata, // Update form title, description, etc.
addQuestion, // Add a new question
updateQuestion, // Update existing question
deleteQuestion, // Delete a question
reorderQuestion, // Change question order
addConditional, // Add conditional logic
removeConditional, // Remove conditional logic
registerExternalCondition, // Register external condition
registerDataSource, // Register data source
fetchDataSourceOptions, // Fetch options from data source
save, // Save form definition
load, // Load form definition
reset // Reset to initial state
} = useFormBuilder(initialForm?, options?);useFormRenderer Hook
const {
form, // React Hook Form instance
currentStep, // Current step index
totalSteps, // Total visible steps
currentQuestion, // Current question object
canProceed, // Can navigate forward
canGoBack, // Can navigate backward
handleNext, // Go to next question
handlePrevious, // Go to previous question
handleSubmit, // Submit form
progress, // Completion percentage
isFirstStep, // On first question
isLastStep, // On last question
isLoading, // Loading external data
isSaving, // Saving form data
errors, // Error messages
isValid, // Current input is valid
formResponse, // Current response data
resetForm // Reset form
} = useFormRenderer(options);Examples
Check out the /demo directory for a complete example implementation with a working form builder and renderer.
Development
# Install dependencies
yarn install
# Run development demo
yarn dev
# Run tests
yarn test
# Build package
yarn build
# Type check
yarn type-check