Skip to content

RN Extra — Template Starter

Progetto Expo completo con navigazione, persistenza e componenti base.

Terminal window
npx create-expo-app@latest MioProgetto --template blank
cd MioProgetto
npx expo install @react-native-async-storage/async-storage expo-router
npx expo install @expo/vector-icons
MioProgetto/
├── app/
│ ├── _layout.js
│ ├── index.js
│ └── (tabs)/
│ ├── _layout.js
│ ├── index.js
│ └── profilo.js
├── components/
│ ├── Card.js
│ └── Loading.js
├── utils/
│ └── storage.js
└── app.json
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen
name="index"
options={{ headerShown: false }}
/>
<Stack.Screen
name="(tabs)"
options={{ headerShown: false }}
/>
</Stack>
);
}
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { router } from 'expo-router';
export default function Welcome() {
return (
<View style={styles.container}>
<Text style={styles.emoji}>📱</Text>
<Text style={styles.titolo}>Mia App</Text>
<Text style={styles.sottotitolo}>La mia prima app React Native</Text>
<TouchableOpacity
style={styles.bottone}
onPress={() => router.replace('/(tabs)')}
>
<Text style={styles.bottoneTesto}>Inizia</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#3498db',
padding: 30,
},
emoji: { fontSize: 60, marginBottom: 20 },
titolo: { fontSize: 32, fontWeight: 'bold', color: 'white', marginBottom: 10 },
sottotitolo: { fontSize: 16, color: 'rgba(255,255,255,0.8)', marginBottom: 40 },
bottone: {
backgroundColor: 'white',
paddingHorizontal: 40,
paddingVertical: 15,
borderRadius: 25,
},
bottoneTesto: { color: '#3498db', fontWeight: 'bold', fontSize: 18 },
});
import { View, Text, StyleSheet } from 'react-native';
export default function Card({ children, style }) {
return (
<View style={[styles.card, style]}>
{children}
</View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: 'white',
padding: 15,
borderRadius: 12,
marginBottom: 10,
elevation: 2,
shadowColor: '#000',
shadowOpacity: 0.08,
shadowRadius: 8,
shadowOffset: { width: 0, height: 2 },
},
});
import { View, ActivityIndicator, Text, StyleSheet } from 'react-native';
export default function Loading({ message = 'Caricamento...' }) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#3498db" />
<Text style={styles.text}>{message}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
text: {
marginTop: 10,
color: 'grey',
fontSize: 16,
},
});
import AsyncStorage from '@react-native-async-storage/async-storage';
export const saveData = async (key, data) => {
try {
await AsyncStorage.setItem(key, JSON.stringify(data));
return true;
} catch (e) {
console.error('Save error:', e);
return false;
}
};
export const loadData = async (key) => {
try {
const json = await AsyncStorage.getItem(key);
return json ? JSON.parse(json) : [];
} catch (e) {
console.error('Load error:', e);
return [];
}
};
export const removeData = async (key) => {
try {
await AsyncStorage.removeItem(key);
return true;
} catch (e) {
console.error('Remove error:', e);
return false;
}
};
const BASE_URL = 'https://jsonplaceholder.typicode.com';
export const fetchData = async (endpoint) => {
const res = await fetch(`${BASE_URL}/${endpoint}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
};
export const postData = async (endpoint, data) => {
const res = await fetch(`${BASE_URL}/${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
};