Skip to content

Commit 2d9f2f4

Browse files
feat: scroll to errors on submit ;)
1 parent 6cf9a5c commit 2d9f2f4

File tree

10 files changed

+4901
-9237
lines changed

10 files changed

+4901
-9237
lines changed

example/metro.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const path = require('path');
2-
const blacklist = require('metro-config/src/defaults/blacklist');
2+
const exclusionList = require('metro-config/src/defaults/exclusionList');
33
const escape = require('escape-string-regexp');
44
const pak = require('../package.json');
55

@@ -16,7 +16,7 @@ module.exports = {
1616
// We need to make sure that only one version is loaded for peerDependencies
1717
// So we blacklist them at the root, and alias them to the versions in example's node_modules
1818
resolver: {
19-
blacklistRE: blacklist(
19+
blacklistRE: exclusionList(
2020
modules.map(
2121
(m) =>
2222
new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`)

example/package.json

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,19 @@
1212
"test": "jest"
1313
},
1414
"dependencies": {
15-
"expo": "^40.0.0",
16-
"expo-splash-screen": "~0.8.1",
17-
"react": "16.13.1",
18-
"react-dom": "16.13.1",
19-
"react-native": "0.63.4",
15+
"expo": "^47.0.0",
16+
"expo-splash-screen": "~0.17.5",
17+
"react": "18.1.0",
18+
"react-dom": "18.1.0",
19+
"react-native": "0.70.5",
2020
"react-native-paper": "^4.9.1",
2121
"react-native-unimodules": "~0.12.0",
22-
"react-native-web": "^0.16.3"
22+
"react-native-web": "~0.18.7"
2323
},
2424
"devDependencies": {
25-
"@babel/core": "~7.12.10",
25+
"@babel/core": "^7.19.3",
2626
"@babel/runtime": "^7.9.6",
27-
"@types/react": "^17.0.9",
28-
"@types/react-native": "^0.64.10",
2927
"babel-plugin-module-resolver": "^4.0.0",
30-
"babel-preset-expo": "8.3.0",
31-
"expo-cli": "^4.0.13",
32-
"typescript": "^4.3.2"
28+
"babel-preset-expo": "~9.2.1"
3329
}
3430
}

example/src/App.tsx

Lines changed: 113 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import * as React from 'react';
22

3-
import { StyleSheet, View } from 'react-native';
3+
import { Platform, ScrollView, StyleSheet, View } from 'react-native';
44
import { Form, useFormState } from '../../src/index';
5-
import { Button, Surface, TextInput, Title } from 'react-native-paper';
5+
import { Appbar, Button, Surface, Title } from 'react-native-paper';
66
import TextInputWithError from './TextInputWithError';
7+
import { useRef } from 'react';
78

89
type AddressCompany = {
910
name: string;
@@ -28,10 +29,8 @@ type FormType = {
2829
address?: AddressType | null;
2930
};
3031
export default function App() {
31-
const [
32-
{ values, errors, submit, formProps, hasError },
33-
fh,
34-
] = useFormState<FormType>(
32+
const scrollViewRef = useRef<ScrollView>(null);
33+
const [{ submit, formProps, hasError }, fh] = useFormState<FormType>(
3534
{
3635
email: '',
3736
telephone: '',
@@ -46,6 +45,7 @@ export default function App() {
4645
},
4746
},
4847
{
48+
scrollViewRef: scrollViewRef,
4949
onChange: () => {
5050
// TODO: fix enum in backend
5151
},
@@ -56,99 +56,106 @@ export default function App() {
5656
}
5757
);
5858

59-
console.log({ values, errors });
59+
// console.log({ values, errors });
6060
return (
6161
<View style={styles.root}>
62-
<Form {...formProps}>
63-
<TextInputWithError
64-
mode="outlined"
65-
error={hasError('email')}
66-
{...fh.email('email', {
67-
validate: (v) => {
68-
return looksLikeMail(v) ? true : 'Email-address is invalid';
69-
},
70-
label: 'Email',
71-
})}
72-
/>
73-
<TextInputWithError
74-
mode="outlined"
75-
{...fh.telephone('telephone', {
76-
required: true,
77-
minLength: 3,
78-
maxLength: 10,
79-
shouldFollowRegexes: [telephoneRegex],
80-
label: 'Telephone',
81-
})}
82-
/>
83-
<TextInputWithError
84-
mode="outlined"
85-
{...fh.text('postalCode', {
86-
enhance: (v) => {
87-
return (v || '').toUpperCase();
88-
},
89-
label: 'Postalcode',
90-
})}
91-
/>
62+
<Appbar.Header>
63+
<Appbar.Content title="Form" />
64+
</Appbar.Header>
65+
<ScrollView style={styles.scrollView} ref={scrollViewRef}>
66+
<View style={styles.inner}>
67+
<Form {...formProps}>
68+
<TextInputWithError
69+
mode="outlined"
70+
error={hasError('email')}
71+
{...fh.email('email', {
72+
validate: (v) => {
73+
return looksLikeMail(v) ? true : 'Email-address is invalid';
74+
},
75+
label: 'Email',
76+
})}
77+
/>
78+
<TextInputWithError
79+
mode="outlined"
80+
{...fh.telephone('telephone', {
81+
required: true,
82+
minLength: 3,
83+
maxLength: 10,
84+
shouldFollowRegexes: [telephoneRegex],
85+
label: 'Telephone',
86+
})}
87+
/>
88+
<TextInputWithError
89+
mode="outlined"
90+
{...fh.text('postalCode', {
91+
enhance: (v) => {
92+
return (v || '').toUpperCase();
93+
},
94+
label: 'Postalcode',
95+
})}
96+
/>
9297

93-
<TextInputWithError
94-
mode="outlined"
95-
{...fh.password('password', {
96-
required: true,
97-
minLength: 3,
98-
maxLength: 10,
99-
label: 'Password',
100-
})}
101-
/>
98+
<TextInputWithError
99+
mode="outlined"
100+
{...fh.password('password', {
101+
required: true,
102+
minLength: 3,
103+
maxLength: 10,
104+
label: 'Password',
105+
})}
106+
/>
102107

103-
<TextInputWithError
104-
mode="outlined"
105-
{...fh.number('age', {
106-
required: true,
107-
minLength: 3,
108-
maxLength: 10,
109-
label: 'Age',
110-
})}
111-
/>
112-
<TextInputWithError
113-
mode="outlined"
114-
{...fh.decimal('money', {
115-
required: true,
116-
minLength: 3,
117-
maxLength: 10,
118-
label: 'Money bank account',
119-
})}
120-
/>
121-
<TextInputWithError
122-
mode="outlined"
123-
{...fh.text('organization.telephone', {
124-
required: true,
125-
minLength: 3,
126-
maxLength: 10,
127-
shouldFollowRegexes: [telephoneRegex],
128-
label: 'Organization telephone',
129-
})}
130-
/>
131-
<TextInputWithError
132-
mode="outlined"
133-
{...fh.number('organization.revenue', {
134-
required: true,
135-
minLength: 3,
136-
maxLength: 10,
137-
validate: (v) => {
138-
if (v < 10) {
139-
return 'revenue too low';
140-
}
141-
return undefined;
142-
},
143-
label: 'Organization revenue',
144-
})}
145-
/>
146-
<AddressEdit {...fh.raw('address')} />
147-
<AddressCompanyEdit {...fh.raw('address.company')} />
148-
<Button mode="contained" onPress={submit} style={{ marginTop: 24 }}>
149-
Save
150-
</Button>
151-
</Form>
108+
<TextInputWithError
109+
mode="outlined"
110+
{...fh.number('age', {
111+
required: true,
112+
minLength: 3,
113+
maxLength: 10,
114+
label: 'Age',
115+
})}
116+
/>
117+
<TextInputWithError
118+
mode="outlined"
119+
{...fh.decimal('money', {
120+
required: true,
121+
minLength: 3,
122+
maxLength: 10,
123+
label: 'Money bank account',
124+
})}
125+
/>
126+
<TextInputWithError
127+
mode="outlined"
128+
{...fh.text('organization.telephone', {
129+
required: true,
130+
minLength: 3,
131+
maxLength: 10,
132+
shouldFollowRegexes: [telephoneRegex],
133+
label: 'Organization telephone',
134+
})}
135+
/>
136+
<TextInputWithError
137+
mode="outlined"
138+
{...fh.number('organization.revenue', {
139+
required: true,
140+
minLength: 3,
141+
maxLength: 10,
142+
validate: (v) => {
143+
if (v < 10) {
144+
return 'revenue too low';
145+
}
146+
return undefined;
147+
},
148+
label: 'Organization revenue',
149+
})}
150+
/>
151+
<AddressEdit {...fh.raw('address')} />
152+
<AddressCompanyEdit {...fh.raw('address.company')} />
153+
<Button mode="contained" onPress={submit} style={{ marginTop: 24 }}>
154+
Save
155+
</Button>
156+
</Form>
157+
</View>
158+
</ScrollView>
152159
</View>
153160
);
154161
}
@@ -171,12 +178,12 @@ function AddressEdit({
171178
<Surface {...rest}>
172179
<Title>Nested form</Title>
173180
<Form {...formProps}>
174-
<TextInput
181+
<TextInputWithError
175182
mode="outlined"
176183
label="Street"
177184
{...fh.streetAddress('street', { required: true })}
178185
/>
179-
<TextInput
186+
<TextInputWithError
180187
mode="outlined"
181188
label="House number"
182189
{...fh.streetAddress('houseNumber')}
@@ -204,7 +211,11 @@ function AddressCompanyEdit({
204211
<Surface {...rest} style={{ padding: 12 }}>
205212
<Title>Nested form</Title>
206213
<Form {...formProps}>
207-
<TextInput mode="outlined" label="Street" {...fh.text('name')} />
214+
<TextInputWithError
215+
mode="outlined"
216+
label="Street"
217+
{...fh.text('name')}
218+
/>
208219
</Form>
209220
</Surface>
210221
);
@@ -228,8 +239,11 @@ function looksLikeMail(str: string): boolean {
228239
}
229240

230241
const styles = StyleSheet.create({
231-
root: {
242+
root: { flex: 1, maxHeight: Platform.OS === 'web' ? '100vh' : undefined },
243+
scrollView: {
232244
flex: 1,
245+
},
246+
inner: {
233247
marginTop: 100,
234248
marginLeft: 12,
235249
marginRight: 12,

example/src/TextInputWithError.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
import { HelperText, TextInput } from 'react-native-paper';
2+
import type { TextInputProps } from 'react-native';
23
import * as React from 'react';
34

4-
export default function TextInputWithError({
5-
errorMessage,
6-
...rest
7-
}: React.ComponentProps<typeof TextInput> & { errorMessage?: string }) {
5+
function TextInputWithError(
6+
{
7+
errorMessage,
8+
...rest
9+
}: TextInputProps & {
10+
errorMessage?: string;
11+
mode?: any;
12+
label?: string;
13+
error?: boolean;
14+
},
15+
ref: any
16+
) {
817
return (
918
<>
10-
<TextInput {...rest} />
19+
{/*// @ts-ignore*/}
20+
<TextInput {...rest} ref={ref} />
21+
{/*// @ts-ignore*/}
1122
<HelperText type="error" visible={rest.error}>
1223
{errorMessage || ' '}
1324
</HelperText>
1425
</>
1526
);
1627
}
28+
export default React.forwardRef(TextInputWithError);

example/tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"instantiationDepth": 500,
34
"jsx": "react-native",
45
"target": "esnext",
56
"lib": [
@@ -12,5 +13,6 @@
1213
"resolveJsonModule": true,
1314
"esModuleInterop": true,
1415
"moduleResolution": "node"
15-
}
16+
},
17+
"extends": "expo/tsconfig.base"
1618
}

0 commit comments

Comments
 (0)