From 63bb8fda2d28e11d7e60808e1e86384d48ec1b47 Mon Sep 17 00:00:00 2001
From: Paul Frazee <pfrazee@gmail.com>
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 <dan.abramov@gmail.com>
---
 src/screens/Signup/StepInfo/index.tsx | 87 +++++++++++++++++++++++++++++------
 1 file changed, 74 insertions(+), 13 deletions(-)

(limited to 'src/screens/Signup/StepInfo/index.tsx')

diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx
index 691e23a53..47fb4c70b 100644
--- a/src/screens/Signup/StepInfo/index.tsx
+++ b/src/screens/Signup/StepInfo/index.tsx
@@ -1,8 +1,10 @@
-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 * as EmailValidator from 'email-validator'
 
+import {logEvent} from '#/lib/statsig/statsig'
 import {logger} from '#/logger'
 import {ScreenTransition} from '#/screens/Login/ScreenTransition'
 import {is13, is18, useSignupContext} from '#/screens/Signup/state'
@@ -16,6 +18,7 @@ import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/E
 import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
 import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
 import {Loader} from '#/components/Loader'
+import {BackNextButtons} from '../BackNextButtons'
 
 function sanitizeDate(date: Date): Date {
   if (!date || date.toString() === 'Invalid Date') {
@@ -28,13 +31,72 @@ function sanitizeDate(date: Date): Date {
 }
 
 export function StepInfo({
+  onPressBack,
+  isServerError,
+  refetchServer,
   isLoadingStarterPack,
 }: {
+  onPressBack: () => void
+  isServerError: boolean
+  refetchServer: () => void
   isLoadingStarterPack: boolean
 }) {
   const {_} = useLingui()
   const {state, dispatch} = useSignupContext()
 
+  const inviteCodeValueRef = useRef<string>(state.inviteCode)
+  const emailValueRef = useRef<string>(state.email)
+  const passwordValueRef = useRef<string>(state.password)
+
+  const onNextPress = React.useCallback(async () => {
+    const inviteCode = inviteCodeValueRef.current
+    const email = emailValueRef.current
+    const password = passwordValueRef.current
+
+    if (!is13(state.dateOfBirth)) {
+      return
+    }
+
+    if (state.serviceDescription?.inviteCodeRequired && !inviteCode) {
+      return dispatch({
+        type: 'setError',
+        value: _(msg`Please enter your invite code.`),
+      })
+    }
+    if (!email) {
+      return dispatch({
+        type: 'setError',
+        value: _(msg`Please enter your email.`),
+      })
+    }
+    if (!EmailValidator.validate(email)) {
+      return dispatch({
+        type: 'setError',
+        value: _(msg`Your email appears to be invalid.`),
+      })
+    }
+    if (!password) {
+      return dispatch({
+        type: 'setError',
+        value: _(msg`Please choose your password.`),
+      })
+    }
+
+    dispatch({type: 'setInviteCode', value: inviteCode})
+    dispatch({type: 'setEmail', value: email})
+    dispatch({type: 'setPassword', value: password})
+    dispatch({type: 'next'})
+    logEvent('signup:nextPressed', {
+      activeStep: state.activeStep,
+    })
+  }, [
+    _,
+    dispatch,
+    state.activeStep,
+    state.dateOfBirth,
+    state.serviceDescription?.inviteCodeRequired,
+  ])
+
   return (
     <ScreenTransition>
       <View style={[a.gap_md]}>
@@ -65,10 +127,7 @@ export function StepInfo({
                   <TextField.Icon icon={Ticket} />
                   <TextField.Input
                     onChangeText={value => {
-                      dispatch({
-                        type: 'setInviteCode',
-                        value: value.trim(),
-                      })
+                      inviteCodeValueRef.current = value.trim()
                     }}
                     label={_(msg`Required for this provider`)}
                     defaultValue={state.inviteCode}
@@ -88,10 +147,7 @@ export function StepInfo({
                 <TextField.Input
                   testID="emailInput"
                   onChangeText={value => {
-                    dispatch({
-                      type: 'setEmail',
-                      value: value.trim(),
-                    })
+                    emailValueRef.current = value.trim()
                   }}
                   label={_(msg`Enter your email address`)}
                   defaultValue={state.email}
@@ -110,10 +166,7 @@ export function StepInfo({
                 <TextField.Input
                   testID="passwordInput"
                   onChangeText={value => {
-                    dispatch({
-                      type: 'setPassword',
-                      value,
-                    })
+                    passwordValueRef.current = value
                   }}
                   label={_(msg`Choose your password`)}
                   defaultValue={state.password}
@@ -147,6 +200,14 @@ export function StepInfo({
           </>
         ) : undefined}
       </View>
+      <BackNextButtons
+        hideNext={!is13(state.dateOfBirth)}
+        showRetry={isServerError}
+        isLoading={state.isLoading}
+        onBackPress={onPressBack}
+        onNextPress={onNextPress}
+        onRetryPress={refetchServer}
+      />
     </ScreenTransition>
   )
 }
-- 
cgit 1.4.1

