react-native-form-container
Advanced tools
| export const ValidationFields = { | ||
| email: { | ||
| pattern: { | ||
| value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i, | ||
| }, | ||
| }, | ||
| minPassword: { | ||
| pattern: { | ||
| value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/, | ||
| }, | ||
| }, | ||
| speacialCharacter: { | ||
| pattern: { | ||
| value: /[^a-zA-Z0-9]/, | ||
| }, | ||
| }, | ||
| upperCaseCharacter: { | ||
| pattern: { | ||
| value: /[A-Z]/, | ||
| }, | ||
| }, | ||
| lowerCaseCharacter: { | ||
| pattern: { | ||
| value: /[a-z]/, | ||
| }, | ||
| }, | ||
| number: { | ||
| pattern: { | ||
| value: /[0-9]/, | ||
| }, | ||
| }, | ||
| text: { | ||
| pattern: { | ||
| value: /^[a-zA-Z]*$/, | ||
| }, | ||
| }, | ||
| phone: { | ||
| pattern: { | ||
| value: /^[0-9]*$/, | ||
| }, | ||
| }, | ||
| }; | ||
| export default interface ValidationFields { | ||
| email?: boolean; | ||
| password?: boolean; | ||
| text?: boolean; | ||
| phone?: boolean; | ||
| number?: boolean; | ||
| } | ||
| export interface ValidationPasswordOptions { | ||
| minLength?: number; | ||
| speacial?: boolean; | ||
| upperCase?: boolean; | ||
| lowerCase?: boolean; | ||
| number?: boolean; | ||
| } | ||
| export type ValidationFieldsKeys = typeof ValidationFields; | ||
| export const isValidation = ( | ||
| validation: ValidationFieldsKeys, | ||
| value: any, | ||
| passwordOptions?: ValidationPasswordOptions, | ||
| ) => { | ||
| if (value === undefined || value === null || value === '') { | ||
| return false; | ||
| } | ||
| if (validation === 'email') { | ||
| if (!ValidationFields.email.pattern.value.test(value)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (validation === 'password') { | ||
| if (passwordOptions?.lowerCase) { | ||
| if (!ValidationFields.lowerCaseCharacter.pattern.value.test(value)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (passwordOptions?.upperCase) { | ||
| if (!ValidationFields.upperCaseCharacter.pattern.value.test(value)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (passwordOptions?.number) { | ||
| if (!ValidationFields.number.pattern.value.test(value)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (passwordOptions?.speacial) { | ||
| if (!ValidationFields.speacialCharacter.pattern.value.test(value)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (passwordOptions?.minLength) { | ||
| return value.length >= passwordOptions.minLength; | ||
| } | ||
| } | ||
| if (validation === 'text') { | ||
| if (!ValidationFields.text.pattern.value.test(value)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (validation === 'phone') { | ||
| if (!ValidationFields.phone.pattern.value.test(value)) { | ||
| return false; | ||
| } | ||
| } | ||
| if (validation === 'number') { | ||
| if (!ValidationFields.number.pattern.value.test(value)) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| }; | ||
| export const checkPasswordOptions = ( | ||
| options: ValidationPasswordOptions, | ||
| value: string, | ||
| inputId: string, | ||
| ): { | ||
| password?: boolean; | ||
| lowerCase?: boolean; | ||
| upperCase?: boolean; | ||
| number?: boolean; | ||
| speacial?: boolean; | ||
| minLength?: boolean; | ||
| } => { | ||
| let result = {}; | ||
| if (value === undefined || value.length === 0) { | ||
| result = { | ||
| [inputId]: true, | ||
| }; | ||
| } | ||
| if (options?.lowerCase) { | ||
| if (ValidationFields.lowerCaseCharacter.pattern.value.test(value)) { | ||
| result = { | ||
| lowerCase: true, | ||
| }; | ||
| } | ||
| } | ||
| if (options?.upperCase) { | ||
| if (ValidationFields.upperCaseCharacter.pattern.value.test(value)) { | ||
| result = { | ||
| upperCase: true, | ||
| }; | ||
| } | ||
| } | ||
| if (options?.number) { | ||
| if (ValidationFields.number.pattern.value.test(value)) { | ||
| result = { | ||
| number: true, | ||
| }; | ||
| } | ||
| } | ||
| if (options?.speacial) { | ||
| if (ValidationFields.speacialCharacter.pattern.value.test(value)) { | ||
| result = { | ||
| speacial: true, | ||
| }; | ||
| } | ||
| } | ||
| if (options?.minLength) { | ||
| if (ValidationFields.minPassword.pattern.value.test(value)) { | ||
| result = { | ||
| minLength: true, | ||
| }; | ||
| } | ||
| } | ||
| return result; | ||
| }; |
+20
-8
@@ -1,4 +0,4 @@ | ||
| declare module "react-native-form-container" { | ||
| import { MutableRefObject, ReactNode } from "react"; | ||
| import { ViewProps, TextInputProps } from "react-native"; | ||
| declare module 'react-native-form-container' { | ||
| import {MutableRefObject, ReactNode} from 'react'; | ||
| import {ViewProps, TextInputProps} from 'react-native'; | ||
@@ -9,2 +9,3 @@ export interface FormContainerProps extends ViewProps { | ||
| formContainerRef?: MutableRefObject<FormContainerRef | null>; | ||
| errorMessageField?: string; | ||
| } | ||
@@ -19,7 +20,6 @@ | ||
| export interface FormInputProps extends TextInputProps { | ||
| id: string; | ||
| iconPosition?: "left" | "right"; | ||
| interface InputProps extends TextInputProps { | ||
| iconPosition?: 'left' | 'right'; | ||
| icon?: any; | ||
| inputSize?: "sm" | "md"; | ||
| inputSize?: 'sm' | 'md'; | ||
| enableFocusBorder?: boolean; | ||
@@ -35,5 +35,17 @@ errorMessage?: string; | ||
| errorMessageContainerStyle?: StyleProp<ViewStyle>; | ||
| validation?: keyof ValidationFields; | ||
| } | ||
| interface PasswordInputProps extends InputProps { | ||
| passwordOptions: ValidationPasswordOptions; | ||
| validation: 'password'; | ||
| } | ||
| interface NonPasswordFormInputProps extends InputProps { | ||
| validation?: Exclude<keyof ValidationFields, 'password'>; | ||
| passwordOptions?: never; | ||
| } | ||
| export type FormInputProps = PasswordInputProps | NonPasswordFormInputProps; | ||
| const FormInput: (props: FormInputProps) => JSX.Element; | ||
| export { FormInput }; | ||
| export {FormInput}; | ||
| } |
+2
-5
| { | ||
| "name": "react-native-form-container", | ||
| "version": "1.0.39", | ||
| "version": "1.0.40", | ||
| "description": "A form container component for React Native", | ||
@@ -25,8 +25,5 @@ "main": "src/index.js", | ||
| "devDependencies": { | ||
| "@types/react-i18next": "^8.1.0", | ||
| "@types/react-native": "^0.60.0" | ||
| }, | ||
| "dependencies": { | ||
| "react-i18next": "^14.1.2" | ||
| } | ||
| "dependencies": {} | ||
| } |
+6
-0
@@ -5,2 +5,4 @@ # React Native Form Container | ||
| 📝 [Medium Article](https://medium.com/@ozkankocakaplan07/simple-form-validation-with-react-native-form-container-a198f5fd8597) | ||
| ## Support the Project | ||
@@ -22,2 +24,6 @@ | ||
| <img src="https://miro.medium.com/v2/resize:fit:592/format:webp/1*VWxjZQFQ1UP40jEzVH0UIw.gif" alt="react-native basic form validate" width="250"> | ||
| ## Installation | ||
@@ -24,0 +30,0 @@ |
@@ -1,2 +0,2 @@ | ||
| import React, { useState } from "react"; | ||
| import React, {useState} from 'react'; | ||
| import { | ||
@@ -14,7 +14,9 @@ View, | ||
| Text, | ||
| } from "react-native"; | ||
| } from 'react-native'; | ||
| import ValidationFields, {ValidationPasswordOptions} from './ValidationFields'; | ||
| interface InputProps extends TextInputProps { | ||
| iconPosition?: "left" | "right"; | ||
| iconPosition?: 'left' | 'right'; | ||
| icon?: any; | ||
| inputSize?: "sm" | "md"; | ||
| inputSize?: 'sm' | 'md'; | ||
| enableFocusBorder?: boolean; | ||
@@ -30,7 +32,19 @@ errorMessage?: string; | ||
| errorMessageContainerStyle?: StyleProp<ViewStyle>; | ||
| validation?: keyof ValidationFields; | ||
| } | ||
| interface PasswordInputProps extends InputProps { | ||
| passwordOptions: ValidationPasswordOptions; | ||
| validation: 'password'; | ||
| } | ||
| interface NonPasswordFormInputProps extends InputProps { | ||
| validation?: Exclude<keyof ValidationFields, 'password'>; | ||
| passwordOptions?: never; | ||
| } | ||
| export type FormInputProps = PasswordInputProps | NonPasswordFormInputProps; | ||
| export default function FormInput({ | ||
| iconPosition = "left", | ||
| iconPosition = 'left', | ||
| icon = undefined, | ||
| inputSize = "md", | ||
| inputSize = 'md', | ||
| enableFocusBorder = true, | ||
@@ -41,9 +55,9 @@ errorMessage, | ||
| passwordShowIcon, | ||
| activeBorder = "green", | ||
| inputBorder = "#143722", | ||
| activeBorder = 'green', | ||
| inputBorder = '#143722', | ||
| errorMessageComponent, | ||
| errorMessageTextStyle = { color: "red", marginTop: 5 }, | ||
| errorMessageTextStyle = {color: 'red', marginTop: 5}, | ||
| errorMessageContainerStyle, | ||
| ...props | ||
| }: InputProps) { | ||
| }: FormInputProps) { | ||
| const [passwordShow, setPasswordShow] = useState(false); | ||
@@ -63,14 +77,13 @@ const [isFocused, setIsFocused] = useState(false); | ||
| const size = inputSize === "sm" ? 10 : 15; | ||
| const iconSize = inputSize === "sm" ? 17 : 20; | ||
| const size = inputSize === 'sm' ? 10 : 15; | ||
| const iconSize = inputSize === 'sm' ? 17 : 20; | ||
| const iconSmTop = inputSize === "sm" ? (Platform.OS === "ios" ? 10 : 15) : 20; | ||
| const iconMdTop = inputSize === "md" ? 15 : 20; | ||
| const iconTop = inputSize === "sm" ? iconSmTop : iconMdTop; | ||
| const iconSmTop = inputSize === 'sm' ? (Platform.OS === 'ios' ? 10 : 15) : 20; | ||
| const iconMdTop = inputSize === 'md' ? 15 : 20; | ||
| const iconTop = inputSize === 'sm' ? iconSmTop : iconMdTop; | ||
| const inputPaddingHorizontal = inputSize === "sm" ? 33 : 40; | ||
| const inputPaddingHorizontal = inputSize === 'sm' ? 33 : 40; | ||
| return ( | ||
| <View> | ||
| {iconPosition === "left" && icon && icon()} | ||
| {iconPosition === 'left' && icon && icon()} | ||
| <TextInput | ||
@@ -87,7 +100,7 @@ autoFocus={false} | ||
| paddingLeft: | ||
| iconPosition === "left" && icon !== undefined | ||
| iconPosition === 'left' && icon !== undefined | ||
| ? inputPaddingHorizontal | ||
| : size, | ||
| paddingRight: | ||
| iconPosition === "right" && icon !== undefined | ||
| iconPosition === 'right' && icon !== undefined | ||
| ? inputPaddingHorizontal | ||
@@ -101,5 +114,4 @@ : size, | ||
| <TouchableOpacity | ||
| style={[styles.passwordIcon, { top: iconTop, right: 10 }]} | ||
| onPress={() => setPasswordShow(!passwordShow)} | ||
| > | ||
| style={[styles.passwordIcon, {top: iconTop, right: 10}]} | ||
| onPress={() => setPasswordShow(!passwordShow)}> | ||
| {passwordShow ? passwordShowIcon() : passwordHideIcon()} | ||
@@ -109,4 +121,4 @@ </TouchableOpacity> | ||
| {iconPosition === "right" && icon !== undefined && ( | ||
| <View style={[styles.icon, { top: iconTop, right: 10 }]}>{icon()}</View> | ||
| {iconPosition === 'right' && icon !== undefined && ( | ||
| <View style={[styles.icon, {top: iconTop, right: 10}]}>{icon()}</View> | ||
| )} | ||
@@ -128,16 +140,16 @@ {errorMessage && ( | ||
| input: { | ||
| width: "100%", | ||
| width: '100%', | ||
| borderRadius: 10, | ||
| backgroundColor: "#fff", | ||
| color: "#143722", | ||
| backgroundColor: '#fff', | ||
| color: '#143722', | ||
| borderWidth: 1, | ||
| }, | ||
| icon: { | ||
| position: "absolute", | ||
| position: 'absolute', | ||
| zIndex: 1, | ||
| }, | ||
| passwordIcon: { | ||
| position: "absolute", | ||
| position: 'absolute', | ||
| zIndex: 1, | ||
| }, | ||
| }); |
+62
-53
@@ -7,9 +7,11 @@ import React, { | ||
| useState, | ||
| } from "react"; | ||
| import { LogBox, View, ViewProps } from "react-native"; | ||
| import { useTranslation } from "react-i18next"; | ||
| } from 'react'; | ||
| import {View, ViewProps} from 'react-native'; | ||
| import { | ||
| isValidation, | ||
| checkPasswordOptions, | ||
| } from './components/ValidationFields'; | ||
| export interface FormContainerProps extends ViewProps { | ||
| children: ReactNode; | ||
| formId?: string; | ||
| errorMessageField?: string; | ||
@@ -22,3 +24,3 @@ formContainerRef?: MutableRefObject<FormContainerRef | null>; | ||
| } | ||
| LogBox.ignoreLogs([/react-i18next::/]); | ||
| export default function FormContainer(props: FormContainerProps) { | ||
@@ -28,12 +30,8 @@ const { | ||
| formContainerRef, | ||
| formId, | ||
| errorMessageField = "errorMessage", | ||
| errorMessageField = 'errorMessage', | ||
| } = props; | ||
| const { t } = useTranslation(formId); | ||
| const [children, setChildren] = useState<ReactNode[] | any>( | ||
| React.Children.toArray(initialChildren) | ||
| React.Children.toArray(initialChildren), | ||
| ); | ||
| const [errors, setErrors] = useState<{ [key: string]: string | undefined }>( | ||
| {} | ||
| ); | ||
| const [errors, setErrors] = useState<{[key: string]: string | undefined}>({}); | ||
| const checkValidation = useCallback((errorData: any) => { | ||
@@ -45,11 +43,23 @@ handleErrorMessage(errorData); | ||
| let isEmpty = true; | ||
| React.Children.forEach(initialChildren, (child) => { | ||
| React.Children.forEach(initialChildren, child => { | ||
| if (React.isValidElement(child)) { | ||
| const childProps = { ...child.props }; | ||
| const childProps = {...child.props}; | ||
| if (childProps.id) { | ||
| if ( | ||
| (childProps.required && childProps?.value === "") || | ||
| childProps.value === undefined | ||
| ) { | ||
| isEmpty = false; | ||
| let checkValidation = childProps?.validation; | ||
| if (checkValidation) { | ||
| let result = isValidation( | ||
| childProps?.validation, | ||
| childProps?.value, | ||
| childProps?.passwordOptions, | ||
| ); | ||
| if (!result) { | ||
| isEmpty = false; | ||
| } | ||
| } else { | ||
| if ( | ||
| (childProps.required && childProps?.value === '') || | ||
| childProps.value === undefined | ||
| ) { | ||
| isEmpty = false; | ||
| } | ||
| } | ||
@@ -79,22 +89,9 @@ } | ||
| const handleErrorMessage = useCallback((errorData?: any) => { | ||
| let errorFields = {} as any; | ||
| if (props.formId && !errorData) { | ||
| React.Children.forEach(initialChildren, (child) => { | ||
| if (React.isValidElement(child)) { | ||
| const childProps = { ...child.props }; | ||
| if (childProps.required && childProps?.id) { | ||
| errorFields[childProps.id] = t(childProps.id); | ||
| } | ||
| } | ||
| }); | ||
| setErrors(errorFields); | ||
| } else { | ||
| setErrors(errorData); | ||
| } | ||
| setErrors(errorData); | ||
| }, []); | ||
| useEffect(() => { | ||
| setChildren( | ||
| React.Children.map(initialChildren, (child) => { | ||
| React.Children.map(initialChildren, child => { | ||
| if (React.isValidElement(child)) { | ||
| const childProps = { ...child.props }; | ||
| var childProps = {...child.props}; | ||
@@ -104,26 +101,38 @@ if (childProps.id) { | ||
| let error = errors?.[childProps.id]; | ||
| if ( | ||
| (childProps?.value === "" || childProps?.value === undefined) && | ||
| error | ||
| ) { | ||
| if (props.formId) { | ||
| childProps[errorMessageField] = t(childProps?.id); | ||
| } else { | ||
| let validationCheck = childProps?.validation; | ||
| if (validationCheck) { | ||
| let result = isValidation( | ||
| childProps?.validation, | ||
| childProps?.value, | ||
| childProps?.passwordOptions, | ||
| ); | ||
| if (!result) { | ||
| let errorOptions = checkPasswordOptions( | ||
| childProps?.passwordOptions, | ||
| childProps?.value, | ||
| childProps?.id, | ||
| ); | ||
| let findKeyErrorOptions = Object.keys(errorOptions)[0]; | ||
| childProps[errorMessageField] = errors?.[findKeyErrorOptions]; | ||
| } | ||
| } else { | ||
| if ( | ||
| (childProps?.value === '' || | ||
| childProps?.value === undefined) && | ||
| error | ||
| ) { | ||
| if (error) { | ||
| childProps[errorMessageField] = error; | ||
| } | ||
| } else { | ||
| delete childProps[errorMessageField]; | ||
| } | ||
| } else { | ||
| delete childProps[errorMessageField]; | ||
| } | ||
| } | ||
| if (childProps.type === "checkbox") { | ||
| if (childProps.type === 'checkbox') { | ||
| if (!childProps.checked) { | ||
| if (props.formId) { | ||
| childProps[errorMessageField] = t(childProps.id); | ||
| } else { | ||
| let error = errors[childProps.id]; | ||
| if (error) { | ||
| childProps[errorMessageField] = error; | ||
| } | ||
| let error = errors[childProps.id]; | ||
| if (error) { | ||
| childProps[errorMessageField] = error; | ||
| } | ||
@@ -140,3 +149,3 @@ } else { | ||
| return child; | ||
| }) | ||
| }), | ||
| ); | ||
@@ -143,0 +152,0 @@ }, [initialChildren, errors]); |
21454
30.6%0
-100%1
-50%7
16.67%490
65.54%226
2.73%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed