Kurs React Native · Moduł 5: Formularze
Projekt: Formularz rejestracji Atlantydy
Połączmy wszystko w kompletny formularz rejestracji!
Kompletny formularz
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import {
View,
Text,
TextInput,
Switch,
Pressable,
ScrollView,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
Alert,
} from 'react-native';
// Schema
const registrationSchema = z.object({
name: z.string().min(2, 'Min. 2 znaki').max(50),
email: z.string().email('Nieprawidłowy email'),
password: z
.string()
.min(8, 'Min. 8 znaków')
.regex(/[A-Z]/, 'Wymagana wielka litera')
.regex(/[0-9]/, 'Wymagana cyfra'),
confirmPassword: z.string(),
birthDate: z.date().refine(
(date) => {
const age = (Date.now() - date.getTime()) / (365.25 * 24 * 60 * 60 * 1000);
return age >= 18;
},
{ message: 'Musisz mieć min. 18 lat' }
),
newsletter: z.boolean(),
terms: z.literal(true, {
errorMap: () => ({ message: 'Musisz zaakceptować regulamin' }),
}),
}).refine((data) => data.password === data.confirmPassword, {
message: 'Hasła muszą być identyczne',
path: ['confirmPassword'],
});
type FormData = z.infer<typeof registrationSchema>;
export function RegistrationForm() {
const {
control,
handleSubmit,
formState: { errors, isSubmitting },
reset,
} = useForm<FormData>({
resolver: zodResolver(registrationSchema),
defaultValues: {
name: '',
email: '',
password: '',
confirmPassword: '',
birthDate: new Date(),
newsletter: true,
terms: false,
},
});
const onSubmit = async (data: FormData) => {
try {
// Symulacja API call
await new Promise((resolve) => setTimeout(resolve, 2000));
Alert.alert(
'Sukces!',
`Witaj w Atlantydzie, ${data.name}!`,
[{ text: 'OK', onPress: () => reset() }]
);
} catch (error) {
Alert.alert('Błąd', 'Nie udało się zarejestrować');
}
};
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>Rejestracja w Atlantydzie</Text>
{/* Imię */}
<View style={styles.field}>
<Text style={styles.label}>Imię</Text>
<Controller
control={control}
name="name"
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={[styles.input, errors.name && styles.inputError]}
placeholder="Twoje imię"
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
{errors.name && (
<Text style={styles.error}>{errors.name.message}</Text>
)}
</View>
{/* Email */}
<View style={styles.field}>
<Text style={styles.label}>Email</Text>
<Controller
control={control}
name="email"
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={[styles.input, errors.email && styles.inputError]}
placeholder="email@atlantyda.sea"
keyboardType="email-address"
autoCapitalize="none"
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
{errors.email && (
<Text style={styles.error}>{errors.email.message}</Text>
)}
</View>
{/* Hasło */}
<View style={styles.field}>
<Text style={styles.label}>Hasło</Text>
<Controller
control={control}
name="password"
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={[styles.input, errors.password && styles.inputError]}
placeholder="Min. 8 znaków"
secureTextEntry
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
{errors.password && (
<Text style={styles.error}>{errors.password.message}</Text>
)}
</View>
{/* Potwierdź hasło */}
<View style={styles.field}>
<Text style={styles.label}>Potwierdź hasło</Text>
<Controller
control={control}
name="confirmPassword"
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={[styles.input, errors.confirmPassword && styles.inputError]}
placeholder="Powtórz hasło"
secureTextEntry
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
{errors.confirmPassword && (
<Text style={styles.error}>{errors.confirmPassword.message}</Text>
)}
</View>
{/* Newsletter */}
<Controller
control={control}
name="newsletter"
render={({ field: { onChange, value } }) => (
<View style={styles.switchRow}>
<Text>Newsletter Atlantydy</Text>
<Switch value={value} onValueChange={onChange} />
</View>
)}
/>
{/* Regulamin */}
<Controller
control={control}
name="terms"
render={({ field: { onChange, value } }) => (
<View>
<View style={styles.switchRow}>
<Text>Akceptuję regulamin</Text>
<Switch value={value} onValueChange={onChange} />
</View>
{errors.terms && (
<Text style={styles.error}>{errors.terms.message}</Text>
)}
</View>
)}
/>
{/* Submit */}
<Pressable
style={[styles.button, isSubmitting && styles.buttonDisabled]}
onPress={handleSubmit(onSubmit)}
disabled={isSubmitting}
>
{isSubmitting ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}>Zarejestruj się</Text>
)}
</Pressable>
</ScrollView>
</KeyboardAvoidingView>
);
}Ten projekt łączy wszystkie poznane techniki: TextInput, KeyboardAvoidingView, react-hook-form, walidację z Zod, Switch i stylowanie!
Kod do tej lekcji: App.tsx
import React from 'react';
import { View, Text, TextInput, Switch, Pressable, StyleSheet } from 'react-native';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(2, 'Min. 2 znaki'),
email: z.string().email('Nieprawidłowy email'),
newsletter: z.boolean(),
});
type FormData = z.infer<typeof schema>;
export default function App() {
const {
control,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
// TODO: podłącz walidację schematu przez resolver: zodResolver(schema)
defaultValues: { name: '', email: '', newsletter: true },
});
const onSubmit = (data: FormData) => console.log(data);
return (
<View style={styles.container}>
{/* TODO: dodaj <Controller name="name"> z <TextInput> (onChange, onBlur, value) i placeholderem "Imię" */}
{/* TODO: dodaj <Controller name="email"> z <TextInput> (keyboardType="email-address") i placeholderem "Email" */}
<Controller
control={control}
name="newsletter"
render={({ field: { onChange, value } }) => (
<View style={styles.row}>
<Text style={styles.label}>Newsletter Atlantydy</Text>
{/* TODO: dodaj <Switch value={value} onValueChange={onChange} /> */}
</View>
)}
/>
<Pressable style={styles.button} onPress={handleSubmit(onSubmit)}>
<Text style={styles.buttonText}>Zarejestruj się</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
justifyContent: 'center',
backgroundColor: '#1a5276',
},
input: {
backgroundColor: '#fff',
borderRadius: 8,
padding: 14,
fontSize: 16,
color: '#1a5276',
marginBottom: 8,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginVertical: 12,
},
label: {
color: '#fff',
fontSize: 16,
},
error: {
color: '#e74c3c',
marginBottom: 12,
},
button: {
backgroundColor: '#2980b9',
borderRadius: 8,
padding: 14,
alignItems: 'center',
marginTop: 12,
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Zadania praktyczne w grze
- Edytor kodu
Uzupełnij kod łączący wszystkie poznane techniki.