Submit fix (#4978)

* Fix submit logic

* Fix type

* Align submit task creation 1:1 with callsites

* blegh. `useThrottledValue`

* make `useThrottledValue`'s time required

---------

Co-authored-by: Hailey <me@haileyok.com>
zio/stable
dan 2024-08-22 22:43:23 +01:00 committed by GitHub
parent df5bf28e61
commit 27bb383268
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 46 additions and 32 deletions

View File

@ -2,7 +2,7 @@ import {useEffect, useRef, useState} from 'react'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
export function useThrottledValue<T>(value: T, time?: number) { export function useThrottledValue<T>(value: T, time: number) {
const pendingValueRef = useRef(value) const pendingValueRef = useRef(value)
const [throttledValue, setThrottledValue] = useState(value) const [throttledValue, setThrottledValue] = useState(value)

View File

@ -8,7 +8,7 @@ import {createFullHandle} from '#/lib/strings/handles'
import {logger} from '#/logger' import {logger} from '#/logger'
import {logEvent} from 'lib/statsig/statsig' import {logEvent} from 'lib/statsig/statsig'
import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {ScreenTransition} from '#/screens/Login/ScreenTransition'
import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' import {useSignupContext} from '#/screens/Signup/state'
import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {FormError} from '#/components/forms/FormError' import {FormError} from '#/components/forms/FormError'
@ -20,7 +20,6 @@ export function StepCaptcha() {
const {_} = useLingui() const {_} = useLingui()
const theme = useTheme() const theme = useTheme()
const {state, dispatch} = useSignupContext() const {state, dispatch} = useSignupContext()
const submit = useSubmitSignup({state, dispatch})
const [completed, setCompleted] = React.useState(false) const [completed, setCompleted] = React.useState(false)
@ -42,9 +41,13 @@ export function StepCaptcha() {
(code: string) => { (code: string) => {
setCompleted(true) setCompleted(true)
logEvent('signup:captchaSuccess', {}) logEvent('signup:captchaSuccess', {})
submit(code) const submitTask = {code, mutableProcessed: false}
dispatch({
type: 'submit',
task: submitTask,
})
}, },
[submit], [dispatch],
) )
const onError = React.useCallback( const onError = React.useCallback(

View File

@ -7,9 +7,10 @@ import {logEvent} from '#/lib/statsig/statsig'
import {createFullHandle, validateHandle} from '#/lib/strings/handles' import {createFullHandle, validateHandle} from '#/lib/strings/handles'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {ScreenTransition} from '#/screens/Login/ScreenTransition'
import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' import {useSignupContext} from '#/screens/Signup/state'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import * as TextField from '#/components/forms/TextField' import * as TextField from '#/components/forms/TextField'
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
@ -20,10 +21,10 @@ export function StepHandle() {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const {state, dispatch} = useSignupContext() const {state, dispatch} = useSignupContext()
const submit = useSubmitSignup({state, dispatch})
const agent = useAgent() const agent = useAgent()
const handleValueRef = useRef<string>(state.handle) const handleValueRef = useRef<string>(state.handle)
const [draftValue, setDraftValue] = React.useState(state.handle) const [draftValue, setDraftValue] = React.useState(state.handle)
const isLoading = useThrottledValue(state.isLoading, 500)
const onNextPress = React.useCallback(async () => { const onNextPress = React.useCallback(async () => {
const handle = handleValueRef.current.trim() const handle = handleValueRef.current.trim()
@ -64,7 +65,8 @@ export function StepHandle() {
}) })
// phoneVerificationRequired is actually whether a captcha is required // phoneVerificationRequired is actually whether a captcha is required
if (!state.serviceDescription?.phoneVerificationRequired) { if (!state.serviceDescription?.phoneVerificationRequired) {
submit() const submitTask = {code: undefined, mutableProcessed: false}
dispatch({type: 'submit', task: submitTask})
return return
} }
dispatch({type: 'next'}) dispatch({type: 'next'})
@ -74,7 +76,6 @@ export function StepHandle() {
state.activeStep, state.activeStep,
state.serviceDescription?.phoneVerificationRequired, state.serviceDescription?.phoneVerificationRequired,
state.userDomain, state.userDomain,
submit,
agent, agent,
]) ])
@ -175,7 +176,7 @@ export function StepHandle() {
)} )}
</View> </View>
<BackNextButtons <BackNextButtons
isLoading={state.isLoading} isLoading={isLoading}
isNextDisabled={!validCheck.overall} isNextDisabled={!validCheck.overall}
onBackPress={onBackPress} onBackPress={onBackPress}
onNextPress={onNextPress} onNextPress={onNextPress}

View File

@ -16,6 +16,7 @@ import {
reducer, reducer,
SignupContext, SignupContext,
SignupStep, SignupStep,
useSubmitSignup,
} from '#/screens/Signup/state' } from '#/screens/Signup/state'
import {StepCaptcha} from '#/screens/Signup/StepCaptcha' import {StepCaptcha} from '#/screens/Signup/StepCaptcha'
import {StepHandle} from '#/screens/Signup/StepHandle' import {StepHandle} from '#/screens/Signup/StepHandle'
@ -33,6 +34,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
const {screen} = useAnalytics() const {screen} = useAnalytics()
const [state, dispatch] = React.useReducer(reducer, initialState) const [state, dispatch] = React.useReducer(reducer, initialState)
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const submit = useSubmitSignup()
const activeStarterPack = useActiveStarterPack() const activeStarterPack = useActiveStarterPack()
const { const {
@ -81,6 +83,15 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
} }
}, [_, serviceInfo, isError]) }, [_, serviceInfo, isError])
React.useEffect(() => {
if (state.pendingSubmit) {
if (!state.pendingSubmit.mutableProcessed) {
state.pendingSubmit.mutableProcessed = true
submit(state, dispatch)
}
}
}, [state, dispatch, submit])
return ( return (
<SignupContext.Provider value={{state, dispatch}}> <SignupContext.Provider value={{state, dispatch}}>
<LoggedOutLayout <LoggedOutLayout

View File

@ -26,6 +26,11 @@ export enum SignupStep {
CAPTCHA, CAPTCHA,
} }
type SubmitTask = {
code: string | undefined
mutableProcessed: boolean // OK to mutate assuming it's never read in render.
}
export type SignupState = { export type SignupState = {
hasPrev: boolean hasPrev: boolean
activeStep: SignupStep activeStep: SignupStep
@ -41,6 +46,8 @@ export type SignupState = {
error: string error: string
isLoading: boolean isLoading: boolean
pendingSubmit: null | SubmitTask
} }
export type SignupAction = export type SignupAction =
@ -58,6 +65,7 @@ export type SignupAction =
| {type: 'setVerificationCode'; value: string} | {type: 'setVerificationCode'; value: string}
| {type: 'setError'; value: string} | {type: 'setError'; value: string}
| {type: 'setIsLoading'; value: boolean} | {type: 'setIsLoading'; value: boolean}
| {type: 'submit'; task: SubmitTask}
export const initialState: SignupState = { export const initialState: SignupState = {
hasPrev: false, hasPrev: false,
@ -74,6 +82,8 @@ export const initialState: SignupState = {
error: '', error: '',
isLoading: false, isLoading: false,
pendingSubmit: null,
} }
export function is13(date: Date) { export function is13(date: Date) {
@ -149,6 +159,10 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
next.error = a.value next.error = a.value
break break
} }
case 'submit': {
next.pendingSubmit = a.task
break
}
} }
next.hasPrev = next.activeStep !== SignupStep.INFO next.hasPrev = next.activeStep !== SignupStep.INFO
@ -169,19 +183,17 @@ interface IContext {
export const SignupContext = React.createContext<IContext>({} as IContext) export const SignupContext = React.createContext<IContext>({} as IContext)
export const useSignupContext = () => React.useContext(SignupContext) export const useSignupContext = () => React.useContext(SignupContext)
export function useSubmitSignup({ export function useSubmitSignup() {
state,
dispatch,
}: {
state: SignupState
dispatch: (action: SignupAction) => void
}) {
const {_} = useLingui() const {_} = useLingui()
const {createAccount} = useSessionApi() const {createAccount} = useSessionApi()
const onboardingDispatch = useOnboardingDispatch() const onboardingDispatch = useOnboardingDispatch()
return useCallback( return useCallback(
async (verificationCode?: string) => { async (
state: SignupState,
dispatch: (action: SignupAction) => void,
verificationCode?: string,
) => {
if (!state.email) { if (!state.email) {
dispatch({type: 'setStep', value: SignupStep.INFO}) dispatch({type: 'setStep', value: SignupStep.INFO})
return dispatch({ return dispatch({
@ -270,19 +282,6 @@ export function useSubmitSignup({
dispatch({type: 'setIsLoading', value: false}) dispatch({type: 'setIsLoading', value: false})
} }
}, },
[ [_, onboardingDispatch, createAccount],
state.email,
state.password,
state.handle,
state.serviceDescription?.phoneVerificationRequired,
state.serviceUrl,
state.userDomain,
state.inviteCode,
state.dateOfBirth,
dispatch,
_,
onboardingDispatch,
createAccount,
],
) )
} }