Last active
July 15, 2023 10:10
-
-
Save artyorsh/7cf130bc99547cc635dc54b85072bfff to your computer and use it in GitHub Desktop.
UI Kitten Components x Formik
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { StyleSheet, NativeSyntheticEvent, TextInputSubmitEditingEventData } from 'react-native'; | |
import { FormikContextType, useFormikContext } from 'formik'; | |
import { Autocomplete, AutocompleteProps, AutocompleteItem, AutocompleteItemElement } from '@ui-kitten/components'; | |
export interface FormAutocompleteDataItem { | |
toString: () => string; | |
} | |
type FormAutocompleteDataProps<Item extends FormAutocompleteDataItem = any> = Override<AutocompleteProps, { | |
onSelect?: (item: Item) => void; | |
data: Item[]; | |
}>; | |
export interface FormAutocompleteProps extends FormAutocompleteDataProps { | |
/* | |
* Used for extracting input value from form. Required. | |
*/ | |
id: string; | |
/* | |
* Handle Autocomplete status automatically depending on field validation. Optional, defaults to true. | |
*/ | |
autoStatus?: boolean; | |
/* | |
* Handle Autocomplete caption automatically depending on field validation. Optional, defaults to false. | |
*/ | |
autoCaption?: boolean; | |
} | |
export type FormAutocompleteElement = React.ReactElement<FormAutocompleteProps>; | |
const useFormikAutocompleteState = (props: FormAutocompleteProps): Partial<FormAutocompleteProps> => { | |
const formContext: FormikContextType<{}> = useFormikContext(); | |
const error: string = formContext.errors[props.id]; | |
const [value, setValue] = React.useState(formContext.values[props.id]?.toString() || ''); | |
const validationState: Partial<AutocompleteProps> = { | |
status: props.autoStatus && !formContext.isSubmitting && error && value && 'danger', | |
caption: props.autoCaption && !formContext.isSubmitting && value && error, | |
}; | |
const data = (props.data as FormAutocompleteDataItem[]).filter((item: FormAutocompleteDataItem): boolean => { | |
return item.toString().toLowerCase().includes(value.toLowerCase()); | |
}); | |
const onChangeText = (nextValue: string): void => { | |
setValue(nextValue); | |
props.onChangeText && props.onChangeText(nextValue); | |
}; | |
const onSelect = (index: number): void => { | |
formContext.setFieldValue(props.id, data[index], true); | |
setValue(data[index].toString()); | |
props.onSelect && props.onSelect(data[index]); | |
}; | |
return { value, ...validationState, data, onChangeText, onSelect }; | |
}; | |
const defaultProps: Partial<FormAutocompleteProps> = { | |
autoStatus: true, | |
}; | |
/** | |
* Autocomplete component that works within Formik. | |
* Updates itself automatically by using FormikContext. | |
* | |
* @param {FormAutocompleteProps} props - Autocomplete props with identifier. | |
* | |
* @example Simple Usage | |
* ``` | |
* const Form = (props) => ( | |
* <Autocomplete id='email' /> | |
* ); | |
* | |
* <Formik> | |
* {Form} | |
* </Formik> | |
* ``` | |
* | |
* @see https://github.com/jaredpalmer/formik | |
*/ | |
export const FormAutocomplete = React.forwardRef((props: FormAutocompleteProps, ref: React.Ref<Autocomplete>) => { | |
const autocompleteRef = React.useRef<Autocomplete>(ref); | |
const { id, autoStatus, autoCaption, data: allData, onChangeText, ...inputProps } = { ...defaultProps, ...props }; | |
const { data, ...state } = useFormikAutocompleteState({ id, autoStatus, autoCaption, data: allData, onChangeText }); | |
// TODO: UI Kitten issue. Not updates data list visibility, if initially focused with wrong value. | |
const onAutocompleteChangeText = (query: string): void => { | |
if (!autocompleteRef.current?.state.listVisible && data?.length) { | |
autocompleteRef.current.setState({ listVisible: true }); | |
} | |
state.onChangeText && state.onChangeText(query); | |
}; | |
const onAutocompleteSubmitEditing = (e: NativeSyntheticEvent<TextInputSubmitEditingEventData>): void => { | |
autocompleteRef.current?.setState({ listVisible: false }); | |
props.onSubmitEditing && props.onSubmitEditing(e); | |
}; | |
const renderOption = (item: FormAutocompleteDataItem, index: number): AutocompleteItemElement => ( | |
<AutocompleteItem | |
key={index} | |
style={styles.item} | |
title={item.toString()} | |
/> | |
); | |
return ( | |
<Autocomplete | |
ref={autocompleteRef} | |
{...inputProps} | |
{...state} | |
onChangeText={onAutocompleteChangeText} | |
onSubmitEditing={onAutocompleteSubmitEditing}> | |
{data?.map(renderOption)} | |
</Autocomplete> | |
); | |
}); | |
const styles = StyleSheet.create({ | |
item: { | |
minHeight: 0, | |
paddingVertical: 8, | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { StyleSheet, View, ViewProps } from 'react-native'; | |
import ImagePicker, { ImagePickerOptions, ImagePickerResponse } from 'react-native-image-picker'; | |
import { FormikContextType, useFormikContext } from 'formik'; | |
import { Button } from '@ui-kitten/components'; | |
import { Avatar } from '@components/avatar.component'; | |
import { CameraIcon } from '@components/icons.component'; | |
interface FormAvatarProps extends Omit<ViewProps, 'source'> { | |
id: string; | |
} | |
const IMAGE_PICKER_OPTIONS: ImagePickerOptions = { | |
storageOptions: { skipBackup: true }, | |
}; | |
export const FormAvatar = (props: FormAvatarProps): React.ReactElement => { | |
const { id, ...viewProps } = props; | |
const formContext: FormikContextType<{}> = useFormikContext(); | |
const onEditButtonPress = (): void => { | |
ImagePicker.launchImageLibrary(IMAGE_PICKER_OPTIONS, onPickImageResponse); | |
}; | |
const onPickImageResponse = (result: ImagePickerResponse): void => { | |
if (result.didCancel || result.error) { | |
return; | |
} | |
onPickImageSuccess(result); | |
}; | |
const onPickImageSuccess = (response: ImagePickerResponse): void => { | |
formContext.setFieldValue(id, { | |
type: response.type || '', | |
name: id, | |
uri: response.uri, | |
}); | |
}; | |
return ( | |
<View | |
{...viewProps} | |
style={[styles.container, viewProps.style]}> | |
<Avatar | |
style={styles.image} | |
source={formContext.values[id]} | |
/> | |
<Button | |
style={styles.editButton} | |
icon={CameraIcon} | |
onPress={onEditButtonPress} | |
/> | |
</View> | |
); | |
}; | |
const styles = StyleSheet.create({ | |
container: { | |
width: 100, | |
height: 80, | |
alignSelf: 'center', | |
}, | |
image: { | |
width: 80, | |
height: 80, | |
marginBottom: 16, | |
}, | |
editButton: { | |
position: 'absolute', | |
alignSelf: 'flex-end', | |
width: 44, | |
height: 44, | |
borderRadius: 22, | |
bottom: 0, | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { FormikContextType, useFormikContext } from 'formik'; | |
import { Datepicker, DatepickerProps } from '@ui-kitten/components'; | |
type FormDatepickerOverrideProps<D = Date> = Override<DatepickerProps<D>, { | |
onSelect?: (date: D) => void; | |
}>; | |
export interface FormDatepickerProps<D = Date> extends FormDatepickerOverrideProps<D> { | |
/* | |
* Used for extracting select value from form. Required. | |
*/ | |
id: string; | |
/* | |
* Handle Datepicker status automatically depending on field validation. Optional, defaults to true. | |
*/ | |
autoStatus?: boolean; | |
/* | |
* Handle Datepicker caption automatically depending on field validation. Optional, defaults to false. | |
*/ | |
autoCaption?: boolean; | |
} | |
export type FormDatepickerElement = React.ReactElement<FormDatepickerProps>; | |
const useFormikDatepickerState = (props: FormDatepickerProps): Partial<DatepickerProps> => { | |
const formContext: FormikContextType<{}> = useFormikContext(); | |
const date = formContext.values[props.id]; | |
const error: string = formContext.errors[props.id]; | |
const validationState = { | |
status: props.autoStatus && !formContext.isSubmitting && error && date && 'danger', | |
caption: props.autoCaption && error && date && error, | |
}; | |
const onSelect = (nextDate: Date): void => { | |
formContext.setFieldValue(props.id, nextDate, true); | |
props.onSelect && props.onSelect(nextDate); | |
}; | |
return { date, ...validationState, onSelect }; | |
}; | |
const defaultProps: Partial<FormDatepickerProps> = { | |
autoStatus: true, | |
}; | |
/** | |
* Datepicker component that works within Formik. | |
* Updates itself automatically by using FormikContext. | |
* | |
* @param {FormDatepickerProps} props - Input props with identifier. | |
* | |
* @example Simple Usage | |
* ``` | |
* const Form = (props) => ( | |
* <FormDatepicker id='dob' /> | |
* ); | |
* | |
* <Formik> | |
* {Form} | |
* </Formik> | |
* ``` | |
* | |
* @see https://github.com/jaredpalmer/formik | |
*/ | |
export const FormDatepicker = React.forwardRef((props: FormDatepickerProps, ref: React.Ref<any>) => { | |
const { id, autoStatus, autoCaption, onSelect, ...datepickerProps } = { ...defaultProps, ...props }; | |
const state = useFormikDatepickerState({ id, autoStatus, autoCaption, onSelect }); | |
return ( | |
<Datepicker | |
ref={ref} | |
{...datepickerProps} | |
{...state} | |
/> | |
); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { TextInput } from 'react-native'; | |
import { FormikContextType, useFormikContext } from 'formik'; | |
import { Input, InputProps } from '@ui-kitten/components'; | |
export interface FormInputProps extends InputProps { | |
/* | |
* Used for extracting input value from form. Required. | |
*/ | |
id: string; | |
/* | |
* Handle Input status automatically depending on field validation. Optional, defaults to true. | |
*/ | |
autoStatus?: boolean; | |
/* | |
* Handle Input caption automatically depending on field validation. Optional, defaults to false. | |
*/ | |
autoCaption?: boolean; | |
} | |
export type FormInputElement = React.ReactElement<FormInputProps>; | |
const useFormikInputState = (props: FormInputProps): Partial<InputProps> => { | |
const formContext: FormikContextType<{}> = useFormikContext(); | |
const value = formContext.values[props.id]; | |
const error: string = formContext.errors[props.id]; | |
const validationState: Partial<InputProps> = { | |
status: props.autoStatus && !formContext.isSubmitting && error && value && 'danger', | |
caption: props.autoCaption && !formContext.isSubmitting && value && error, | |
}; | |
const onChangeText = (nextValue: string): void => { | |
formContext.setFieldValue(props.id, nextValue); | |
props.onChangeText && props.onChangeText(nextValue); | |
}; | |
return { value, ...validationState, onChangeText }; | |
}; | |
const defaultProps: Partial<FormInputProps> = { | |
autoStatus: true, | |
}; | |
/** | |
* Input component that works within Formik. | |
* Updates itself automatically by using FormikContext. | |
* | |
* @param {FormInputProps} props - Input props with identifier. | |
* | |
* @example Simple Usage | |
* ``` | |
* const Form = (props) => ( | |
* <Input id='email' /> | |
* ); | |
* | |
* <Formik> | |
* {Form} | |
* </Formik> | |
* ``` | |
* | |
* @see https://github.com/jaredpalmer/formik | |
*/ | |
export const FormInput = React.forwardRef((props: FormInputProps, ref: React.Ref<TextInput>) => { | |
const { id, autoStatus, autoCaption, onChangeText, ...inputProps } = { ...defaultProps, ...props }; | |
const state = useFormikInputState({ id, autoStatus, autoCaption, onChangeText }); | |
return ( | |
<Input | |
ref={ref} | |
{...inputProps} | |
{...state} | |
/> | |
); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { FormikContextType, useFormikContext } from 'formik'; | |
import { IndexPath, Select, SelectItem, SelectItemElement, SelectProps } from '@ui-kitten/components'; | |
export interface FormSelectDataItem { | |
toString: () => string; | |
} | |
type FormSelectDataProps<Item extends FormSelectDataItem = any> = Override<SelectProps, { | |
onSelect?: (item: Item) => void; | |
data: Item[]; | |
}>; | |
export interface FormSelectProps<Item extends FormSelectDataItem> extends FormSelectDataProps<Item> { | |
/* | |
* Used for extracting select value from form. Required. | |
*/ | |
id: string; | |
/* | |
* Handle Select status automatically depending on field validation. Optional, defaults to true. | |
*/ | |
autoStatus?: boolean; | |
/* | |
* Handle Select caption automatically depending on field validation. Optional, defaults to false. | |
*/ | |
autoCaption?: boolean; | |
} | |
export type FormSelectElement<Item = FormSelectDataItem> = React.ReactElement<FormSelectProps<Item>>; | |
const useFormikSelectState = <Item extends FormSelectDataItem>(props: FormSelectProps<Item>): Partial<SelectProps> => { | |
const formContext: FormikContextType<{}> = useFormikContext(); | |
let state: Partial<SelectProps> = {}; | |
const selectedValue: FormSelectDataItem = formContext.values[props.id]; | |
const error = formContext.errors[props.id]; | |
if (selectedValue) { | |
const row = props.data.findIndex(option => option.toString() === selectedValue.toString()); | |
const selectedIndex = new IndexPath(row); | |
state = { selectedIndex, value: selectedValue.toString() }; | |
} | |
const onSelect = (index: IndexPath): void => { | |
formContext.setFieldValue(props.id, props.data[index.row], true); | |
props.onSelect && props.onSelect(props.data[index.row]); | |
}; | |
const validationState: Partial<SelectProps> = { | |
status: props.autoStatus && !formContext.isSubmitting && error && selectedValue && 'danger', | |
caption: props.autoCaption && !formContext.isSubmitting && selectedValue && error, | |
}; | |
// @ts-ignore: onSelect is only for single selected item currently | |
return { ...state, ...validationState, onSelect }; | |
}; | |
const defaultProps: Partial<FormSelectProps<any>> = { | |
autoStatus: true, | |
}; | |
/** | |
* Select component that works within Formik. | |
* Updates itself automatically by using FormikContext. | |
* | |
* @param {FormSelectProps} props - Select props with identifier. | |
* | |
* @example Simple Usage | |
* ``` | |
* class FormSelectItem { | |
* constructor(title) { | |
* this.title = title; | |
* } | |
* | |
* toString() { | |
* return this.title; | |
* } | |
* } | |
* | |
* const data = ['Option 1', 'Option 2'].map(option => new FormSelectItem(option)); | |
* | |
* const Form = (props) => ( | |
* <FormSelect id='option' data={data}/> | |
* ); | |
* | |
* <Formik> | |
* {Form} | |
* </Formik> | |
* ``` | |
* | |
* @see https://github.com/jaredpalmer/formik | |
*/ | |
export const FormSelect = React.forwardRef((props: FormSelectProps<FormSelectDataItem>, ref: React.Ref<any>) => { | |
const { id, autoStatus, data, onSelect, ...selectProps } = { ...defaultProps, ...props }; | |
const state = useFormikSelectState({ id, autoStatus, data, onSelect }); | |
const renderOption = (item: FormSelectDataItem, index: number): SelectItemElement => ( | |
<SelectItem | |
key={index} | |
title={item.toString()} | |
/> | |
); | |
return ( | |
<Select | |
ref={ref} | |
{...selectProps} | |
{...state}> | |
{data.map(renderOption)} | |
</Select> | |
); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment