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
parent
df5bf28e61
commit
27bb383268
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue