Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

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>

authored by danabra.mov

Hailey and committed by
GitHub
27bb3832 df5bf28e

+46 -32
+1 -1
src/components/hooks/useThrottledValue.ts
··· 2 2 3 3 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 4 4 5 - export function useThrottledValue<T>(value: T, time?: number) { 5 + export function useThrottledValue<T>(value: T, time: number) { 6 6 const pendingValueRef = useRef(value) 7 7 const [throttledValue, setThrottledValue] = useState(value) 8 8
+7 -4
src/screens/Signup/StepCaptcha/index.tsx
··· 8 8 import {logger} from '#/logger' 9 9 import {logEvent} from 'lib/statsig/statsig' 10 10 import {ScreenTransition} from '#/screens/Login/ScreenTransition' 11 - import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' 11 + import {useSignupContext} from '#/screens/Signup/state' 12 12 import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' 13 13 import {atoms as a, useTheme} from '#/alf' 14 14 import {FormError} from '#/components/forms/FormError' ··· 20 20 const {_} = useLingui() 21 21 const theme = useTheme() 22 22 const {state, dispatch} = useSignupContext() 23 - const submit = useSubmitSignup({state, dispatch}) 24 23 25 24 const [completed, setCompleted] = React.useState(false) 26 25 ··· 42 41 (code: string) => { 43 42 setCompleted(true) 44 43 logEvent('signup:captchaSuccess', {}) 45 - submit(code) 44 + const submitTask = {code, mutableProcessed: false} 45 + dispatch({ 46 + type: 'submit', 47 + task: submitTask, 48 + }) 46 49 }, 47 - [submit], 50 + [dispatch], 48 51 ) 49 52 50 53 const onError = React.useCallback(
+6 -5
src/screens/Signup/StepHandle.tsx
··· 7 7 import {createFullHandle, validateHandle} from '#/lib/strings/handles' 8 8 import {useAgent} from '#/state/session' 9 9 import {ScreenTransition} from '#/screens/Login/ScreenTransition' 10 - import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' 10 + import {useSignupContext} from '#/screens/Signup/state' 11 11 import {atoms as a, useTheme} from '#/alf' 12 12 import * as TextField from '#/components/forms/TextField' 13 + import {useThrottledValue} from '#/components/hooks/useThrottledValue' 13 14 import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' 14 15 import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 15 16 import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' ··· 20 21 const {_} = useLingui() 21 22 const t = useTheme() 22 23 const {state, dispatch} = useSignupContext() 23 - const submit = useSubmitSignup({state, dispatch}) 24 24 const agent = useAgent() 25 25 const handleValueRef = useRef<string>(state.handle) 26 26 const [draftValue, setDraftValue] = React.useState(state.handle) 27 + const isLoading = useThrottledValue(state.isLoading, 500) 27 28 28 29 const onNextPress = React.useCallback(async () => { 29 30 const handle = handleValueRef.current.trim() ··· 64 65 }) 65 66 // phoneVerificationRequired is actually whether a captcha is required 66 67 if (!state.serviceDescription?.phoneVerificationRequired) { 67 - submit() 68 + const submitTask = {code: undefined, mutableProcessed: false} 69 + dispatch({type: 'submit', task: submitTask}) 68 70 return 69 71 } 70 72 dispatch({type: 'next'}) ··· 74 76 state.activeStep, 75 77 state.serviceDescription?.phoneVerificationRequired, 76 78 state.userDomain, 77 - submit, 78 79 agent, 79 80 ]) 80 81 ··· 175 176 )} 176 177 </View> 177 178 <BackNextButtons 178 - isLoading={state.isLoading} 179 + isLoading={isLoading} 179 180 isNextDisabled={!validCheck.overall} 180 181 onBackPress={onBackPress} 181 182 onNextPress={onNextPress}
+11
src/screens/Signup/index.tsx
··· 16 16 reducer, 17 17 SignupContext, 18 18 SignupStep, 19 + useSubmitSignup, 19 20 } from '#/screens/Signup/state' 20 21 import {StepCaptcha} from '#/screens/Signup/StepCaptcha' 21 22 import {StepHandle} from '#/screens/Signup/StepHandle' ··· 33 34 const {screen} = useAnalytics() 34 35 const [state, dispatch] = React.useReducer(reducer, initialState) 35 36 const {gtMobile} = useBreakpoints() 37 + const submit = useSubmitSignup() 36 38 37 39 const activeStarterPack = useActiveStarterPack() 38 40 const { ··· 80 82 dispatch({type: 'setError', value: ''}) 81 83 } 82 84 }, [_, serviceInfo, isError]) 85 + 86 + React.useEffect(() => { 87 + if (state.pendingSubmit) { 88 + if (!state.pendingSubmit.mutableProcessed) { 89 + state.pendingSubmit.mutableProcessed = true 90 + submit(state, dispatch) 91 + } 92 + } 93 + }, [state, dispatch, submit]) 83 94 84 95 return ( 85 96 <SignupContext.Provider value={{state, dispatch}}>
+21 -22
src/screens/Signup/state.ts
··· 26 26 CAPTCHA, 27 27 } 28 28 29 + type SubmitTask = { 30 + code: string | undefined 31 + mutableProcessed: boolean // OK to mutate assuming it's never read in render. 32 + } 33 + 29 34 export type SignupState = { 30 35 hasPrev: boolean 31 36 activeStep: SignupStep ··· 41 46 42 47 error: string 43 48 isLoading: boolean 49 + 50 + pendingSubmit: null | SubmitTask 44 51 } 45 52 46 53 export type SignupAction = ··· 58 65 | {type: 'setVerificationCode'; value: string} 59 66 | {type: 'setError'; value: string} 60 67 | {type: 'setIsLoading'; value: boolean} 68 + | {type: 'submit'; task: SubmitTask} 61 69 62 70 export const initialState: SignupState = { 63 71 hasPrev: false, ··· 74 82 75 83 error: '', 76 84 isLoading: false, 85 + 86 + pendingSubmit: null, 77 87 } 78 88 79 89 export function is13(date: Date) { ··· 149 159 next.error = a.value 150 160 break 151 161 } 162 + case 'submit': { 163 + next.pendingSubmit = a.task 164 + break 165 + } 152 166 } 153 167 154 168 next.hasPrev = next.activeStep !== SignupStep.INFO ··· 169 183 export const SignupContext = React.createContext<IContext>({} as IContext) 170 184 export const useSignupContext = () => React.useContext(SignupContext) 171 185 172 - export function useSubmitSignup({ 173 - state, 174 - dispatch, 175 - }: { 176 - state: SignupState 177 - dispatch: (action: SignupAction) => void 178 - }) { 186 + export function useSubmitSignup() { 179 187 const {_} = useLingui() 180 188 const {createAccount} = useSessionApi() 181 189 const onboardingDispatch = useOnboardingDispatch() 182 190 183 191 return useCallback( 184 - async (verificationCode?: string) => { 192 + async ( 193 + state: SignupState, 194 + dispatch: (action: SignupAction) => void, 195 + verificationCode?: string, 196 + ) => { 185 197 if (!state.email) { 186 198 dispatch({type: 'setStep', value: SignupStep.INFO}) 187 199 return dispatch({ ··· 270 282 dispatch({type: 'setIsLoading', value: false}) 271 283 } 272 284 }, 273 - [ 274 - state.email, 275 - state.password, 276 - state.handle, 277 - state.serviceDescription?.phoneVerificationRequired, 278 - state.serviceUrl, 279 - state.userDomain, 280 - state.inviteCode, 281 - state.dateOfBirth, 282 - dispatch, 283 - _, 284 - onboardingDispatch, 285 - createAccount, 286 - ], 285 + [_, onboardingDispatch, createAccount], 287 286 ) 288 287 }