Skip to content

RN Lezione 14: Progetto — Sviluppo CRUD

  • Implementare CRUD completo (Create, Read, Update, Delete)
  • Collegare form → salvataggio → visualizzazione
  • Usare lo stato globale tra schermate

Ogni app ha bisogno di queste operazioni:

OperazioneAzioneSchermata
CreateAggiungere elementoForm / Aggiungi
ReadVisualizzare elementiHome / Lista
UpdateModificare elementoModifica / Dettaglio
DeleteEliminare elementoCon Alert
[Form Aggiungi] → salvaDati(nuovo) → AsyncStorage
[Home Lista] ← caricaDati() ← AsyncStorage
├→ clicca → [Dettaglio] (read-only)
└→ long-press → Alert → eliminaDati(id)

// app/_layout.js — stato centrale
import { useState, useEffect } from 'react';
import { Stack } from 'expo-router';
import { caricaDati, salvaDati } from '../utils/storage';
export default function Layout() {
const [dati, setDati] = useState([]);
useEffect(() => {
caricaDati().then(setDati);
}, []);
useEffect(() => {
if (dati.length) salvaDati(dati);
}, [dati]);
// Passa dati alle schermate via context...
}
// Da Home → Dettaglio (passa ID)
router.push({
pathname: '/dettaglio',
params: { id: item.id }
});
// In Dettaglio — carica dati da AsyncStorage
const { id } = useLocalSearchParams();
const [item, setItem] = useState(null);
useEffect(() => {
const carica = async () => {
const dati = await caricaDati();
const trovato = dati.find(d => d.id === id);
setItem(trovato);
};
carica();
}, [id]);

app/aggiungi.js
import { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Alert, StyleSheet } from 'react-native';
import { router } from 'expo-router';
import AsyncStorage from '@react-native-async-storage/async-storage';
const CHIAVE = '@app_dati';
export default function Aggiungi() {
const [titolo, setTitolo] = useState('');
const [testo, setTesto] = useState('');
const [salvataggio, setSalvataggio] = useState(false);
const salva = async () => {
if (!titolo.trim()) {
Alert.alert('Errore', 'Il titolo è obbligatorio');
return;
}
setSalvataggio(true);
try {
// 1. Carica dati esistenti
const json = await AsyncStorage.getItem(CHIAVE);
const dati = json ? JSON.parse(json) : [];
// 2. Aggiungi nuovo elemento
const nuovo = {
id: Date.now().toString(),
titolo: titolo.trim(),
testo: testo.trim(),
data: new Date().toLocaleDateString('it-IT'),
creatoIl: new Date().toISOString(),
};
dati.push(nuovo);
// 3. Salva
await AsyncStorage.setItem(CHIAVE, JSON.stringify(dati));
Alert.alert('✅ Salvato', 'Elemento aggiunto con successo!');
router.back(); // ← torna alla lista
} catch (err) {
Alert.alert('❌ Errore', 'Impossibile salvare i dati');
} finally {
setSalvataggio(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.label}>Titolo</Text>
<TextInput
style={styles.input}
value={titolo}
onChangeText={setTitolo}
placeholder="Inserisci titolo..."
/>
<Text style={styles.label}>Testo</Text>
<TextInput
style={[styles.input, styles.textarea]}
value={testo}
onChangeText={setTesto}
placeholder="Inserisci testo..."
multiline
numberOfLines={5}
/>
<TouchableOpacity
style={[styles.bottone, salvataggio && styles.bottoneDisabile]}
onPress={salva}
disabled={salvataggio}
>
<Text style={styles.bottoneTesto}>
{salvataggio ? 'Salvataggio...' : '💾 Salva'}
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 20, backgroundColor: '#f5f5f5' },
label: { fontSize: 16, fontWeight: 'bold', marginBottom: 5, marginTop: 15 },
input: {
backgroundColor: 'white',
padding: 12,
borderRadius: 8,
borderWidth: 1,
borderColor: '#ddd',
fontSize: 16,
},
textarea: { height: 120, textAlignVertical: 'top' },
bottone: {
backgroundColor: '#3498db',
padding: 15,
borderRadius: 8,
alignItems: 'center',
marginTop: 20,
},
bottoneDisabile: { opacity: 0.6 },
bottoneTesto: { color: 'white', fontWeight: 'bold', fontSize: 16 },
});

app/dettaglio.js
import { View, Text, TouchableOpacity, Alert, StyleSheet } from 'react-native';
import { useLocalSearchParams, router } from 'expo-router';
import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
const CHIAVE = '@app_dati';
export default function Dettaglio() {
const { id } = useLocalSearchParams();
const [item, setItem] = useState(null);
useEffect(() => {
const carica = async () => {
const json = await AsyncStorage.getItem(CHIAVE);
const dati = json ? JSON.parse(json) : [];
const trovato = dati.find(d => d.id === id);
setItem(trovato);
};
carica();
}, [id]);
const elimina = () => {
Alert.alert(
'Elimina',
'Sei sicuro?',
[
{ text: 'Annulla', style: 'cancel' },
{
text: 'Elimina',
style: 'destructive',
onPress: async () => {
const json = await AsyncStorage.getItem(CHIAVE);
const dati = json ? JSON.parse(json) : [];
const filtrati = dati.filter(d => d.id !== id);
await AsyncStorage.setItem(CHIAVE, JSON.stringify(filtrati));
router.back();
}
},
]
);
};
if (!item) {
return (
<View style={styles.center}>
<Text>Caricamento...</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.titolo}>{item.titolo}</Text>
<Text style={styles.data}>{item.data}</Text>
<Text style={styles.testo}>{item.testo}</Text>
<TouchableOpacity style={styles.btnElimina} onPress={elimina}>
<Text style={styles.btnEliminaTesto}>🗑️ Elimina</Text>
</TouchableOpacity>
</View>
);
}

// Aggiungi con logica di modifica (se params.id esiste)
const { id } = useLocalSearchParams();
const [titolo, setTitolo] = useState('');
const isModifica = Boolean(id);
useEffect(() => {
if (id) {
// Carica dati esistenti per modificarli
const carica = async () => {
const json = await AsyncStorage.getItem(CHIAVE);
const dati = json ? JSON.parse(json) : [];
const trovato = dati.find(d => d.id === id);
if (trovato) {
setTitolo(trovato.titolo);
setTesto(trovato.testo);
}
};
carica();
}
}, [id]);
const salva = async () => {
// ... stessa logica, ma se isModifica → aggiorna invece di creare
if (isModifica) {
dati = dati.map(d => d.id === id ? { ...d, titolo, testo } : d);
} else {
dati.push({ id: Date.now().toString(), titolo, testo, data: ... });
}
};

components/Loading.js
import { View, ActivityIndicator, Text } from 'react-native';
export default function Loading({ messaggio = 'Caricamento...' }) {
return (
<View style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
}}>
<ActivityIndicator size="large" color="#3498db" />
<Text style={{ marginTop: 10, color: 'grey' }}>{messaggio}</Text>
</View>
);
}

  • Create: form aggiunge elemento → AsyncStorage
  • Read: FlatList mostra elementi da AsyncStorage
  • Update: form di modifica aggiorna elemento
  • Delete: long-press o bottone elimina con conferma
  • Validazione campi form
  • Gestione lista vuota
  • Navigazione Home ↔ Aggiungi ↔ Dettaglio
  • Test su Expo Go: CRUD funziona su telefono