From 3f787a474e61889d1798d9a6626b1f94b6bef9dd Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 23 Nov 2022 11:54:30 -0800 Subject: [PATCH 1/9] RealmInputScreen [nfc]: Convert to a function component --- src/start/RealmInputScreen.js | 126 ++++++++++++++++------------------ 1 file changed, 59 insertions(+), 67 deletions(-) diff --git a/src/start/RealmInputScreen.js b/src/start/RealmInputScreen.js index 61205495136..88d08318ff8 100644 --- a/src/start/RealmInputScreen.js +++ b/src/start/RealmInputScreen.js @@ -1,5 +1,5 @@ /* @flow strict-local */ -import React, { PureComponent } from 'react'; +import React, { useCallback } from 'react'; import type { Node } from 'react'; import { Keyboard } from 'react-native'; @@ -13,18 +13,13 @@ import Screen from '../common/Screen'; import ZulipButton from '../common/ZulipButton'; import { tryParseUrl } from '../utils/url'; import * as api from '../api'; +import { createStyleSheet } from '../styles'; type Props = $ReadOnly<{| navigation: AppNavigationProp<'realm-input'>, route: RouteProp<'realm-input', {| initial: boolean | void |}>, |}>; -type State = {| - realmInputValue: string, - error: string | null, - progress: boolean, -|}; - const urlFromInputValue = (realmInputValue: string): URL | void => { const withScheme = /^https?:\/\//.test(realmInputValue) ? realmInputValue @@ -33,88 +28,85 @@ const urlFromInputValue = (realmInputValue: string): URL | void => { return tryParseUrl(withScheme); }; -export default class RealmInputScreen extends PureComponent { - state: State = { - progress: false, - realmInputValue: '', - error: null, - }; +export default function RealmInputScreen(props: Props): Node { + const { navigation, route } = props; - tryRealm: () => Promise = async () => { - const { realmInputValue } = this.state; + const [progress, setProgress] = React.useState(false); + const [realmInputValue, setRealmInputValue] = React.useState(''); + const [error, setError] = React.useState(null); + const tryRealm = React.useCallback(async () => { const parsedRealm = urlFromInputValue(realmInputValue); if (!parsedRealm) { - this.setState({ error: 'Please enter a valid URL' }); + setError('Please enter a valid URL'); return; } if (parsedRealm.username !== '') { - this.setState({ error: 'Please enter the server URL, not your email' }); + setError('Please enter the server URL, not your email'); return; } - this.setState({ - progress: true, - error: null, - }); + setProgress(true); + setError(null); try { const serverSettings: ApiResponseServerSettings = await api.getServerSettings(parsedRealm); - this.props.navigation.push('auth', { serverSettings }); + navigation.push('auth', { serverSettings }); Keyboard.dismiss(); } catch (errorIllTyped) { const err: mixed = errorIllTyped; // https://github.com/facebook/flow/issues/2470 - this.setState({ error: 'Cannot connect to server' }); + setError('Cannot connect to server'); /* eslint-disable no-console */ console.warn('RealmInputScreen: failed to connect to server:', err); // $FlowFixMe[incompatible-cast]: assuming caught exception was Error console.warn((err: Error).stack); } finally { - this.setState({ progress: false }); + setProgress(false); } - }; - - handleRealmChange: string => void = value => this.setState({ realmInputValue: value }); + }, [navigation, realmInputValue]); - render(): Node { - const { navigation } = this.props; - const { progress, error, realmInputValue } = this.state; + const handleRealmChange = useCallback(value => { + setRealmInputValue(value); + }, []); - const styles = { - input: { marginTop: 16, marginBottom: 8 }, - hintText: { paddingLeft: 2, fontSize: 12 }, - button: { marginTop: 8 }, - }; + const styles = React.useMemo( + () => + createStyleSheet({ + input: { marginTop: 16, marginBottom: 8 }, + hintText: { paddingLeft: 2, fontSize: 12 }, + button: { marginTop: 8 }, + }), + [], + ); - return ( - - - - {error !== null ? ( - - ) : ( - - )} - - - ); - } + return ( + + + + {error !== null ? ( + + ) : ( + + )} + + + ); } From e757907b27129a572827d040e2951ab230e6e098 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 23 Nov 2022 12:08:57 -0800 Subject: [PATCH 2/9] =?UTF-8?q?SmartUrlInput=20[nfc]:=20Remove=20unneeded?= =?UTF-8?q?=20props=20navigation=20and=20enablesReturnKey=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This and the next several commits prepare to inline the definition of SmartUrlInput at its only caller. --- src/common/SmartUrlInput.js | 11 ++--------- src/start/RealmInputScreen.js | 2 -- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/common/SmartUrlInput.js b/src/common/SmartUrlInput.js index daab8bbf989..3a55c58e01a 100644 --- a/src/common/SmartUrlInput.js +++ b/src/common/SmartUrlInput.js @@ -5,7 +5,6 @@ import { TextInput, View } from 'react-native'; import { useFocusEffect } from '@react-navigation/native'; import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet'; -import type { AppNavigationProp } from '../nav/AppNavigator'; import { ThemeContext, createStyleSheet, HALF_COLOR } from '../styles'; const styles = createStyleSheet({ @@ -21,19 +20,13 @@ const styles = createStyleSheet({ }); type Props = $ReadOnly<{| - // TODO: Currently this type is acceptable because the only - // `navigation` prop we pass to a `SmartUrlInput` instance is the - // one from a component on AppNavigator. - navigation: AppNavigationProp<>, - style?: ViewStyleProp, onChangeText: (value: string) => void, onSubmitEditing: () => Promise, - enablesReturnKeyAutomatically: boolean, |}>; export default function SmartUrlInput(props: Props): Node { - const { style, onChangeText, onSubmitEditing, enablesReturnKeyAutomatically } = props; + const { style, onChangeText, onSubmitEditing } = props; // We should replace the fixme with // `React$ElementRef` when we can. Currently, that @@ -89,7 +82,7 @@ export default function SmartUrlInput(props: Props): Node { keyboardType="url" underlineColorAndroid="transparent" onSubmitEditing={onSubmitEditing} - enablesReturnKeyAutomatically={enablesReturnKeyAutomatically} + enablesReturnKeyAutomatically ref={textInputRef} /> diff --git a/src/start/RealmInputScreen.js b/src/start/RealmInputScreen.js index 88d08318ff8..20b0e3f0fe7 100644 --- a/src/start/RealmInputScreen.js +++ b/src/start/RealmInputScreen.js @@ -90,10 +90,8 @@ export default function RealmInputScreen(props: Props): Node { {error !== null ? ( From 06a970a186c9f27721cf052804551ab32ef61354 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 23 Nov 2022 12:18:38 -0800 Subject: [PATCH 3/9] SmartUrlInput [nfc]: Remove `style` prop --- src/common/SmartUrlInput.js | 8 ++++---- src/start/RealmInputScreen.js | 7 +------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/common/SmartUrlInput.js b/src/common/SmartUrlInput.js index 3a55c58e01a..b34f84f0175 100644 --- a/src/common/SmartUrlInput.js +++ b/src/common/SmartUrlInput.js @@ -3,7 +3,6 @@ import React, { useState, useRef, useCallback, useContext } from 'react'; import type { Node } from 'react'; import { TextInput, View } from 'react-native'; import { useFocusEffect } from '@react-navigation/native'; -import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet'; import { ThemeContext, createStyleSheet, HALF_COLOR } from '../styles'; @@ -11,6 +10,8 @@ const styles = createStyleSheet({ wrapper: { flexDirection: 'row', opacity: 0.8, + marginTop: 16, + marginBottom: 8, }, realmInput: { flex: 1, @@ -20,13 +21,12 @@ const styles = createStyleSheet({ }); type Props = $ReadOnly<{| - style?: ViewStyleProp, onChangeText: (value: string) => void, onSubmitEditing: () => Promise, |}>; export default function SmartUrlInput(props: Props): Node { - const { style, onChangeText, onSubmitEditing } = props; + const { onChangeText, onSubmitEditing } = props; // We should replace the fixme with // `React$ElementRef` when we can. Currently, that @@ -67,7 +67,7 @@ export default function SmartUrlInput(props: Props): Node { ); return ( - + createStyleSheet({ - input: { marginTop: 16, marginBottom: 8 }, hintText: { paddingLeft: 2, fontSize: 12 }, button: { marginTop: 8 }, }), @@ -88,11 +87,7 @@ export default function RealmInputScreen(props: Props): Node { shouldShowLoadingBanner={false} > - + {error !== null ? ( ) : ( From 1226e1b4d0a9471dea1bcef47f34e8b87f60e38e Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 23 Nov 2022 12:11:59 -0800 Subject: [PATCH 4/9] SmartUrlInput [nfc]: Fix type-checking of ref --- src/common/SmartUrlInput.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/common/SmartUrlInput.js b/src/common/SmartUrlInput.js index b34f84f0175..fe0a425e7ba 100644 --- a/src/common/SmartUrlInput.js +++ b/src/common/SmartUrlInput.js @@ -1,5 +1,5 @@ /* @flow strict-local */ -import React, { useState, useRef, useCallback, useContext } from 'react'; +import React, { useState, useCallback, useContext } from 'react'; import type { Node } from 'react'; import { TextInput, View } from 'react-native'; import { useFocusEffect } from '@react-navigation/native'; @@ -28,11 +28,7 @@ type Props = $ReadOnly<{| export default function SmartUrlInput(props: Props): Node { const { onChangeText, onSubmitEditing } = props; - // We should replace the fixme with - // `React$ElementRef` when we can. Currently, that - // would make `.current` be `any(implicit)`, which we don't want; - // this is probably down to bugs in Flow's special support for React. - const textInputRef = useRef<$FlowFixMe>(); + const textInputRef = React.useRef | null>(null); const [value, setValue] = useState(''); @@ -51,8 +47,6 @@ export default function SmartUrlInput(props: Props): Node { // screen, sometimes the keyboard flicks open then closed, instead // of just opening. Shrug. See // https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/realm-input/near/1346690 - // - // `.current` is not type-checked; see definition. textInputRef.current.focus(); } }, []), From 579871760a63fe0b20f4cdba8122595cf1559b65 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 23 Nov 2022 12:15:58 -0800 Subject: [PATCH 5/9] SmartUrlInput [nfc]: Make value controlled by caller --- src/common/SmartUrlInput.js | 17 ++++------------- src/start/RealmInputScreen.js | 6 +++++- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/common/SmartUrlInput.js b/src/common/SmartUrlInput.js index fe0a425e7ba..421c68dc8f0 100644 --- a/src/common/SmartUrlInput.js +++ b/src/common/SmartUrlInput.js @@ -1,5 +1,5 @@ /* @flow strict-local */ -import React, { useState, useCallback, useContext } from 'react'; +import React, { useCallback, useContext } from 'react'; import type { Node } from 'react'; import { TextInput, View } from 'react-native'; import { useFocusEffect } from '@react-navigation/native'; @@ -22,16 +22,15 @@ const styles = createStyleSheet({ type Props = $ReadOnly<{| onChangeText: (value: string) => void, + value: string, onSubmitEditing: () => Promise, |}>; export default function SmartUrlInput(props: Props): Node { - const { onChangeText, onSubmitEditing } = props; + const { onChangeText, value, onSubmitEditing } = props; const textInputRef = React.useRef | null>(null); - const [value, setValue] = useState(''); - const themeContext = useContext(ThemeContext); // When the route is focused in the navigation, focus the input. @@ -52,14 +51,6 @@ export default function SmartUrlInput(props: Props): Node { }, []), ); - const handleChange = useCallback( - (_value: string) => { - setValue(_value); - onChangeText(_value); - }, - [onChangeText], - ); - return ( - + {error !== null ? ( ) : ( From 52cb05f9b5dce6c2fe413ec4d39826764e9cc287 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 23 Nov 2022 12:21:54 -0800 Subject: [PATCH 6/9] SmartUrlInput [nfc]: Use React.useMemo for styles --- src/common/SmartUrlInput.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/common/SmartUrlInput.js b/src/common/SmartUrlInput.js index 421c68dc8f0..1136a26f012 100644 --- a/src/common/SmartUrlInput.js +++ b/src/common/SmartUrlInput.js @@ -6,20 +6,6 @@ import { useFocusEffect } from '@react-navigation/native'; import { ThemeContext, createStyleSheet, HALF_COLOR } from '../styles'; -const styles = createStyleSheet({ - wrapper: { - flexDirection: 'row', - opacity: 0.8, - marginTop: 16, - marginBottom: 8, - }, - realmInput: { - flex: 1, - padding: 0, - fontSize: 20, - }, -}); - type Props = $ReadOnly<{| onChangeText: (value: string) => void, value: string, @@ -51,13 +37,32 @@ export default function SmartUrlInput(props: Props): Node { }, []), ); + const styles = React.useMemo( + () => + createStyleSheet({ + wrapper: { + flexDirection: 'row', + opacity: 0.8, + marginTop: 16, + marginBottom: 8, + }, + realmInput: { + flex: 1, + padding: 0, + fontSize: 20, + color: themeContext.color, + }, + }), + [themeContext], + ); + return ( Date: Wed, 23 Nov 2022 12:30:15 -0800 Subject: [PATCH 7/9] SmartUrlInput [nfc]: Rename styles --- src/common/SmartUrlInput.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/SmartUrlInput.js b/src/common/SmartUrlInput.js index 1136a26f012..d4ca1f87d0b 100644 --- a/src/common/SmartUrlInput.js +++ b/src/common/SmartUrlInput.js @@ -40,13 +40,13 @@ export default function SmartUrlInput(props: Props): Node { const styles = React.useMemo( () => createStyleSheet({ - wrapper: { + inputWrapper: { flexDirection: 'row', opacity: 0.8, marginTop: 16, marginBottom: 8, }, - realmInput: { + input: { flex: 1, padding: 0, fontSize: 20, @@ -57,12 +57,12 @@ export default function SmartUrlInput(props: Props): Node { ); return ( - + Date: Wed, 23 Nov 2022 12:27:49 -0800 Subject: [PATCH 8/9] RealmInputScreen [nfc]: Inline SmartUrlInput --- src/common/SmartUrlInput.js | 80 ----------------------------------- src/start/RealmInputScreen.js | 67 +++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 89 deletions(-) delete mode 100644 src/common/SmartUrlInput.js diff --git a/src/common/SmartUrlInput.js b/src/common/SmartUrlInput.js deleted file mode 100644 index d4ca1f87d0b..00000000000 --- a/src/common/SmartUrlInput.js +++ /dev/null @@ -1,80 +0,0 @@ -/* @flow strict-local */ -import React, { useCallback, useContext } from 'react'; -import type { Node } from 'react'; -import { TextInput, View } from 'react-native'; -import { useFocusEffect } from '@react-navigation/native'; - -import { ThemeContext, createStyleSheet, HALF_COLOR } from '../styles'; - -type Props = $ReadOnly<{| - onChangeText: (value: string) => void, - value: string, - onSubmitEditing: () => Promise, -|}>; - -export default function SmartUrlInput(props: Props): Node { - const { onChangeText, value, onSubmitEditing } = props; - - const textInputRef = React.useRef | null>(null); - - const themeContext = useContext(ThemeContext); - - // When the route is focused in the navigation, focus the input. - // Otherwise, if you go back to this screen from the auth screen, the - // input won't be focused. - useFocusEffect( - useCallback(() => { - if (textInputRef.current) { - // Sometimes the effect of this `.focus()` is immediately undone - // (the keyboard is closed) by a Keyboard.dismiss() from React - // Navigation's internals. Seems like a complex bug, but the symptom - // isn't terrible, it just means that on back-navigating to this - // screen, sometimes the keyboard flicks open then closed, instead - // of just opening. Shrug. See - // https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/realm-input/near/1346690 - textInputRef.current.focus(); - } - }, []), - ); - - const styles = React.useMemo( - () => - createStyleSheet({ - inputWrapper: { - flexDirection: 'row', - opacity: 0.8, - marginTop: 16, - marginBottom: 8, - }, - input: { - flex: 1, - padding: 0, - fontSize: 20, - color: themeContext.color, - }, - }), - [themeContext], - ); - - return ( - - - - ); -} diff --git a/src/start/RealmInputScreen.js b/src/start/RealmInputScreen.js index 295ca089060..30e9d4378e8 100644 --- a/src/start/RealmInputScreen.js +++ b/src/start/RealmInputScreen.js @@ -1,19 +1,20 @@ /* @flow strict-local */ import React, { useCallback } from 'react'; import type { Node } from 'react'; -import { Keyboard } from 'react-native'; +import { Keyboard, View, TextInput } from 'react-native'; +import { useFocusEffect } from '@react-navigation/native'; import type { RouteProp } from '../react-navigation'; import type { AppNavigationProp } from '../nav/AppNavigator'; import type { ApiResponseServerSettings } from '../api/settings/getServerSettings'; import ErrorMsg from '../common/ErrorMsg'; import ZulipTextIntl from '../common/ZulipTextIntl'; -import SmartUrlInput from '../common/SmartUrlInput'; import Screen from '../common/Screen'; import ZulipButton from '../common/ZulipButton'; import { tryParseUrl } from '../utils/url'; import * as api from '../api'; -import { createStyleSheet } from '../styles'; +import { ThemeContext } from '../styles/theme'; +import { createStyleSheet, HALF_COLOR } from '../styles'; type Props = $ReadOnly<{| navigation: AppNavigationProp<'realm-input'>, @@ -31,10 +32,32 @@ const urlFromInputValue = (realmInputValue: string): URL | void => { export default function RealmInputScreen(props: Props): Node { const { navigation, route } = props; + const themeContext = React.useContext(ThemeContext); + const [progress, setProgress] = React.useState(false); const [realmInputValue, setRealmInputValue] = React.useState(''); const [error, setError] = React.useState(null); + const textInputRef = React.useRef | null>(null); + + // When the route is focused in the navigation, focus the input. + // Otherwise, if you go back to this screen from the auth screen, the + // input won't be focused. + useFocusEffect( + useCallback(() => { + if (textInputRef.current) { + // Sometimes the effect of this `.focus()` is immediately undone + // (the keyboard is closed) by a Keyboard.dismiss() from React + // Navigation's internals. Seems like a complex bug, but the symptom + // isn't terrible, it just means that on back-navigating to this + // screen, sometimes the keyboard flicks open then closed, instead + // of just opening. Shrug. See + // https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/realm-input/near/1346690 + textInputRef.current.focus(); + } + }, []), + ); + const tryRealm = React.useCallback(async () => { const parsedRealm = urlFromInputValue(realmInputValue); if (!parsedRealm) { @@ -71,10 +94,22 @@ export default function RealmInputScreen(props: Props): Node { const styles = React.useMemo( () => createStyleSheet({ + inputWrapper: { + flexDirection: 'row', + opacity: 0.8, + marginTop: 16, + marginBottom: 8, + }, + input: { + flex: 1, + padding: 0, + fontSize: 20, + color: themeContext.color, + }, hintText: { paddingLeft: 2, fontSize: 12 }, button: { marginTop: 8 }, }), - [], + [themeContext], ); return ( @@ -87,11 +122,25 @@ export default function RealmInputScreen(props: Props): Node { shouldShowLoadingBanner={false} > - + + + {error !== null ? ( ) : ( From c93923ed7b209bedeb9fa748f6bc94f0c504ad46 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Thu, 1 Dec 2022 21:44:25 -0800 Subject: [PATCH 9/9] RealmInputScreen [nfc]: Inline a very thin wrapper --- src/start/RealmInputScreen.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/start/RealmInputScreen.js b/src/start/RealmInputScreen.js index 30e9d4378e8..2189486160a 100644 --- a/src/start/RealmInputScreen.js +++ b/src/start/RealmInputScreen.js @@ -87,10 +87,6 @@ export default function RealmInputScreen(props: Props): Node { } }, [navigation, realmInputValue]); - const handleRealmChange = useCallback(value => { - setRealmInputValue(value); - }, []); - const styles = React.useMemo( () => createStyleSheet({ @@ -132,7 +128,7 @@ export default function RealmInputScreen(props: Props): Node { autoCorrect={false} autoCapitalize="none" returnKeyType="go" - onChangeText={handleRealmChange} + onChangeText={setRealmInputValue} blurOnSubmit={false} keyboardType="url" underlineColorAndroid="transparent"