Native mobile UI components for SurveyJS Form Library - iOS and Android only, no WebView dependencies
This library includes a custom-built version of survey-core with React Native compatibility fixes. The table below shows which version of survey-core is included with each release:
react-native-survey-js-ui | survey-core | Release Date | Notes |
---|---|---|---|
0.1.0 | 2.1.1 | 2025-06-07 | Initial release with React Native fixes |
We maintain our own build of survey-core with the following React Native compatibility fixes:
- Fixed
window.addEventListener
calls in drag-drop functionality - Fixed
document.head
access in settings initialization - Added minimal window/document polyfills for React Native environment
- Ensured proper module resolution for React Native bundlers
Note: This library is locked to specific survey-core versions to ensure compatibility. Each release is thoroughly tested with the corresponding survey-core version.
npm install react-native-survey-js-ui
This library requires the following peer dependencies to be installed in your React Native project:
react
(any version)react-native
(any version)
These dependencies are not bundled with the library and must be provided by your application.
import { Survey } from 'react-native-survey-js-ui';
import type { SurveyModel } from 'react-native-survey-js-ui';
const surveyModel: SurveyModel = {
pages: [
{
name: 'page1',
elements: [
{
type: 'text',
name: 'question1',
title: 'What is your name?',
},
],
},
],
};
// In your component
<Survey model={surveyModel} />
The Survey component supports event handlers to respond to user interactions:
<Survey
model={surveyModel}
onComplete={(result) => console.log('Survey completed:', result)}
onValueChanged={(event) => console.log('Value changed:', event)}
onCurrentPageChanged={(event) => console.log('Page changed:', event)}
/>
For detailed event documentation and examples, see Event System Documentation.
The main Survey component for rendering SurveyJS models in React Native.
Props:
Prop | Type | Required | Description |
---|---|---|---|
model |
SurveyModel |
âś“ | The survey configuration object |
onComplete |
(result: SurveyCompleteEvent) => void |
Called when survey is completed | |
onValueChanged |
(event: SurveyValueChangedEvent) => void |
Called when any question value changes | |
onCurrentPageChanged |
(event: SurveyCurrentPageChangedEvent) => void |
Called when page navigation occurs |
Example:
import { Survey, type SurveyModel } from 'react-native-survey-js-ui';
const surveyModel: SurveyModel = {
pages: [{
elements: [{
type: 'text',
name: 'firstName',
title: 'What is your first name?'
}]
}]
};
<Survey
model={surveyModel}
onComplete={(result) => {
console.log('Survey data:', result.data);
}}
onValueChanged={(event) => {
console.log(`${event.name} changed to:`, event.value);
}}
/>
A simplified Survey component with minimal configuration options.
Props:
Prop | Type | Required | Description |
---|---|---|---|
model |
SurveyModel |
âś“ | The survey configuration object |
onComplete |
(result: SurveyCompleteEvent) => void |
Called when survey is completed |
Example:
import { SimpleSurvey } from 'react-native-survey-js-ui';
<SimpleSurvey
model={surveyModel}
onComplete={(result) => console.log(result)}
/>
Hook for managing SurveyJS model state and lifecycle.
Parameters:
Parameter | Type | Required | Description |
---|---|---|---|
model |
SurveyModel |
âś“ | Initial survey model |
options |
UseSurveyModelOptions |
Configuration options |
Returns: UseSurveyModelResult
Example:
import { useSurveyModel } from 'react-native-survey-js-ui';
function MySurveyComponent() {
const { surveyModel, isCompleted, data } = useSurveyModel(initialModel, {
autoNavigateToNextPage: true
});
return (
<Survey model={surveyModel} />
);
}
Hook for managing survey state and data.
Parameters:
Parameter | Type | Required | Description |
---|---|---|---|
initialData |
SurveyData |
Initial survey data |
Returns: SurveyState
Example:
import { useSurveyState } from 'react-native-survey-js-ui';
function MyComponent() {
const { data, setData, isCompleted } = useSurveyState({
firstName: 'John'
});
return (
<Survey
model={surveyModel}
onValueChanged={(event) => {
setData(prev => ({ ...prev, [event.name]: event.value }));
}}
/>
);
}
Validates a survey model for correctness and compatibility.
Parameters:
Parameter | Type | Required | Description |
---|---|---|---|
model |
any |
âś“ | The survey model to validate |
Returns: boolean
- True if model is valid
Example:
import { validateSurveyModel } from 'react-native-survey-js-ui';
const isValid = validateSurveyModel(surveyModel);
if (!isValid) {
console.error('Invalid survey model');
}
Initializes the SurveyJS core with React Native polyfills.
Returns: void
Example:
import { initializeSurveyCore } from 'react-native-survey-js-ui';
// Call once before using surveys
initializeSurveyCore();
Creates a SurveyJS model instance from a model definition.
Parameters:
Parameter | Type | Required | Description |
---|---|---|---|
model |
SurveyModel |
âś“ | The survey model definition |
Returns: Survey model instance
Utility class for managing multiple survey models.
Methods:
create(model: SurveyModel)
- Create a new survey modeldispose(model)
- Clean up survey model resourcesgetById(id: string)
- Get model by identifier
The library provides comprehensive TypeScript definitions:
SurveyModel
- Main survey configuration interfacePageModel
- Survey page definitionQuestionModel
- Question configurationSurveyData
- Survey response dataSurveyResult
- Complete survey result
SurveyCompleteEvent
- Survey completion eventSurveyValueChangedEvent
- Question value change eventSurveyCurrentPageChangedEvent
- Page navigation event
SurveyProps
- Survey component propsSimpleSurveyProps
- SimpleSurvey component propsBaseQuestionProps
- Base question component props
UseSurveyModelOptions
- useSurveyModel configurationUseSurveyModelResult
- useSurveyModel return typeSurveyState
- useSurveyState return type
QuestionType
- Union of supported question typesChoiceItem
- Choice option for selection questionsValidatorModel
- Question validation rulesMatrixRow
,MatrixColumn
- Matrix question definitions
import React from 'react';
import { View } from 'react-native';
import { Survey, type SurveyModel } from 'react-native-survey-js-ui';
const textSurvey: SurveyModel = {
pages: [{
elements: [
{
type: 'text',
name: 'firstName',
title: 'What is your first name?',
isRequired: true
},
{
type: 'text',
name: 'email',
title: 'What is your email address?',
inputType: 'email',
validators: [
{ type: 'email', text: 'Please enter a valid email address' }
]
}
]
}]
};
export default function BasicSurvey() {
return (
<View style={{ flex: 1, padding: 20 }}>
<Survey
model={textSurvey}
onComplete={(result) => {
console.log('User data:', result.data);
// Handle survey completion
}}
/>
</View>
);
}
import React from 'react';
import { Survey, type SurveyModel } from 'react-native-survey-js-ui';
const choiceSurvey: SurveyModel = {
pages: [{
elements: [
{
type: 'radiogroup',
name: 'favoriteColor',
title: 'What is your favorite color?',
choices: [
{ value: 'red', text: 'Red' },
{ value: 'blue', text: 'Blue' },
{ value: 'green', text: 'Green' },
{ value: 'yellow', text: 'Yellow' }
]
},
{
type: 'checkbox',
name: 'hobbies',
title: 'What are your hobbies? (Select all that apply)',
choices: [
'Reading',
'Gaming',
'Sports',
'Music',
'Travel'
],
hasOther: true,
otherText: 'Other (please specify)'
}
]
}]
};
export default function ChoiceSurvey() {
return (
<Survey
model={choiceSurvey}
onValueChanged={(event) => {
console.log(`${event.name} changed to:`, event.value);
}}
/>
);
}
import React, { useState } from 'react';
import { View, Text } from 'react-native';
import { Survey, type SurveyModel } from 'react-native-survey-js-ui';
const multiPageSurvey: SurveyModel = {
title: 'Customer Feedback Survey',
showProgressBar: 'top',
showPageNumbers: true,
pages: [
{
name: 'personalInfo',
title: 'Personal Information',
elements: [
{
type: 'text',
name: 'name',
title: 'Full Name',
isRequired: true
},
{
type: 'dropdown',
name: 'age',
title: 'Age Range',
choices: ['18-24', '25-34', '35-44', '45-54', '55+']
}
]
},
{
name: 'feedback',
title: 'Your Feedback',
elements: [
{
type: 'rating',
name: 'satisfaction',
title: 'How satisfied are you with our service?',
rateMin: 1,
rateMax: 5,
minRateDescription: 'Very Unsatisfied',
maxRateDescription: 'Very Satisfied'
},
{
type: 'comment',
name: 'suggestions',
title: 'Any suggestions for improvement?',
placeholder: 'Please share your thoughts...'
}
]
}
]
};
export default function MultiPageSurvey() {
const [currentPage, setCurrentPage] = useState(0);
return (
<View style={{ flex: 1 }}>
<Text>Page {currentPage + 1} of 2</Text>
<Survey
model={multiPageSurvey}
onCurrentPageChanged={(event) => {
setCurrentPage(event.newCurrentPage?.name === 'feedback' ? 1 : 0);
}}
onComplete={(result) => {
console.log('Survey completed!', result.data);
}}
/>
</View>
);
}
import React from 'react';
import { Survey, type SurveyModel } from 'react-native-survey-js-ui';
const conditionalSurvey: SurveyModel = {
pages: [{
elements: [
{
type: 'boolean',
name: 'hasJob',
title: 'Are you currently employed?'
},
{
type: 'text',
name: 'jobTitle',
title: 'What is your job title?',
visibleIf: '{hasJob} = true',
isRequired: true
},
{
type: 'text',
name: 'company',
title: 'What company do you work for?',
visibleIf: '{hasJob} = true'
},
{
type: 'radiogroup',
name: 'jobSearch',
title: 'Are you actively looking for work?',
visibleIf: '{hasJob} = false',
choices: [
'Yes, actively searching',
'Yes, but not urgently',
'No, not looking'
]
}
]
}]
};
export default function ConditionalSurvey() {
return (
<Survey
model={conditionalSurvey}
onValueChanged={(event) => {
// This will show/hide questions based on the hasJob answer
console.log('Survey updated:', event.name, event.value);
}}
/>
);
}
import React from 'react';
import { Survey, type SurveyModel } from 'react-native-survey-js-ui';
const validationSurvey: SurveyModel = {
pages: [{
elements: [
{
type: 'text',
name: 'password',
title: 'Create a password',
inputType: 'password',
validators: [
{
type: 'regex',
text: 'Password must be at least 8 characters with numbers and letters',
regex: '^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$'
}
]
},
{
type: 'text',
name: 'confirmPassword',
title: 'Confirm your password',
inputType: 'password',
validators: [
{
type: 'expression',
text: 'Passwords do not match',
expression: '{confirmPassword} = {password}'
}
]
},
{
type: 'text',
name: 'age',
title: 'Your age',
inputType: 'number',
validators: [
{
type: 'numeric',
text: 'Age must be between 13 and 120',
minValue: 13,
maxValue: 120
}
]
}
]
}]
};
export default function ValidationSurvey() {
return (
<Survey model={validationSurvey} />
);
}
import React from 'react';
import {
Survey,
type SurveyModel,
type SurveyCompleteEvent,
type SurveyValueChangedEvent,
validateSurveyModel
} from 'react-native-survey-js-ui';
// Define your survey data type
interface CustomerData {
name: string;
email: string;
rating: number;
feedback?: string;
}
const customerSurvey: SurveyModel = {
pages: [{
elements: [
{
type: 'text',
name: 'name',
title: 'Your Name',
isRequired: true
},
{
type: 'text',
name: 'email',
title: 'Email Address',
inputType: 'email',
isRequired: true
},
{
type: 'rating',
name: 'rating',
title: 'Rate our service',
rateMin: 1,
rateMax: 5
},
{
type: 'comment',
name: 'feedback',
title: 'Additional feedback'
}
]
}]
};
export default function TypeScriptSurvey() {
const handleComplete = (event: SurveyCompleteEvent) => {
// Type-safe access to survey data
const data = event.data as CustomerData;
console.log('Customer name:', data.name);
console.log('Customer email:', data.email);
console.log('Rating:', data.rating);
};
const handleValueChanged = (event: SurveyValueChangedEvent) => {
console.log(`Field ${event.name} changed to:`, event.value);
};
// Validate before rendering
if (!validateSurveyModel(customerSurvey)) {
return <Text>Invalid survey configuration</Text>;
}
return (
<Survey
model={customerSurvey}
onComplete={handleComplete}
onValueChanged={handleValueChanged}
/>
);
}
- Event System - Comprehensive guide to handling survey events
- Development Guide - Development workflow and best practices
- Build Guide - Building and releasing the library
This project follows strict Test-Driven Development (TDD) practices. All contributions must adhere to TDD principles:
- TDD Workflow Guide - Complete TDD process and principles
- TDD Integration Guide - Integrating TDD into development workflow
- React Native Testing Best Practices - Library-specific testing patterns
- Mock Usage Guide - Comprehensive mocking strategies
- Test Organization Conventions - Structure and naming standards
- Test Templates - Ready-to-use test templates
- TDD Code Review Checklist - Ensuring TDD compliance
- Testing Troubleshooting FAQ - Common issues and solutions
- CRITICAL: All code must follow the Red-Green-Refactor cycle
- Coverage: >90% code coverage required for all new code
- Tests First: No implementation without corresponding tests
- Quality: Tests must validate behavior, not just achieve coverage
Problem: Survey component renders but shows blank content.
Solutions:
-
Verify your survey model has valid structure:
import { validateSurveyModel } from 'react-native-survey-js-ui'; if (!validateSurveyModel(yourModel)) { console.error('Invalid survey model'); }
-
Ensure survey model has at least one page with elements:
const validModel = { pages: [{ elements: [{ type: 'text', name: 'question1', title: 'Your question' }] }] };
Problem: TypeScript compilation errors when using the library.
Solutions:
-
Import types explicitly:
import { Survey, type SurveyModel } from 'react-native-survey-js-ui';
-
Ensure your tsconfig.json includes proper module resolution:
{ "compilerOptions": { "moduleResolution": "node", "esModuleInterop": true, "allowSyntheticDefaultImports": true } }
Problem: Metro cannot resolve the module or shows bundling errors.
Solutions:
-
Clear Metro cache:
npx react-native start --reset-cache
-
Add to your metro.config.js if using custom configuration:
module.exports = { resolver: { alias: { 'react-native-survey-js-ui': path.resolve(__dirname, 'node_modules/react-native-survey-js-ui') } } };
Problem: Library not working with specific React Native versions.
Solutions:
- Check peer dependency compatibility in package.json
- This library requires React Native 0.60+ for auto-linking
- For older versions, manual linking may be required
Problem: onComplete, onValueChanged handlers not being called.
Solutions:
-
Ensure handlers are defined as functions:
const handleComplete = (result) => { console.log('Survey completed:', result); }; <Survey model={model} onComplete={handleComplete} />
-
Check that survey model has proper question names:
{ type: 'text', name: 'uniqueName', // Must have unique name title: 'Question' }
Solutions:
- Split large surveys into multiple pages
- Use conditional logic to reduce visible questions
- Consider lazy loading for complex question types
Solutions:
- Properly dispose of survey models:
import { disposeSurveyModel } from 'react-native-survey-js-ui'; useEffect(() => { return () => { if (surveyModel) { disposeSurveyModel(surveyModel); } }; }, [surveyModel]);
A: Yes, this library is compatible with Expo managed and bare workflows. No native linking is required.
A: Currently, the library supports basic question types (text, radiogroup, checkbox, dropdown, etc.). Advanced question types are being added progressively. Check the latest version for supported types.
A: Yes, the library provides styling options and theming support. See the theming documentation for details.
A: Yes, this library works completely offline since it renders natively without WebView dependencies.
A: The API is largely compatible. Main changes:
- Import from
react-native-survey-js-ui
instead ofsurvey-react
- Some web-specific properties may not be supported
- Styling uses React Native style objects instead of CSS
A: Yes, the library includes comprehensive TypeScript definitions and is built with TypeScript.
A: The library provides survey data through completion events. You handle persistence:
<Survey
model={model}
onComplete={(result) => {
// Save to AsyncStorage, database, or API
AsyncStorage.setItem('surveyData', JSON.stringify(result.data));
}}
/>
A: There are no hard limits, but for best performance:
- Keep individual pages under 20 questions
- Use pagination for longer surveys
- Consider conditional logic to reduce visible questions
A: Please use the GitHub Issues page for bug reports and feature requests.
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { Survey } from 'react-native-survey-js-ui';
const Stack = createStackNavigator();
function SurveyScreen({ route, navigation }) {
const { surveyModel } = route.params;
return (
<Survey
model={surveyModel}
onComplete={(result) => {
// Navigate to results screen with data
navigation.navigate('Results', { data: result.data });
}}
/>
);
}
export default function App() {
return (
<Stack.Navigator>
<Stack.Screen name="Survey" component={SurveyScreen} />
<Stack.Screen name="Results" component={ResultsScreen} />
</Stack.Navigator>
);
}
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Survey } from 'react-native-survey-js-ui';
import { saveSurveyData, updateSurveyProgress } from './surveySlice';
export default function ReduxSurvey() {
const dispatch = useDispatch();
const surveyModel = useSelector(state => state.survey.currentModel);
return (
<Survey
model={surveyModel}
onValueChanged={(event) => {
dispatch(updateSurveyProgress({
questionName: event.name,
value: event.value
}));
}}
onComplete={(result) => {
dispatch(saveSurveyData(result.data));
}}
/>
);
}
import React, { useEffect, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Survey, type SurveyModel } from 'react-native-survey-js-ui';
export default function PersistentSurvey({ surveyModel }: { surveyModel: SurveyModel }) {
const [savedData, setSavedData] = useState(null);
useEffect(() => {
// Load saved data on component mount
AsyncStorage.getItem('survey_progress')
.then(data => {
if (data) {
setSavedData(JSON.parse(data));
}
})
.catch(console.error);
}, []);
// Update the model with saved data if available
const modelWithData = savedData
? { ...surveyModel, data: savedData }
: surveyModel;
return (
<Survey
model={modelWithData}
onValueChanged={(event) => {
// Save progress after each change
AsyncStorage.setItem('survey_progress', JSON.stringify({
...savedData,
[event.name]: event.value
}));
setSavedData(prev => ({ ...prev, [event.name]: event.value }));
}}
onComplete={(result) => {
// Clear saved progress on completion
AsyncStorage.removeItem('survey_progress');
console.log('Survey completed:', result.data);
}}
/>
);
}
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
Made with create-react-native-library