Skip to content

RN Lezione 8: Form complessi e AsyncStorage

  • Costruire form con validazione
  • Usare Alert per messaggi e conferme
  • Salvare dati persistenti con AsyncStorage
  • Creare una Todo List completa e persistente

Pattern: stato per ogni campo + stato errore

Section titled “Pattern: stato per ogni campo + stato errore”
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errori, setErrori] = useState({});
const valida = () => {
const nuoviErrori = {};
if (!email.trim()) {
nuoviErrori.email = 'L\'email è obbligatoria';
} else if (!email.includes('@')) {
nuoviErrori.email = 'Email non valida';
}
if (!password) {
nuoviErrori.password = 'La password è obbligatoria';
} else if (password.length < 6) {
nuoviErrori.password = 'Minimo 6 caratteri';
}
setErrori(nuoviErrori);
return Object.keys(nuoviErrori).length === 0;
};
const submit = () => {
if (valida()) {
Alert.alert('✅ Successo', 'Login valido!');
}
};
<TextInput
style={[
styles.input,
errori.email && styles.inputErrore // ← bordo rosso
]}
placeholder="Email"
value={email}
onChangeText={(text) => {
setEmail(text);
setErrori(prev => ({ ...prev, email: '' })); // pulisci errore
}}
keyboardType="email-address"
/>
{errori.email && (
<Text style={styles.erroreTesto}>{errori.email}</Text>
)}
const styles = StyleSheet.create({
input: {
borderWidth: 1,
borderColor: '#ddd',
padding: 12,
borderRadius: 8,
marginBottom: 4,
fontSize: 16,
},
inputErrore: {
borderColor: '#e74c3c',
borderWidth: 2,
},
erroreTesto: {
color: '#e74c3c',
fontSize: 12,
marginBottom: 10,
marginLeft: 4,
},
});

// Alert semplice
Alert.alert('Messaggio', 'Ciao mondo!');
// Alert con pulsante
Alert.alert('Successo', 'Operazione completata', [
{ text: 'OK' }
]);
// Alert con scelta
Alert.alert(
'Elimina',
'Sei sicuro?',
[
{ text: 'Annulla', style: 'cancel' },
{ text: 'Elimina', onPress: () => elimina(), style: 'destructive' },
]
);

Pattern: conferma prima di azione distruttiva

Section titled “Pattern: conferma prima di azione distruttiva”
const eliminaTutto = () => {
Alert.alert(
'⚠️ Attenzione',
'Eliminare tutti i dati? Operazione irreversibile.',
[
{ text: 'Annulla', style: 'cancel' },
{
text: 'Elimina tutto',
style: 'destructive',
onPress: () => {
setDati([]);
salvaDati([]);
}
},
]
);
};

AsyncStorage salva dati in modo persistente sul telefono (sopravvive alla chiusura dell’app).

Terminal window
npx expo install @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
// Salvare (solo stringhe! usa JSON.stringify)
const salva = async (chiave, valore) => {
try {
const json = JSON.stringify(valore);
await AsyncStorage.setItem(chiave, json);
} catch (e) {
console.error('Errore salvataggio:', e);
}
};
// Caricare
const carica = async (chiave) => {
try {
const json = await AsyncStorage.getItem(chiave);
return json ? JSON.parse(json) : null;
} catch (e) {
console.error('Errore caricamento:', e);
return null;
}
};
// Eliminare
const eliminaChiave = async (chiave) => {
try {
await AsyncStorage.removeItem(chiave);
} catch (e) {
console.error('Errore eliminazione:', e);
}
};
const CHIAVE_LISTA = '@lista_spesa';
export default function App() {
const [items, setItems] = useState([]);
const [caricato, setCaricato] = useState(false);
// All'avvio: carica dati salvati
useEffect(() => {
const init = async () => {
const salvati = await AsyncStorage.getItem(CHIAVE_LISTA);
if (salvati) setItems(JSON.parse(salvati));
setCaricato(true);
};
init();
}, []);
// A ogni modifica: salva
useEffect(() => {
if (caricato) {
AsyncStorage.setItem(CHIAVE_LISTA, JSON.stringify(items));
}
}, [items, caricato]);
// ...
}

import { View, Text, TextInput, TouchableOpacity, FlatList, Alert, StyleSheet } from 'react-native';
import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
const CHIAVE = '@todo_list';
export default function App() {
const [task, setTask] = useState('');
const [tasks, setTasks] = useState([]);
const [pronto, setPronto] = useState(false);
// Carica all'avvio
useEffect(() => {
const init = async () => {
const salvate = await AsyncStorage.getItem(CHIAVE);
if (salvate) setTasks(JSON.parse(salvate));
setPronto(true);
};
init();
}, []);
// Salva a ogni modifica
useEffect(() => {
if (pronto) {
AsyncStorage.setItem(CHIAVE, JSON.stringify(tasks));
}
}, [tasks, pronto]);
const aggiungi = () => {
if (!task.trim()) return;
setTasks([
...tasks,
{ id: Date.now().toString(), testo: task, fatto: false }
]);
setTask('');
};
const toggleTask = (id) => {
setTasks(tasks.map(t =>
t.id === id ? { ...t, fatto: !t.fatto } : t
));
};
const eliminaTask = (id) => {
setTasks(tasks.filter(t => t.id !== id));
};
const confermaElimina = (item) => {
Alert.alert(
'Elimina',
`Eliminare "${item.testo}"?`,
[
{ text: 'Annulla', style: 'cancel' },
{ text: 'Elimina', onPress: () => eliminaTask(item.id), style: 'destructive' },
]
);
};
const renderItem = ({ item }) => (
<TouchableOpacity
style={styles.taskItem}
onPress={() => toggleTask(item.id)}
onLongPress={() => confermaElimina(item)}
>
<View style={[styles.check, item.fatto && styles.checkFatto]}>
{item.fatto && <Text style={styles.checkSegno}></Text>}
</View>
<Text style={[styles.taskTesto, item.fatto && styles.taskFatto]}>
{item.testo}
</Text>
<TouchableOpacity onPress={() => confermaElimina(item)}>
<Text style={styles.eliminaIcona}></Text>
</TouchableOpacity>
</TouchableOpacity>
);
return (
<View style={styles.container}>
<Text style={styles.titolo}>✅ Todo List</Text>
<Text style={styles.sottotitolo}>
{tasks.filter(t => t.fatto).length}/{tasks.length} completate
</Text>
<View style={styles.form}>
<TextInput
style={styles.input}
placeholder="Nuovo task..."
value={task}
onChangeText={setTask}
onSubmitEditing={aggiungi}
/>
<TouchableOpacity style={styles.btnAggiungi} onPress={aggiungi}>
<Text style={styles.btnTesto}>+</Text>
</TouchableOpacity>
</View>
<FlatList
data={tasks}
renderItem={renderItem}
keyExtractor={item => item.id}
ListEmptyComponent={
<Text style={styles.vuoto}>
{pronto ? 'Nessun task. Aggiungine uno!' : 'Caricamento...'}
</Text>
}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 50,
paddingHorizontal: 20,
backgroundColor: '#f5f5f5',
},
titolo: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 5,
},
sottotitolo: {
fontSize: 14,
color: 'grey',
marginBottom: 20,
},
form: {
flexDirection: 'row',
gap: 10,
marginBottom: 20,
},
input: {
flex: 1,
backgroundColor: 'white',
padding: 15,
borderRadius: 10,
fontSize: 16,
elevation: 1,
},
btnAggiungi: {
width: 50,
backgroundColor: '#3498db',
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
},
btnTesto: {
color: 'white',
fontSize: 24,
},
taskItem: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'white',
padding: 15,
borderRadius: 10,
marginBottom: 8,
elevation: 1,
},
check: {
width: 24,
height: 24,
borderRadius: 12,
borderWidth: 2,
borderColor: '#ccc',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
checkFatto: {
backgroundColor: '#2ecc71',
borderColor: '#2ecc71',
},
checkSegno: {
color: 'white',
fontSize: 14,
fontWeight: 'bold',
},
taskTesto: {
flex: 1,
fontSize: 16,
},
taskFatto: {
textDecorationLine: 'line-through',
color: '#aaa',
},
eliminaIcona: {
fontSize: 18,
color: '#e74c3c',
padding: 5,
},
vuoto: {
textAlign: 'center',
color: 'grey',
marginTop: 50,
fontSize: 16,
},
});

const resetDati = async () => {
Alert.alert(
'Reset',
'Cancellare tutti i dati salvati?',
[
{ text: 'Annulla', style: 'cancel' },
{
text: 'Reset',
style: 'destructive',
onPress: async () => {
await AsyncStorage.clear(); // Cancella TUTTO
setTasks([]);
setPronto(false);
// Ricarica stato iniziale
setPronto(true);
}
},
]
);
};

OperazioneCodice
Salvare datiAsyncStorage.setItem(key, JSON.stringify(val))
Caricare datiJSON.parse(await AsyncStorage.getItem(key))
Eliminare chiaveAsyncStorage.removeItem(key)
Cancellare tuttoAsyncStorage.clear()
Alert informativoAlert.alert('Titolo', 'Msg')
Alert con sceltaAlert.alert('Titolo', 'Msg', [{text:'Si', onPress:fn}])

Crea un’app diario che:

  1. Form per scrivere: titolo + testo + pulsante “Salva”
  2. Data automatica: new Date().toLocaleDateString('it-IT')
  3. AsyncStorage per salvare tutti i post
  4. FlatList per mostrare i post salvati
  5. TouchableOpacity per leggere il post completo (Alert)
  6. Long-press per eliminare
  7. Messaggio “Nessun post” quando vuoto
{ id: '12345', titolo: 'Primo giorno', testo: '...', data: '27/06/2026' }