-
Notifications
You must be signed in to change notification settings - Fork 23
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Environment
package.json
{
"name": "zstation",
"version": "1.0.0",
"private": true,
"scripts": {
"android:run": "react-native run-android",
"colors:update": "npx @klarna/platform-colors",
"ios:run": "react-native run-ios",
"lint:check": "eslint .",
"lint:fix": "eslint . --fix",
"start": "react-native start",
"supabase:types:generate": "source .env && pnpm supabase gen types typescript --project-id $SUPABASE_PROJECT_ID > src/@types/supabase/client.ts",
"test": "jest --config=./jest.config.ts",
"type:check": "tsc --noEmit --skipLibCheck"
},
"packageManager": "pnpm@10.17.0",
"dependencies": {
"@callstack/liquid-glass": "0.4.1",
"@hookform/resolvers": "5.2.2",
"@klarna/platform-colors": "0.4.0",
"@react-native/new-app-screen": "0.81.4",
"@react-navigation/native": "7.1.17",
"@react-navigation/native-stack": "7.3.26",
"@supabase/supabase-js": "2.57.4",
"i18next": "25.5.2",
"jwt-decode": "4.0.0",
"lodash.debounce": "4.0.8",
"react": "19.1.0",
"react-hook-form": "7.63.0",
"react-i18next": "15.7.3",
"react-native": "0.81.4",
"react-native-advanced-input-mask": "1.4.5",
"react-native-avoid-softinput": "8.0.0",
"react-native-config": "1.5.9",
"react-native-edge-to-edge": "1.7.0",
"react-native-gesture-handler": "2.28.0",
"react-native-localize": "3.5.2",
"react-native-mmkv": "3.3.3",
"react-native-notifier": "2.0.0",
"react-native-safe-area-context": "5.6.1",
"react-native-screens": "4.16.0",
"react-native-svg": "15.13.0",
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.16.1",
"zod": "4.1.11"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-transform-export-namespace-from": "7.27.1",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.0.0",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.81.4",
"@react-native/eslint-config": "0.81.4",
"@react-native/metro-config": "0.81.4",
"@react-native/typescript-config": "0.81.4",
"@types/jest": "29.5.13",
"@types/lodash.debounce": "4.0.9",
"@types/react": "19.1.0",
"@types/react-test-renderer": "19.1.0",
"babel-plugin-module-resolver": "5.0.2",
"eslint": "8.57.0",
"eslint-plugin-import-helpers": "2.0.1",
"eslint-plugin-no-relative-import-paths": "1.6.1",
"eslint-plugin-perfectionist": "4.15.0",
"eslint-plugin-prettier": "5.5.4",
"eslint-plugin-sort-destructure-keys": "2.0.0",
"eslint-plugin-sort-keys-fix": "1.1.2",
"eslint-plugin-typescript-sort-keys": "3.3.0",
"jest": "29.6.3",
"prettier": "3.6.2",
"react-native-asset": "2.1.1",
"react-test-renderer": "19.1.0",
"supabase": "2.40.7",
"typescript": "5.8.3"
},
"engines": {
"node": ">=20"
}
}Affected platforms
- Android
- iOS
Current behavior
In my CodeForm component, I use the CodeInput component with 6 digits. In the onSubmitEditing function of each input, I focus on the next field, and this is pushing my screen up infinitely.
When next button is pressed, this code is called.
const onSubmitEditing = useCallback(
(event: TextInputSubmitEditingEvent, index: number) => {
if (index === length - 1) return props.onSubmitEditing(event);
inputRefs.current[index + 1]?.focus();
},
[length, props],
);LoginTemplate
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { View, StyleSheet, ScrollView, TouchableOpacity, useWindowDimensions, StatusBar } from 'react-native';
import { AvoidSoftInputView } from 'react-native-avoid-softinput';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Video from 'react-native-video';
import { LiquidView } from '~/components/LiquidView';
import { Logo } from '~/components/svg/Logo';
import { Typography } from '~/components/Typography';
import { black_8 } from '~/theme/colors';
import { Radius } from '~/theme/radius';
import { Spacing } from '~/theme/spacing';
import { CodeForm } from './CodeForm';
import type { CodeFormProps } from './CodeForm/types';
import { PhoneForm } from './PhoneForm';
import type { PhoneFormProps } from './PhoneForm/types';
import type { FormMode } from './types';
export function LoginTemplate() {
const { t } = useTranslation();
const { bottom, top } = useSafeAreaInsets();
const { height } = useWindowDimensions();
const [formMode, setFormMode] = useState<FormMode>('phone');
const [phone, setPhone] = useState('');
const onSubmitPhone: PhoneFormProps['onSuccess'] = async ({ phone: formPhone }) => {
setPhone(formPhone);
setFormMode('code');
};
const onSubmitCode: CodeFormProps['onSuccess'] = async () => {};
return (
<View style={[styles.container, { paddingBottom: bottom, paddingTop: top }]}>
<StatusBar barStyle="dark-content" />
<Video
ignoreSilentSwitch="obey"
muted
paused
playInBackground={false}
playWhenInactive={false}
pointerEvents="none"
repeat
resizeMode="cover"
source={require('~/assets/files/login_video.mp4')}
style={StyleSheet.absoluteFill}
/>
<AvoidSoftInputView avoidOffset={0} showAnimationDelay={0} showAnimationDuration={0} style={{ flex: 1 }}>
<ScrollView
bounces={false}
contentContainerStyle={{ height: height - top - bottom }}
contentInsetAdjustmentBehavior="always"
keyboardShouldPersistTaps="handled"
overScrollMode="never"
>
<View style={styles.content}>
<LiquidView colorScheme="dark" effect="clear" style={styles.liquidView} tintColor={black_8}>
<View style={styles.header}>
<Logo height={48} variant="logo_3" width={120} />
{formMode === 'code' && (
<TouchableOpacity onPress={() => setFormMode('phone')}>
<Typography color="red_zera" variant="caption" weight="regular">
{t('translation:login.switchPhone')}
</Typography>
</TouchableOpacity>
)}
</View>
<View style={styles.titleContainer}>
<Typography variant="title" weight="light">
{t('translation:login.description')}
</Typography>
</View>
<View style={styles.form}>
{formMode === 'phone' ? (
<CodeForm onSuccess={onSubmitCode} phone={phone} />
) : (
<PhoneForm onSuccess={onSubmitPhone} phone={phone} />
)}
</View>
</LiquidView>
</View>
</ScrollView>
</AvoidSoftInputView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
justifyContent: 'flex-end',
padding: Spacing.large,
paddingBottom: 0,
},
form: {
marginTop: Spacing.medium,
},
header: {
alignItems: 'flex-start',
flexDirection: 'row',
justifyContent: 'space-between',
},
liquidView: {
borderRadius: Radius.large,
padding: Spacing.large,
},
titleContainer: {
marginTop: Spacing.medium,
},
});CodeInput
import React, { useCallback, useRef } from 'react';
import type { FC } from 'react';
import { StyleSheet, View } from 'react-native';
import type { TextInput, TextInputKeyPressEvent, TextInputSubmitEditingEvent } from 'react-native';
import { ErrorMessage } from '~/components/ErrorMessage';
import { Input, styles as inputStyles } from '~/components/Input';
import { Label } from '~/components/Label';
import { black_6 } from '~/theme/colors';
import { Spacing } from '~/theme/spacing';
import type { CodeInputProps } from './types';
export const CodeInput: FC<CodeInputProps> = ({
error,
label,
length,
onChangeText,
returnKeyType,
value,
...props
}) => {
const inputRefs = useRef<Array<TextInput>>(Array(length).fill(null));
const handleChangeText = useCallback(
(text: string, index: number) => {
const newValue = value.split('');
newValue[index] = text;
const combinedValue = newValue.join('');
onChangeText(combinedValue);
// Move to next input if there's a value
if (text && index < length - 1) inputRefs.current[index + 1]?.focus();
},
[length, onChangeText, value],
);
const handleKeyPress = useCallback(
(event: TextInputKeyPressEvent, index: number) => {
// Move to previous input on backspace if current input is empty
if (event.nativeEvent.key === 'Backspace' && !value[index] && index > 0) {
inputRefs.current[index - 1]?.focus();
}
},
[value],
);
const onSubmitEditing = useCallback(
(event: TextInputSubmitEditingEvent, index: number) => {
if (index === length - 1) return props.onSubmitEditing(event);
inputRefs.current[index + 1]?.focus();
},
[length, props],
);
return (
<View>
{label && <Label>{label}</Label>}
<View style={styles.container}>
{Array(length)
.fill(0)
.map((_, index) => (
<Input
{...props}
autoComplete="one-time-code"
containerStyle={styles.inputContainerStyle}
key={`input-code-${index}`}
keyboardType="number-pad"
maxLength={1}
onChangeText={text => handleChangeText(text, index)}
onKeyPress={e => handleKeyPress(e, index)}
onSubmitEditing={e => onSubmitEditing(e, index)}
ref={ref => {
inputRefs.current[index] = ref;
}}
returnKeyType={index === length - 1 ? returnKeyType : 'next'}
style={[styles.input, error ? inputStyles.inputError : undefined]}
textAlign="center"
value={value[index] || ''}
/>
))}
</View>
{!!error && <ErrorMessage style={inputStyles.errorContainer}>{error}</ErrorMessage>}
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
gap: Spacing.small,
},
input: {
borderColor: black_6,
borderWidth: 1,
},
inputContainerStyle: {
flex: 1,
},
});Video
final.mov
Expected behavior
Do not add this extra space to the page scroll.
Reproduction
This project is private
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working