expo-dynamic-form
A highly customizable dynamic form component for React Native and Expo applications. This package provides a flexible and powerful way to create dynamic forms with various input types, validation, and multi-step support.
Features
Installation
npm install expo-dynamic-form
yarn add expo-dynamic-form
Required Peer Dependencies
npm install react-hook-form @hookform/resolvers zod date-fns
npm install @react-native-community/datetimepicker @react-native-async-storage/async-storage
npm install expo-document-picker expo-file-system expo-image-picker expo-location
npm install axios
Basic Usage
Simple Form with API Submission
import React from "react";
import { View, StyleSheet } from "react-native";
import DynamicForm, { initConfig } from "expo-dynamic-form";
import { z } from "zod";
export default function App() {
React.useEffect(() => {
initConfig(
{
api: {
baseURL: "https://your-api-base-url.com",
headers: {
"Content-Type": "application/json",
},
timeout: 30000,
},
},
async () => {
return { accessToken: "your-access-token" };
}
);
}, []);
const formSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
age: z.number().min(18, "You must be at least 18 years old"),
});
const controllers = [
{
name: "name",
label: "Full Name",
type: "text",
placeholder: "Enter your full name",
},
{
name: "email",
label: "Email Address",
type: "email",
placeholder: "Enter your email",
},
{
name: "age",
label: "Your Age",
type: "number",
placeholder: "Enter your age",
},
];
const apiOptions = {
api: "/users/create",
method: "POST",
options: {
headers: {
"X-Custom-Header": "value",
},
},
extraData: {
source: "mobile-app",
},
errorHandler: (error, type) => {
if (type === "FORM_ERROR") {
console.error("Form submission error:", error);
}
},
onFinish: (data) => {
console.log("Submission completed:", data);
},
};
return (
<View style={styles.container}>
<DynamicForm
controllers={controllers}
formSchema={formSchema}
apiOptions={apiOptions}
submitBtn={{ title: "Submit Form" }}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: "#fff",
},
});
Key API Submission Features
The DynamicForm component provides powerful API submission capabilities:
- Automatic Error Handling: Handles form-level and API-level errors
- Flexible API Configuration:
- Specify API endpoint
- Choose HTTP method
- Add custom headers
- Include extra data
- Error Callback: Custom error handling
- Submission Callback: Handle successful submissions
- Global Configuration: Set base URL, default headers via
initConfig()
The component uses Axios under the hood, supporting:
- Automatic token attachment
- Timeout configuration
- Custom error handling
- Flexible API interaction
Multi-step Form Example
import React from "react";
import { View, StyleSheet } from "react-native";
import DynamicForm from "expo-dynamic-form";
import { z } from "zod";
export default function App() {
const personalInfoSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters"),
email: z.string().email("Invalid email address"),
});
const addressSchema = z.object({
street: z.string().min(5, "Please enter a valid street address"),
city: z.string().min(2, "City is required"),
zipCode: z.string().min(5, "ZIP code is required"),
});
const steps = [
{
stepName: "Personal Information",
stepSchema: personalInfoSchema,
controllers: [
{
name: "name",
label: "Full Name",
type: "text",
placeholder: "Enter your full name",
},
{
name: "email",
label: "Email Address",
type: "email",
placeholder: "Enter your email",
},
],
},
{
stepName: "Address",
stepSchema: addressSchema,
controllers: [
{
name: "street",
label: "Street Address",
type: "text",
placeholder: "Enter street address",
},
{
name: "city",
label: "City",
type: "text",
placeholder: "Enter city",
},
{
name: "zipCode",
label: "ZIP Code",
type: "text",
placeholder: "Enter ZIP code",
},
],
},
];
const handleSubmit = async ({ values }) => {
console.log("Form values:", values);
};
return (
<View style={styles.container}>
<DynamicForm
steps={steps}
formtype="steper"
handleSubmit={handleSubmit}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: "#fff",
},
});
Controller Types
The package supports a wide range of controller types:
-
Text Inputs
text: Standard text input
email: Email input with validation
password: Secure text input
number: Numeric input
textarea: Multi-line text input
-
Selection Inputs
select: Dropdown select
multi-select: Multi-selection dropdown
searchable-select: Searchable dropdown
group-checkbox: Group of checkboxes
-
Date and Time Inputs
date: Date picker
date-of-birth: Specialized date of birth picker
datetime: Combined date and time picker
-
Location Inputs
location: Single location selection
multi-location: Multiple location selection
current-location: Current device location
-
Special Inputs
phone: Phone number input with country code
tags-input: Tagging input
currency: Currency selection
rich-text: Rich text editor
-
File and Image Inputs
file-upload: File upload with multiple type support
image-gallery: Multiple image upload
featured-image: Single image upload
-
Advanced Inputs
sub-form: Nested form support
list-creator: Dynamic list creation
react-node: Custom React component input
API Reference
DynamicForm Props
controllers | FormControllerProps[] | Array of form controllers for normal forms |
steps | StepsType<ZodSchema>[] | Array of step configurations for multi-step forms |
formSchema | ZodSchema | Zod schema for form validation |
formtype | "normal" | "steper" | Type of form to render (default: "normal") |
handleSubmit | (params: DynamicFormHanldeSubmitParamType<ZodSchema>) => Promise<void> | Function to handle form submission |
submitBtn | { title?: string } | Configuration for submit button |
stepPreview | (value: any) => ReactNode | Function to render step preview in multi-step forms |
hideStepsIndication | boolean | Hide step indicators in multi-step forms |
apiOptions | apiOptionsType | API configuration for form submission |
tricker | (props: any) => ReactNode | Custom trigger for form submission |
props | PropsPropsType | Custom props for form components |
modalComponent | (data: ModalType, setModal: (modal: ModalType) => void) => ReactNode | Custom modal component |
Configuration
Global API Configuration
You can initialize global configuration for API calls using the initConfig method:
import { initConfig } from "expo-dynamic-form";
import config from "./form.config.json";
import { getSession } from "./your-session-service";
initConfig(config, getSession);
Configuration Object Example
Create a form.config.json in your project root:
{
"api": {
"baseURL": "https://your-api-base-url.com",
"headers": {
"Content-Type": "application/json"
},
"timeout": 30000
}
}
Session Retrieval Function
The session retrieval function is optional and can be used to dynamically fetch access tokens:
export const getSession = async () => {
try {
const storedSession = await AsyncStorage.getItem("user_session");
if (storedSession) {
const sessionData = JSON.parse(storedSession);
return {
accessToken: sessionData.accessToken,
};
}
return { accessToken: undefined };
} catch (error) {
console.error("Failed to retrieve session", error);
return { accessToken: undefined };
}
};
Advanced API Submission Options
apiOptions={{
api: "/auth/login",
method: "POST",
options: {
headers: {
'X-Custom-Header': 'value'
},
withCredentials: true
},
extraData: {
deviceInfo: 'mobile-app',
timezone: 'UTC'
},
onVerify: (data) => {
if (data.requiresVerification) {
navigateToVerificationScreen();
}
},
onFinish: async (data) => {
await saveUserSession(data);
navigateToMainScreen();
}
}}
Error Handling and Form Validation
The package provides a sophisticated error handling mechanism that seamlessly integrates backend validation with Zod schema validation.
Backend Error Response Format
Your backend can return errors in two primary formats:
{
"error": {
"path": ["email"],
"message": "Invalid Email Or Password"
},
"errorType": "FORM_ERROR"
}
{
"errors": [
{
"path": ["email"],
"message": "Invalid Email address"
},
{
"path": ["password"],
"message": "Password is too short"
}
],
"errorType": "FORM_ERROR"
}
How Error Handling Works
- Automatic Error Mapping: The form automatically maps backend errors to specific form fields
- Supports Single and Multiple Errors: Works with both single error objects and error arrays
- Zod Schema Compatibility: Error structure matches Zod's
safeParse error format
- Field-Specific Error Placement: Errors are displayed next to corresponding form fields
Example Backend Error Handling:
app.post("/auth/login", (req, res) => {
if (!isValidUser) {
return res.status(404).json({
error: {
path: ["email"],
message: "Invalid Email Or Password",
},
errorType: "FORM_ERROR",
});
}
if (validationFailed) {
return res.status(400).json({
errors: [
{
path: ["email"],
message: "Email is required",
},
{
path: ["password"],
message: "Password must be at least 8 characters",
},
],
errorType: "FORM_ERROR",
});
}
});
Verification Flow
The package supports advanced verification scenarios, such as two-factor authentication or email verification:
{
"succesType": "VERIFIED",
"data": {
"user": { ... },
"requiresAdditionalAction": true
}
}
Example API Options for Verification:
apiOptions={{
api: "/auth/login",
method: "POST",
onVerify: (data) => {
if (data.requiresVerification) {
navigateToVerificationScreen();
}
},
onFinish: async (data) => {
if (data.succesType === "VERIFIED") {
await saveUserSession(data.data);
navigateToMainScreen();
}
}
}}
Verification Flow Characteristics
- Flexible Verification Handling:
- Detect when additional verification is needed
- Support for multi-step authentication processes
- Success Types:
VERIFIED: Indicates complete authentication
- Allows for conditional navigation based on verification status
- Rich Metadata:
- Carry additional user or verification-related data
- Support complex authentication scenarios
Backend Example:
app.post("/auth/login", (req, res) => {
if (needsTwoFactorVerification) {
return res.status(200).json({
succesType: "VERIFIED",
data: {
user: partialUserData,
requiresVerification: true,
verificationMethod: "otp",
},
});
}
if (authenticationSuccessful) {
return res.status(200).json({
succesType: "VERIFIED",
data: {
user: fullUserData,
accessToken: generatedToken,
},
});
}
});
Key Configuration Features
- Flexible Configuration:
- Global API settings via JSON config
- Optional session token retrieval
- Dynamic headers and timeout
- Seamless Token Management:
- Automatically attach access tokens
- Support for custom session retrieval
- Error Handling:
- Built-in error management
- Custom error callback support
- Submission Callbacks:
onFinish for successful submissions
errorHandler for custom error processing
The configuration is designed to be:
- Optional
- Easily customizable
- Adaptable to different project structures
Contributing
Contributions are welcome! Please see the contributing guide for details on how to get started.
License
MIT License