Skip to content

Commit

Permalink
Improve textinput performance in login and account creation (#4673)
Browse files Browse the repository at this point in the history
* Change login form to use uncontrolled inputs

* Debounce state updates in account creation to reduce flicker

* Refactor state-control of account creation forms to fix perf without relying on debounces

* Remove canNext and enforce is13

* Re-add live validation to signup form (#4720)

* Update validation in real time

* Disable on invalid

* Clear server error on typing

* Remove unnecessary clearing of error

---------

Co-authored-by: Dan Abramov <[email protected]>
  • Loading branch information
pfrazee and gaearon authored Jul 2, 2024
1 parent 4bb4452 commit 63bb8fd
Show file tree
Hide file tree
Showing 7 changed files with 357 additions and 269 deletions.
60 changes: 40 additions & 20 deletions src/screens/Login/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ export const LoginForm = ({
const {track} = useAnalytics()
const t = useTheme()
const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [isReady, setIsReady] = useState<boolean>(false)
const [isAuthFactorTokenNeeded, setIsAuthFactorTokenNeeded] =
useState<boolean>(false)
const [identifier, setIdentifier] = useState<string>(initialHandle)
const [password, setPassword] = useState<string>('')
const [authFactorToken, setAuthFactorToken] = useState<string>('')
const passwordInputRef = useRef<TextInput>(null)
const identifierValueRef = useRef<string>(initialHandle || '')
const passwordValueRef = useRef<string>('')
const authFactorTokenValueRef = useRef<string>('')
const passwordRef = useRef<TextInput>(null)
const {_} = useLingui()
const {login} = useSessionApi()
const requestNotificationsPermission = useRequestNotificationsPermission()
Expand All @@ -84,6 +85,10 @@ export const LoginForm = ({
setError('')
setIsProcessing(true)

const identifier = identifierValueRef.current.toLowerCase().trim()
const password = passwordValueRef.current
const authFactorToken = authFactorTokenValueRef.current

try {
// try to guess the handle if the user just gave their own username
let fullIdent = identifier
Expand Down Expand Up @@ -152,7 +157,22 @@ export const LoginForm = ({
}
}

const isReady = !!serviceDescription && !!identifier && !!password
const checkIsReady = () => {
if (
!!serviceDescription &&
!!identifierValueRef.current &&
!!passwordValueRef.current
) {
if (!isReady) {
setIsReady(true)
}
} else {
if (isReady) {
setIsReady(false)
}
}
}

return (
<FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}>
<View>
Expand Down Expand Up @@ -181,14 +201,15 @@ export const LoginForm = ({
autoComplete="username"
returnKeyType="next"
textContentType="username"
defaultValue={initialHandle || ''}
onChangeText={v => {
identifierValueRef.current = v
checkIsReady()
}}
onSubmitEditing={() => {
passwordInputRef.current?.focus()
passwordRef.current?.focus()
}}
blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field
value={identifier}
onChangeText={str =>
setIdentifier((str || '').toLowerCase().trim())
}
editable={!isProcessing}
accessibilityHint={_(
msg`Input the username or email address you used at signup`,
Expand All @@ -200,7 +221,7 @@ export const LoginForm = ({
<TextField.Icon icon={Lock} />
<TextField.Input
testID="loginPasswordInput"
inputRef={passwordInputRef}
inputRef={passwordRef}
label={_(msg`Password`)}
autoCapitalize="none"
autoCorrect={false}
Expand All @@ -210,16 +231,14 @@ export const LoginForm = ({
secureTextEntry={true}
textContentType="password"
clearButtonMode="while-editing"
value={password}
onChangeText={setPassword}
onChangeText={v => {
passwordValueRef.current = v
checkIsReady()
}}
onSubmitEditing={onPressNext}
blurOnSubmit={false} // HACK: https://github.com/facebook/react-native/issues/21911#issuecomment-558343069 Keyboard blur behavior is now handled in onSubmitEditing
editable={!isProcessing}
accessibilityHint={
identifier === ''
? _(msg`Input your password`)
: _(msg`Input the password tied to ${identifier}`)
}
accessibilityHint={_(msg`Input your password`)}
/>
<Button
testID="forgotPasswordButton"
Expand Down Expand Up @@ -258,8 +277,9 @@ export const LoginForm = ({
returnKeyType="done"
textContentType="username"
blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field
value={authFactorToken}
onChangeText={setAuthFactorToken}
onChangeText={v => {
authFactorTokenValueRef.current = v
}}
onSubmitEditing={onPressNext}
editable={!isProcessing}
accessibilityHint={_(
Expand Down
73 changes: 73 additions & 0 deletions src/screens/Signup/BackNextButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Loader} from '#/components/Loader'

export interface BackNextButtonsProps {
hideNext?: boolean
showRetry?: boolean
isLoading: boolean
isNextDisabled?: boolean
onBackPress: () => void
onNextPress?: () => void
onRetryPress?: () => void
}

export function BackNextButtons({
hideNext,
showRetry,
isLoading,
isNextDisabled,
onBackPress,
onNextPress,
onRetryPress,
}: BackNextButtonsProps) {
const {_} = useLingui()

return (
<View style={[a.flex_row, a.justify_between, a.pb_lg, a.pt_3xl]}>
<Button
label={_(msg`Go back to previous step`)}
variant="solid"
color="secondary"
size="medium"
onPress={onBackPress}>
<ButtonText>
<Trans>Back</Trans>
</ButtonText>
</Button>
{!hideNext &&
(showRetry ? (
<Button
label={_(msg`Press to retry`)}
variant="solid"
color="primary"
size="medium"
onPress={onRetryPress}>
<ButtonText>
<Trans>Retry</Trans>
</ButtonText>
{isLoading && <ButtonIcon icon={Loader} />}
</Button>
) : (
<Button
testID="nextBtn"
label={_(msg`Continue to next step`)}
variant="solid"
color="primary"
size="medium"
disabled={isLoading || isNextDisabled}
onPress={onNextPress}>
<ButtonText>
<Trans>Next</Trans>
</ButtonText>
{isLoading && <ButtonIcon icon={Loader} />}
</Button>
))}
</View>
)
}
16 changes: 16 additions & 0 deletions src/screens/Signup/StepCaptcha/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state'
import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView'
import {atoms as a, useTheme} from '#/alf'
import {FormError} from '#/components/forms/FormError'
import {BackNextButtons} from '../BackNextButtons'

const CAPTCHA_PATH = '/gate/signup'

Expand Down Expand Up @@ -61,6 +62,16 @@ export function StepCaptcha() {
[_, dispatch, state.handle],
)

const onBackPress = React.useCallback(() => {
logger.error('Signup Flow Error', {
errorMessage:
'User went back from captcha step. Possibly encountered an error.',
registrationHandle: state.handle,
})

dispatch({type: 'prev'})
}, [dispatch, state.handle])

return (
<ScreenTransition>
<View style={[a.gap_lg]}>
Expand All @@ -86,6 +97,11 @@ export function StepCaptcha() {
</View>
<FormError error={state.error} />
</View>
<BackNextButtons
hideNext
isLoading={state.isLoading}
onBackPress={onBackPress}
/>
</ScreenTransition>
)
}
Loading

0 comments on commit 63bb8fd

Please sign in to comment.