Last active
May 11, 2019 22:23
-
-
Save mfbx9da4/9035caaabe9ea95d7a53d21154606d30 to your computer and use it in GitHub Desktop.
A first draft of a re-implementation of Formik using react hooks specialised for react native
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, { | |
useState, | |
SetStateAction, | |
createContext, | |
Dispatch, | |
ReactNode | |
} from 'react' | |
import { TextInput, TextInputProps } from 'react-native' | |
export interface FormContextI<T> { | |
values: T | |
errors: { [key in keyof T | string]?: string } | |
setValues: Dispatch<SetStateAction<T>> | |
handleChange: (key: keyof T) => (value: string) => void | |
handleBlur: (key: keyof T) => () => void | |
handleSubmit: () => void | |
setSubmitting: (value: boolean) => void | |
setFieldError: (field: string, message: string) => void | |
isSubmitting: boolean | |
isValidating: boolean | |
} | |
export interface FormConfigI<T extends { [key: string]: string }> { | |
initialValues: T | |
onSubmit: (values: T) => void | |
} | |
function shallowCloneWithDefaults< | |
T extends { [key: string]: unknown }, | |
D extends unknown | |
>(values: T, defaultValue: D) { | |
type U = { [K in keyof T]: D } | |
const keys = Object.keys(values) as Array<keyof U> | |
const partial: Partial<U> = {} | |
for (let k in keys) { | |
partial[k] = defaultValue | |
} | |
const final = partial as U | |
return final | |
} | |
function useForm<T extends { [key: string]: string }>({ | |
initialValues, | |
onSubmit | |
}: FormConfigI<T>): FormContextI<T> { | |
type Key = keyof typeof initialValues | |
const [values, setValues] = useState(initialValues) | |
const [isSubmitting, setIsSubmitting] = useState(false) | |
const [isValidating, setValidating] = useState(false) | |
const initialTouched = shallowCloneWithDefaults(values, false) | |
const [touched, setTouched] = useState(initialTouched) | |
const initialErrors: FormContextI<T>['errors'] = {} | |
const [errors, setErrors] = useState(initialErrors) | |
const handleBlur = (field: Key) => () => { | |
setTouched({ ...touched, [field]: true }) | |
} | |
const handleChange = (field: Key) => (value: string) => { | |
setValues({ ...values, [field]: value }) | |
} | |
const setFieldError = (field: string, message: string) => { | |
if (errors[field] === message) return | |
setErrors({ ...errors, [field]: message }) | |
} | |
const setSubmitting = (value: boolean) => { | |
if (isSubmitting === value) return | |
setIsSubmitting(value) | |
} | |
async function handleSubmit() { | |
onSubmit(values) | |
} | |
return { | |
values, | |
setValues, | |
handleChange, | |
handleBlur, | |
handleSubmit, | |
isSubmitting, | |
isValidating, | |
setSubmitting, | |
setFieldError, | |
errors | |
} | |
} | |
export function TextInputField<T extends { [key: string]: string }>({ | |
name, | |
context, | |
...rest | |
}: { name: keyof T } & TextInputProps & { context: FormContextI<T> }) { | |
return ( | |
<TextInput | |
{...rest} | |
value={context.values[name]} | |
onChangeText={text => { | |
context.handleChange(name)(text) | |
rest.onChangeText && rest.onChangeText(text) | |
}} | |
onBlur={e => context.handleBlur(name)()} | |
/> | |
) | |
} | |
const { Provider: FormProvider } = createContext<FormContextI<any>>({} as any) | |
function Formik<T extends { [key: string]: string }>({ | |
render, | |
...config | |
}: FormConfigI<T> & { render: (context: FormContextI<T>) => ReactNode }) { | |
const context = useForm<T>(config) | |
return <FormProvider value={context}>{render(context)}</FormProvider> | |
} | |
export default 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 View from 'components/View' | |
import gql from 'graphql-tag' | |
import Text from 'components/Text' | |
import { | |
StyleSheet, | |
ScrollView, | |
TextInputProps, | |
TouchableOpacity, | |
TextInput | |
} from 'react-native' | |
import { useQuery, useMutation } from 'react-apollo-hooks' | |
import Formik from './FormikHooks' | |
const SIGNIN_MUTATION = gql` | |
mutation SIGNIN_MUTATION($email: String!, $password: String!) { | |
login(email: $email, password: $password) { | |
id | |
name | |
} | |
} | |
` | |
interface LoginData { | |
id: string | |
email: string | |
name: string | |
} | |
interface LoginVariables { | |
[key: string]: string | |
email: string | |
password: string | |
} | |
function loginMutation() { | |
return useMutation<LoginData, LoginVariables>(SIGNIN_MUTATION) | |
} | |
function LoginForm() { | |
const initialValues: LoginVariables = { | |
password: 'test', | |
email: '[email protected]' | |
} | |
const [login, { data, error, loading }] = loginMutation() | |
const config = { | |
initialValues, | |
onSubmit: async (values: LoginVariables) => { | |
try { | |
await login({ variables: values }) | |
} catch (err) { | |
console.log('caught error') | |
} | |
} | |
} | |
return ( | |
<Formik | |
{...config} | |
render={context => { | |
const { | |
values, | |
handleChange, | |
handleBlur, | |
handleSubmit, | |
setFieldError, | |
setSubmitting, | |
errors, | |
isSubmitting | |
} = context | |
if (error) { | |
setFieldError( | |
'form', | |
error.graphQLErrors.map(x => x.message).join('') | |
) | |
} | |
setSubmitting(loading) | |
return ( | |
<View p={'large'} dbg> | |
<View> | |
{isSubmitting && <Text>Loading</Text>} | |
{errors.form && <Text>{`${errors.form}`}</Text>} | |
{data && <Text>{data && JSON.stringify(data)}</Text>} | |
</View> | |
<View p={'large'} dbg> | |
<TextInput | |
placeholder="Email" | |
onChangeText={handleChange('email')} | |
onBlur={handleBlur('email')} | |
value={values.email} | |
/> | |
</View> | |
{errors.email && ( | |
<View> | |
<Text style={styles.formLabel}>{errors.email}</Text> | |
</View> | |
)} | |
<View p={'large'} dbg> | |
<TextInput | |
value={values.password} | |
textContentType={'password'} | |
secureTextEntry | |
onChangeText={handleChange('password')} | |
onBlur={handleBlur('password')} | |
placeholder={'Password'} | |
/> | |
</View> | |
{errors.password && ( | |
<View> | |
<Text style={styles.formLabel}>{errors.password}</Text> | |
</View> | |
)} | |
<TouchableOpacity onPress={handleSubmit}> | |
<View p={'large'} dbg> | |
<Text style={styles.formLabel}>Login</Text> | |
</View> | |
</TouchableOpacity> | |
</View> | |
) | |
}} | |
/> | |
) | |
} | |
function LoginScreen() { | |
return <ScrollView>{LoginForm()}</ScrollView> | |
} | |
const styles = StyleSheet.create({ | |
formLabel: { | |
fontSize: 20, | |
textAlign: 'center', | |
margin: 10 | |
} | |
}) | |
export default LoginScreen |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment