On this page
React Native Notes App
Create a cross-platform notes app for iOS and Android. Practice React Native components, navigation, list rendering, and local persistence.
Requirements
- Node.js 18+ and npm
- React Native development environment (setup guide)
- Expo CLI (
npx create-expo-app) recommended for beginners - iOS Simulator, Android Emulator, or Expo Go on a physical device
Features
- List all notes with title and preview
- Create and edit notes in a detail screen
- Delete notes with confirmation
- Persist notes locally with AsyncStorage
- Pull-to-refresh on the list
Step 1: Create the Project
npx create-expo-app notes-app
cd notes-app
npx expo install @react-navigation/native @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context
npx expo install @react-native-async-storage/async-storage
Step 2: Note Data Model
src/types.ts
export interface Note {
id: string;
title: string;
body: string;
updatedAt: string;
}
src/storage.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import type { Note } from './types';
const KEY = 'notes';
export async function loadNotes(): Promise<Note[]> {
const data = await AsyncStorage.getItem(KEY);
return data ? JSON.parse(data) : [];
}
export async function saveNotes(notes: Note[]): Promise<void> {
await AsyncStorage.setItem(KEY, JSON.stringify(notes));
}
Step 3: Navigation Setup
App.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import NotesListScreen from './src/screens/NotesListScreen';
import NoteEditorScreen from './src/screens/NoteEditorScreen';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Notes" component={NotesListScreen} options={{ title: 'My Notes' }} />
<Stack.Screen name="Editor" component={NoteEditorScreen} options={{ title: 'Edit Note' }} />
</Stack.Navigator>
</NavigationContainer>
);
}
Step 4: Notes List Screen
src/screens/NotesListScreen.tsx — use FlatList to render notes, useFocusEffect to reload on screen focus, and onRefresh for pull-to-refresh. Long-press a note to delete (with Alert.alert confirmation). Add a floating + button that navigates to the editor.
export default function NotesListScreen({ navigation }) {
const [notes, setNotes] = useState<Note[]>([]);
useFocusEffect(useCallback(() => { loadNotes().then(setNotes); }, []));
return (
<View style={styles.container}>
<FlatList
data={notes}
keyExtractor={item => item.id}
onRefresh={() => loadNotes().then(setNotes)}
ListEmptyComponent={<Text>No notes yet. Tap + to create one.</Text>}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => navigation.navigate('Editor', { note: item })}
onLongPress={() => deleteNote(item.id)}>
<Text style={styles.title}>{item.title || 'Untitled'}</Text>
<Text numberOfLines={2}>{item.body}</Text>
</TouchableOpacity>
)}
/>
<TouchableOpacity style={styles.fab} onPress={() => navigation.navigate('Editor', { note: null })}>
<Text style={styles.fabText}>+</Text>
</TouchableOpacity>
</View>
);
}
Step 5: Note Editor and Run
src/screens/NoteEditorScreen.tsx — load existing note from route.params, provide title and body TextInput fields, and save back to AsyncStorage on a header “Save” button press.
async function handleSave() {
const notes = await loadNotes();
const note = { id: existing?.id ?? Date.now().toString(), title, body, updatedAt: new Date().toISOString() };
const updated = existing ? notes.map(n => n.id === note.id ? note : n) : [note, ...notes];
await saveNotes(updated);
navigation.goBack();
}
Run with npx expo start and test on Expo Go or a simulator.
Extension Ideas
- Search — filter notes by title or body text
- Categories/tags — color-coded labels for organization
- Rich text — use a library like
react-native-pell-rich-editor - Cloud sync — back up notes to Firebase or Supabase
- Biometric lock — protect the app with Face ID or fingerprint
- Share notes — export note content via the system share sheet