From 63bb8fda2d28e11d7e60808e1e86384d48ec1b47 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 2 Jul 2024 14:43:34 -0700 Subject: Improve textinput performance in login and account creation (#4673) * 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 --- src/screens/Signup/StepHandle.tsx | 218 ++++++++++++++++++++++++-------------- 1 file changed, 138 insertions(+), 80 deletions(-) (limited to 'src/screens/Signup/StepHandle.tsx') diff --git a/src/screens/Signup/StepHandle.tsx b/src/screens/Signup/StepHandle.tsx index 2266f4387..b443e822a 100644 --- a/src/screens/Signup/StepHandle.tsx +++ b/src/screens/Signup/StepHandle.tsx @@ -1,56 +1,96 @@ -import React from 'react' +import React, {useRef} from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' -import { - createFullHandle, - IsValidHandle, - validateHandle, -} from '#/lib/strings/handles' +import {logEvent} from '#/lib/statsig/statsig' +import {createFullHandle, validateHandle} from '#/lib/strings/handles' +import {useAgent} from '#/state/session' import {ScreenTransition} from '#/screens/Login/ScreenTransition' -import {useSignupContext} from '#/screens/Signup/state' +import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' import {atoms as a, useTheme} from '#/alf' import * as TextField from '#/components/forms/TextField' import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' import {Text} from '#/components/Typography' +import {BackNextButtons} from './BackNextButtons' export function StepHandle() { const {_} = useLingui() const t = useTheme() const {state, dispatch} = useSignupContext() + const submit = useSubmitSignup({state, dispatch}) + const agent = useAgent() + const handleValueRef = useRef(state.handle) + const [draftValue, setDraftValue] = React.useState(state.handle) - const [validCheck, setValidCheck] = React.useState({ - handleChars: false, - hyphenStartOrEnd: false, - frontLength: false, - totalLength: true, - overall: false, - }) + const onNextPress = React.useCallback(async () => { + const handle = handleValueRef.current.trim() + dispatch({ + type: 'setHandle', + value: handle, + }) - useFocusEffect( - React.useCallback(() => { - setValidCheck(validateHandle(state.handle, state.userDomain)) - }, [state.handle, state.userDomain]), - ) + const newValidCheck = validateHandle(handle, state.userDomain) + if (!newValidCheck.overall) { + return + } - const onHandleChange = React.useCallback( - (value: string) => { - if (state.error) { - dispatch({type: 'setError', value: ''}) - } + try { + dispatch({type: 'setIsLoading', value: true}) - dispatch({ - type: 'setHandle', - value, + const res = await agent.resolveHandle({ + handle: createFullHandle(handle, state.userDomain), }) - }, - [dispatch, state.error], - ) + if (res.data.did) { + dispatch({ + type: 'setError', + value: _(msg`That handle is already taken.`), + }) + return + } + } catch (e) { + // Don't have to handle + } finally { + dispatch({type: 'setIsLoading', value: false}) + } + + logEvent('signup:nextPressed', { + activeStep: state.activeStep, + phoneVerificationRequired: + state.serviceDescription?.phoneVerificationRequired, + }) + // phoneVerificationRequired is actually whether a captcha is required + if (!state.serviceDescription?.phoneVerificationRequired) { + submit() + return + } + dispatch({type: 'next'}) + }, [ + _, + dispatch, + state.activeStep, + state.serviceDescription?.phoneVerificationRequired, + state.userDomain, + submit, + agent, + ]) + + const onBackPress = React.useCallback(() => { + const handle = handleValueRef.current.trim() + dispatch({ + type: 'setHandle', + value: handle, + }) + dispatch({type: 'prev'}) + logEvent('signup:backPressed', { + activeStep: state.activeStep, + }) + }, [dispatch, state.activeStep]) + + const validCheck = validateHandle(draftValue, state.userDomain) return ( @@ -59,9 +99,17 @@ export function StepHandle() { { + if (state.error) { + dispatch({type: 'setError', value: ''}) + } + + // These need to always be in sync. + handleValueRef.current = val + setDraftValue(val) + }} label={_(msg`Input your user handle`)} - defaultValue={state.handle} + defaultValue={draftValue} autoCapitalize="none" autoCorrect={false} autoFocus @@ -69,59 +117,69 @@ export function StepHandle() { /> - - Your full handle will be{' '} - - @{createFullHandle(state.handle, state.userDomain)} + {draftValue !== '' && ( + + Your full handle will be{' '} + + @{createFullHandle(draftValue, state.userDomain)} + - + )} - - {state.error ? ( - - - {state.error} - - ) : undefined} - {validCheck.hyphenStartOrEnd ? ( - - - - Only contains letters, numbers, and hyphens - - - ) : ( - - - - Doesn't begin or end with a hyphen - - - )} - - - {!validCheck.totalLength ? ( - - No longer than 253 characters - + {draftValue !== '' && ( + + {state.error ? ( + + + {state.error} + + ) : undefined} + {validCheck.hyphenStartOrEnd ? ( + + + + Only contains letters, numbers, and hyphens + + ) : ( - - At least 3 characters - + + + + Doesn't begin or end with a hyphen + + )} + + + {!validCheck.totalLength ? ( + + No longer than 253 characters + + ) : ( + + At least 3 characters + + )} + - + )} + ) } -- cgit 1.4.1