Skip to content

Instantly share code, notes, and snippets.

@artyorsh
Last active July 15, 2023 10:10
Show Gist options
  • Save artyorsh/7cf130bc99547cc635dc54b85072bfff to your computer and use it in GitHub Desktop.
Save artyorsh/7cf130bc99547cc635dc54b85072bfff to your computer and use it in GitHub Desktop.
UI Kitten Components x Formik
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,
},
});
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,
},
});
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}
/>
);
});
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}
/>
);
});
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