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