Skip to content

Commit 51fbf2f

Browse files
fix: submit on last field, keyboard dismiss + fix go to next field in modals
1 parent da2cbf4 commit 51fbf2f

14 files changed

+246
-275
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Simple form library for React Native with great UX for developer and end-user ap
2727
- Email, username, password, number, numberText, decimal, decimalText,
2828
- Great typescript support!
2929
- Nested object with dot notation
30-
- Nested forms
30+
- ~~Nested forms~~ (don't work well yet)
3131
- Great decimal support with support for , notation and automatically convert it to a Number object
3232

3333
See a demo: https://twitter.com/RichardLindhout/status/1344009881863516165

example/src/App.tsx

+88-65
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
Form,
99
useFormState,
1010
} from 'react-native-use-form';
11-
import { Appbar, Button, Surface, Text, Title } from 'react-native-paper';
11+
import { Appbar, Button, Text } from 'react-native-paper';
1212
import TextInputWithError from './TextInputWithError';
1313
import { useRef } from 'react';
1414
import { SafeAreaProvider } from 'react-native-safe-area-context';
@@ -30,7 +30,9 @@ type FormType = {
3030
password: string;
3131
age: number | undefined;
3232
money: number | undefined;
33+
description: string | undefined;
3334
postalCode: string | undefined;
35+
postalCodeDisabled: string | undefined;
3436
organization: {
3537
name: string;
3638
telephone: string;
@@ -52,9 +54,11 @@ export default function App() {
5254
email: '',
5355
telephone: '',
5456
password: '',
55-
age: 0,
56-
money: 0,
57+
age: undefined,
58+
money: undefined,
59+
description: '',
5760
postalCode: '',
61+
postalCodeDisabled: '',
5862
organization: {
5963
name: '',
6064
telephone: '',
@@ -135,6 +139,16 @@ export default function App() {
135139
label: 'Postalcode',
136140
})}
137141
/>
142+
<TextInputWithError
143+
editable={false}
144+
mode="outlined"
145+
{...fh.text('postalCode', {
146+
enhance: (v) => {
147+
return (v || '').toUpperCase();
148+
},
149+
label: 'Postalcode (disabled)',
150+
})}
151+
/>
138152

139153
<TextInputWithError
140154
mode="outlined"
@@ -189,8 +203,17 @@ export default function App() {
189203
label: 'Organization revenue',
190204
})}
191205
/>
192-
<AddressEdit {...fh.raw('address')} />
193-
<AddressCompanyEdit {...fh.raw('address.company')} />
206+
<TextInputWithError
207+
mode="outlined"
208+
{...fh.text('description', {
209+
label: 'Description',
210+
required: true,
211+
minLength: 3,
212+
maxLength: 10,
213+
})}
214+
/>
215+
{/*<AddressEdit {...fh.raw('address')} />*/}
216+
{/*<AddressCompanyEdit {...fh.raw('address.company')} />*/}
194217
<Button
195218
mode="contained"
196219
onPress={submit}
@@ -206,66 +229,66 @@ export default function App() {
206229
);
207230
}
208231

209-
function AddressEdit({
210-
value,
211-
onChange,
212-
...rest
213-
}: {
214-
value: AddressType | null | undefined;
215-
onChange: (v: AddressType | null | undefined) => void;
216-
}) {
217-
const [{ formProps }, fh] = useFormState<AddressType>(
218-
value || { street: '', houseNumber: '', company: { name: '' } },
219-
{
220-
onChange,
221-
}
222-
);
223-
return (
224-
<Surface {...rest}>
225-
<Title>Nested form</Title>
226-
<Form {...formProps}>
227-
<TextInputWithError
228-
mode="outlined"
229-
label="Street"
230-
{...fh.streetAddress('street', { required: true })}
231-
/>
232-
<TextInputWithError
233-
mode="outlined"
234-
label="House number"
235-
{...fh.streetAddress('houseNumber')}
236-
/>
237-
</Form>
238-
</Surface>
239-
);
240-
}
241-
242-
function AddressCompanyEdit({
243-
value,
244-
onChange,
245-
...rest
246-
}: {
247-
value: AddressCompany | undefined | null;
248-
onChange: (v: AddressCompany | undefined | null) => void;
249-
}) {
250-
const [{ formProps }, fh] = useFormState<AddressCompany>(
251-
value || { name: '' },
252-
{
253-
onChange,
254-
}
255-
);
256-
return (
257-
<Surface {...rest} style={{ padding: 12 }}>
258-
<Title>Nested form</Title>
259-
<Form {...formProps}>
260-
<TextInputWithError
261-
mode="outlined"
262-
label="Street"
263-
{...fh.text('name')}
264-
/>
265-
</Form>
266-
</Surface>
267-
);
268-
}
232+
// function AddressEdit({
233+
// value,
234+
// onChange,
235+
// ...rest
236+
// }: {
237+
// value: AddressType | null | undefined;
238+
// onChange: (v: AddressType | null | undefined) => void;
239+
// }) {
240+
// const [{ formProps }, fh] = useFormState<AddressType>(
241+
// value || { street: '', houseNumber: '', company: { name: '' } },
242+
// {
243+
// onChange,
244+
// }
245+
// );
246+
// return (
247+
// <Surface {...rest}>
248+
// <Title>Nested form</Title>
249+
// <Form {...formProps}>
250+
// <TextInputWithError
251+
// mode="outlined"
252+
// label="Street"
253+
// {...fh.streetAddress('street', { required: true })}
254+
// />
255+
// <TextInputWithError
256+
// mode="outlined"
257+
// label="House number"
258+
// {...fh.streetAddress('houseNumber')}
259+
// />
260+
// </Form>
261+
// </Surface>
262+
// );
263+
// }
264+
//
265+
// function AddressCompanyEdit({
266+
// value,
267+
// onChange,
268+
// ...rest
269+
// }: {
270+
// value: AddressCompany | undefined | null;
271+
// onChange: (v: AddressCompany | undefined | null) => void;
272+
// }) {
273+
// const [{ formProps }, fh] = useFormState<AddressCompany>(
274+
// value || { name: '' },
275+
// {
276+
// onChange,
277+
// }
278+
// );
279+
// return (
280+
// <Surface {...rest} style={{ padding: 12 }}>
281+
// <Title>Nested form</Title>
282+
// <Form {...formProps}>
283+
// <TextInputWithError
284+
// mode="outlined"
285+
// label="Street"
286+
// {...fh.text('name')}
287+
// />
288+
// </Form>
289+
// </Surface>
290+
// );
291+
// }
269292

270293
const telephoneRegex = {
271294
regex: new RegExp(/^\d+$/),

src/Form.tsx

+3-20
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,7 @@
11
import * as React from 'react';
22
import { FormContext } from './FormContext';
3-
import type { IndexerType, ReferencerType } from './types';
43

5-
export default function Form({
6-
children,
7-
referencer,
8-
indexer,
9-
}: {
10-
children: any;
11-
indexer: IndexerType;
12-
referencer: ReferencerType;
13-
}) {
14-
return (
15-
<FormContext.Provider
16-
value={{
17-
indexer,
18-
referencer,
19-
}}
20-
>
21-
{children}
22-
</FormContext.Provider>
23-
);
4+
const empty = {};
5+
export default function Form({ children }: { children: any }) {
6+
return <FormContext.Provider value={empty}>{children}</FormContext.Provider>;
247
}

src/FormContext.tsx

+3-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import * as React from 'react';
2-
import type { IndexerType, ReferencerType } from './types';
3-
// import { MutableRefObject } from 'react';
42

5-
export type FormContextType = {
6-
indexer: IndexerType;
7-
referencer: ReferencerType;
3+
export type FormContextType = {};
4+
const empty = {};
85

9-
// refForKey: MutableRefObject<FormRefKeyMap>;
10-
};
11-
12-
export const FormContext = React.createContext<FormContextType | undefined>(
13-
undefined
14-
);
6+
export const FormContext = React.createContext<FormContextType>(empty);

src/hasVirtualKeyboard.native.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const hasVirtualKeyboard = true;
2+
export default hasVirtualKeyboard;

src/hasVirtualKeyboard.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const hasTouch = 'ontouchstart' in window;
2+
3+
// const screenIsPortrait = window.matchMedia('(orientation: portrait)').matches;
4+
// return screenIsPortrait ? 'portrait' : 'landscape';
5+
// let heightPerOrientation: Record<string, number> = {
6+
// [getOrientation()]: window.innerHeight,
7+
// };
8+
// window.addEventListener('resize', () => {
9+
// const initialHeight = heightPerOrientation[getOrientation()];
10+
// if (initialHeight) {
11+
// if (window.innerHeight < initialHeight) {
12+
// console.log('Virtual keyboard likely opened');
13+
// } else {
14+
// console.log('Virtual keyboard likely closed');
15+
// initialHeight = window.innerHeight;
16+
// }
17+
// }
18+
// });
19+
20+
const screenIsLarge = window.matchMedia('(min-width: 1000px)').matches;
21+
const hasVirtualKeyboard = hasTouch && !screenIsLarge;
22+
export default hasVirtualKeyboard;

src/types.ts

+2-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
TextInputFocusEventData,
88
TextInputProps,
99
} from 'react-native';
10-
import type { TextInput } from 'react-native';
10+
1111
import type { ScrollView, View } from 'react-native';
1212

1313
type GetIndexedField<T, K> = K extends keyof NonNullable<T>
@@ -133,10 +133,7 @@ export type FormStateType<T> = {
133133
// ) => void;
134134
// clearErrors: () => void;
135135
submit: () => void;
136-
formProps: {
137-
indexer: IndexerType;
138-
referencer: ReferencerType;
139-
};
136+
formProps: {};
140137
setValues: React.Dispatch<SetStateAction<T>>;
141138
hasError: <K extends DotNestedKeys<T>>(key: K) => boolean;
142139
};
@@ -245,14 +242,3 @@ type FormTextType<T> = <K extends DotNestedKeys<T>>(
245242
export type FieldsLastCharacters<T> = {
246243
[key in keyof T]?: string | undefined;
247244
};
248-
249-
type ReferencerReturns = TextInputProps & { ref: React.Ref<TextInput> };
250-
export type ReferencerType = (
251-
key: string,
252-
formIndex: number
253-
) => ReferencerReturns;
254-
255-
export type IndexerType = {
256-
add: () => number;
257-
i: number;
258-
};

src/useFormContext.ts

-16
This file was deleted.

0 commit comments

Comments
 (0)