Skip to content

Commit cddc2f2

Browse files
chrisbobbegnprice
authored andcommitted
RealmInputScreen: Make client-side validation errors clearer (UX and code)
Before this, we were using some React state for whether to show a line of red error text just below the input. We didn't unset it at the moment the error condition went away, which could be confusing. Also, the on-submit validation logic disagreed with the logic for whether the button was disabled: the former counted an email address as invalid, while the latter didn't. Also, when the submit button *was* disabled -- the input value was a URL that doesn't parse -- you wouldn't get the validation message when you pressed it, because we weren't passing isPressHandledWhenDisabled to the button. You could only get the message to appear by triggering the TextInput's onSubmitEditing, e.g. by pressing the go/enter button on a software keyboard. Instead, do as we do for other validated inputs, like in the compose box: on every render, track a validation error (if any) and use it for the submit button's disabled state and its on-press callback, with isPressHandledWhenDisabled.
1 parent 6264ddf commit cddc2f2

File tree

2 files changed

+39
-20
lines changed

2 files changed

+39
-20
lines changed

src/start/RealmInputScreen.js

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { useFocusEffect } from '@react-navigation/native';
77
import type { RouteProp } from '../react-navigation';
88
import type { AppNavigationProp } from '../nav/AppNavigator';
99
import type { ServerSettings } from '../api/settings/getServerSettings';
10-
import ErrorMsg from '../common/ErrorMsg';
1110
import ZulipTextIntl from '../common/ZulipTextIntl';
1211
import Screen from '../common/Screen';
1312
import ZulipButton from '../common/ZulipButton';
@@ -17,18 +16,46 @@ import { ThemeContext } from '../styles/theme';
1716
import { createStyleSheet, HALF_COLOR } from '../styles';
1817
import { showErrorAlert } from '../utils/info';
1918
import { TranslationContext } from '../boot/TranslationProvider';
19+
import type { LocalizableText } from '../types';
2020

2121
type Props = $ReadOnly<{|
2222
navigation: AppNavigationProp<'realm-input'>,
2323
route: RouteProp<'realm-input', {| initial: boolean | void |}>,
2424
|}>;
2525

26-
const urlFromInputValue = (realmInputValue: string): URL | void => {
26+
enum ValidationError {
27+
InvalidUrl = 0,
28+
NoUseEmail = 1,
29+
}
30+
31+
function validationErrorMsg(validationError: ValidationError): LocalizableText {
32+
switch (validationError) {
33+
case ValidationError.InvalidUrl:
34+
return 'Please enter a valid URL.';
35+
case ValidationError.NoUseEmail:
36+
return 'Please enter the server URL, not your email.';
37+
}
38+
}
39+
40+
type MaybeParsedInput =
41+
| {| +valid: true, value: URL |}
42+
| {| +valid: false, error: ValidationError |};
43+
44+
const tryParseInput = (realmInputValue: string): MaybeParsedInput => {
2745
const withScheme = /^https?:\/\//.test(realmInputValue)
2846
? realmInputValue
2947
: `https://${realmInputValue}`;
3048

31-
return tryParseUrl(withScheme);
49+
const url = tryParseUrl(withScheme);
50+
51+
if (!url) {
52+
return { valid: false, error: ValidationError.InvalidUrl };
53+
}
54+
if (url.username !== '') {
55+
return { valid: false, error: ValidationError.NoUseEmail };
56+
}
57+
58+
return { valid: true, value: url };
3259
};
3360

3461
export default function RealmInputScreen(props: Props): Node {
@@ -39,8 +66,7 @@ export default function RealmInputScreen(props: Props): Node {
3966

4067
const [progress, setProgress] = React.useState(false);
4168
const [realmInputValue, setRealmInputValue] = React.useState('');
42-
const parsedRealm = urlFromInputValue(realmInputValue);
43-
const [validationErrorMsg, setError] = React.useState(null);
69+
const maybeParsedInput = tryParseInput(realmInputValue);
4470

4571
const textInputRef = React.useRef<React$ElementRef<typeof TextInput> | null>(null);
4672

@@ -63,19 +89,14 @@ export default function RealmInputScreen(props: Props): Node {
6389
);
6490

6591
const tryRealm = React.useCallback(async () => {
66-
if (!parsedRealm) {
67-
setError('Please enter a valid URL.');
68-
return;
69-
}
70-
if (parsedRealm.username !== '') {
71-
setError('Please enter the server URL, not your email.');
92+
if (!maybeParsedInput.valid) {
93+
showErrorAlert(_('Invalid input'), _(validationErrorMsg(maybeParsedInput.error)));
7294
return;
7395
}
7496

7597
setProgress(true);
76-
setError(null);
7798
try {
78-
const serverSettings: ServerSettings = await api.getServerSettings(parsedRealm);
99+
const serverSettings: ServerSettings = await api.getServerSettings(maybeParsedInput.value);
79100
navigation.push('auth', { serverSettings });
80101
Keyboard.dismiss();
81102
} catch (errorIllTyped) {
@@ -88,7 +109,7 @@ export default function RealmInputScreen(props: Props): Node {
88109
} finally {
89110
setProgress(false);
90111
}
91-
}, [navigation, parsedRealm, _]);
112+
}, [navigation, maybeParsedInput, _]);
92113

93114
const styles = React.useMemo(
94115
() =>
@@ -141,17 +162,14 @@ export default function RealmInputScreen(props: Props): Node {
141162
ref={textInputRef}
142163
/>
143164
</View>
144-
{validationErrorMsg !== null ? (
145-
<ErrorMsg error={validationErrorMsg} />
146-
) : (
147-
<ZulipTextIntl text="e.g. zulip.example.com" style={styles.hintText} />
148-
)}
165+
<ZulipTextIntl text="e.g. zulip.example.com" style={styles.hintText} />
149166
<ZulipButton
150167
style={styles.button}
151168
text="Enter"
152169
progress={progress}
153170
onPress={tryRealm}
154-
disabled={parsedRealm === undefined}
171+
isPressHandledWhenDisabled
172+
disabled={!maybeParsedInput.valid}
155173
/>
156174
</Screen>
157175
);

static/translations/messages_en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
"Request timed out.": "Request timed out.",
123123
"Chat": "Chat",
124124
"Sign in with {method}": "Sign in with {method}",
125+
"Invalid input": "Invalid input",
125126
"Cannot connect to server.": "Cannot connect to server.",
126127
"Please enter a valid URL.": "Please enter a valid URL.",
127128
"Please enter the server URL, not your email.": "Please enter the server URL, not your email.",

0 commit comments

Comments
 (0)