Build a Camera App with React Native
Build a working camera app on your phone using Expo and React Native.
Introduction
30 Second Summary
Your phone's camera app is something you use every day without thinking about it. What would it take to build one yourself?
In this project, you will build a camera app called SnapSave using React Native and Expo. You'll capture photos on your physical phone with a single tap and save them directly to your gallery.
What You'll Build
Open SnapSave on your phone, see a live camera feed filling the screen, tap the shutter button to snap a photo, and hit Save to watch it land in your Photos app ready to share.
By the end of this project, you'll have:
- A live camera preview that fills your screen the moment you open the app, updating in real time as you move your phone around.
- A shutter button and preview screen that lets you capture a photo, see it full-screen, and decide whether to keep it or retake it.
- A Save to Gallery feature that permanently saves your photos to your phone's Photos app so they're ready to share with anyone.
- Secret Mission: Add a front/back camera toggle so you can take selfies too.
Do I need a physical phone for this project?
Yes, camera features can only run on a physical device. You'll need a physical iPhone or Android phone, but no prior mobile development experience is required since this guide walks you through every tool from scratch.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your React Native Environment
Every React Native app needs a JavaScript runtime to bundle your code and a way to test on a real device. Before you can write any camera code, you need both of these in place.
In this step, you will install Node.js as your JavaScript runtime, create the SnapSave project using Expo, and get the default app running on your physical phone.
In this step, get ready to:
- Install Node.js and verify it is working.
- Create the SnapSave project and install the camera library.
- Start the development server and see the app running on your phone.
Install Node.js and create the SnapSave project
Node.js powers the JavaScript bundler and development tools that Expo relies on. You need version 20 or higher installed on your computer.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Check if Node.js is already installed by running this command:
node --version
✔️ I see version 20 or higher
You're all set. Your version of Node.js meets the minimum requirement for Expo SDK 54.
ⓧ I see an older version
Expo SDK 54 requires Node.js version 20 or higher. Your current version is too old and needs to be upgraded.
- Visit nodejs.org and download the LTS version.
- Run the installer and follow the prompts.
- Close your terminal and reopen it.
- Confirm the upgrade by running:
node --version
You should now see a version number starting with v20 or higher.
ⓧ Command not found
Node.js is not installed on your computer yet.
- Visit nodejs.org and download the LTS version.
- Run the installer and follow the prompts.
- Close your terminal and reopen it.
- Confirm the installation by running:
node --version
You should see a version number starting with v20 or higher.
- Move to your Desktop by running this command:
cd ~/Desktop
- The next command uses npx to download a project creator tool. If your terminal asks Ok to proceed? (y), type y and press Enter.
- Create the SnapSave project by running this command:
npx create-expo-app@latest SnapSave --template blank@sdk-54
What does this command do?
The --template blank@sdk-54 flag tells Expo to create a minimal project with a single App.js file and no navigation setup. This keeps the starting code simple so you can focus on building the camera.
The command takes about a minute to download and install all dependencies. Wait until you see a success message before continuing.
Seeing an error during project creation?
- Make sure you have a stable internet connection. The command downloads packages from the npm registry.
- Confirm you are in the Desktop directory by running pwd in your terminal.
- Help me fix my Expo project creation error.
- Move into the new project folder by running:
cd SnapSave
Install Expo Go and the camera library
Your phone is the testing device for this project. Camera features do not work in simulators or emulators, so you need a physical device with the Expo Go app installed.
- Pick up your iPhone or Android device.
- Open the App Store (iPhone) or Google Play Store (Android).
- Search for Expo Go and install the app.
- Open Expo Go on your phone to confirm it installed successfully.
What is Expo Go?
Expo Go is a companion app that runs your React Native project directly on your phone. It connects to your computer's development server over Wi-Fi and reloads the app automatically whenever you save a code change.
- Back in your terminal, install the camera library by running this command:
npx expo install expo-camera
Seeing an install error?
- Make sure you are inside the SnapSave directory. Run pwd to check.
- Check that your internet connection is stable.
- Help me fix my expo-camera install error.
Start the development server and connect your phone
Everything is installed. It is time to start the development server and see the app running live on your phone.
- Make sure your phone and your computer are connected to the same Wi-Fi network.
- Start the Expo development server by running this command:
npx expo start
What you will see in the terminal
The command starts a local development server and prints a large QR code in your terminal. You will scan this QR code with your phone in the next step. The server must stay running the entire time you are developing.
Keep this terminal window open
The development server takes over this terminal window. If you need to run more commands later, open a second terminal window. Closing this terminal or pressing Ctrl+C stops the server and disconnects the app from your phone.
Before you scan the QR code, what do you think you will see on your phone?
- On iPhone, open your Camera app and point it at the QR code in your terminal. Tap the notification banner that appears at the top of the screen.
- On Android, open the Expo Go app and tap Scan QR code. Point your camera at the QR code in your terminal.
Your phone loads the app and displays a white screen with the text Open up App.js to start working on your app! centered on the screen. This is the default blank template.
App not loading on your phone?
- Confirm your phone and computer are on the same Wi-Fi network. A VPN or firewall can also block the connection.
- On Android, make sure you scanned the QR code from inside the Expo Go app, not from the system camera.
- Try stopping the server with Ctrl+C and restarting it with npx expo start.
- Help me connect my phone to the Expo development server.
Your development environment is ready and the app is live on your phone. Next up, you will replace that placeholder text with a live camera feed.
Show a Live Camera Preview
Your SnapSave app is running on your phone, but all you see is a blank screen with placeholder text. The foundation of any camera app is a live preview, so that is exactly what you will build first.
Replacing the placeholder with a live camera feed also means handling your first mobile permission. On phones, apps cannot access the camera without the user explicitly granting access. Your component will check for that permission and show the right screen depending on the answer.
In this step, get ready to:
- Handle camera permissions so the app can request access to the device camera.
- Render a live camera feed that fills the entire screen.
Replace App.js with the camera component
The default App.js currently renders a centered text label. You will replace its entire contents with a new component that checks camera permissions and renders a CameraView.
- In VS Code, open App.js inside your SnapSave folder.
- Select all the existing code and delete it.
- Paste the following component code into the now-empty file:
// Import camera components from expo-camera
import { CameraView, useCameraPermissions } from 'expo-camera';
import { StyleSheet, Text, View, Button } from 'react-native';
export default function App() {
// Hook that checks and manages camera permission
const [permission, requestPermission] = useCameraPermissions();
// While permission status is loading, show an empty screen
if (!permission) {
return <View />;
}
// If permission has not been granted, show a request screen
if (!permission.granted) {
return (
<View style={styles.container}>
<Text style={styles.message}>We need your permission to use the camera</Text>
<Button onPress={requestPermission} title="Grant Permission" />
</View>
);
}
// Permission granted - show the live camera feed
return (
<View style={styles.container}>
<CameraView style={{ flex: 1 }} />
</View>
);
}
What does this code do?
useCameraPermissions is a hook from expo-camera that returns the current permission status and a function to request it. The component checks permission.granted and renders one of three screens depending on the result.
- If permission is null (still loading), the app shows a blank screen to avoid a flash of content.
- If permission is not granted, the app shows a message and a Grant Permission button that triggers the system permission dialog.
- If permission is granted, a CameraView fills the screen with a live camera feed using style={{ flex: 1 }}.
- Below the closing brace of the App function, add the styles that the component references:
const styles = StyleSheet.create({
// Dark background for the camera screens
container: {
flex: 1,
backgroundColor: '#000',
},
// Style for the permission request text
message: {
textAlign: 'center',
paddingBottom: 10,
color: '#fff',
fontSize: 16,
marginTop: 100,
},
});
What do these styles do?
container uses flex: 1 to stretch across the entire screen with a black background. The black background matches what you would expect behind a camera viewfinder.
message styles the permission request text in white, centered at the top of the screen so it is easy to read against the dark background.
- Save App.js.
- Check your phone. Expo Go reloads automatically. You should see a dark screen with the text "We need your permission to use the camera" and a Grant Permission button below it.
Don't see the permission screen?
- Make sure you deleted all the old code before pasting. Leftover lines from the blank template can cause syntax errors.
- Check that both code blocks are in App.js in order. The component code comes first, then the StyleSheet block below it.
- Look at the Expo Go screen for a red error banner. It usually points to the exact line with the problem.
Still stuck? Help me fix the permission screen in my React Native camera app.
✔️ Awesome, I've got everything!
Great. Make sure you have saved App.js before moving on.
ⓧ I'd like to double check the full code
Scroll up and confirm that App.js contains both code blocks in this order:
- The component code starting with import { CameraView, useCameraPermissions } and ending with the closing brace of the App function.
- The StyleSheet.create block with container and message styles directly below the function.
There should be no leftover code from the original blank template.
Grant permission and see the live camera feed
The permission screen is your app's front door. Until the user taps Grant Permission, the camera stays off.
- Tap the Grant Permission button on your phone.
- A system dialog appears asking for camera access. Tap Allow.
Before you look at the result, what do you expect to see now that the app has camera access?
The live camera feed fills your entire screen. Move your phone around and watch the camera view update in real time. That is your React Native app talking directly to the device hardware through expo-camera.
What is CameraView?
CameraView is a React Native component from the expo-camera library. It connects to your device's camera hardware and streams the live feed as a visual element you can size and position like any other component.
Setting style={{ flex: 1 }} tells the view to expand and fill all available space, which is why the camera feed covers the entire screen.
Seeing a black screen instead of the camera feed?
- Camera features do not work in iOS Simulator or Android Emulator. You must test on a physical device.
- If you denied the permission by mistake, close Expo Go completely, then reopen it and scan the QR code again. The app will ask for permission a second time.
- Make sure nothing is covering your phone's camera lens.
Need more help? Help me get the live camera feed working in my Expo Go app.
Your app has gone from a blank placeholder to a live camera feed in a single step. Next up, you will add a shutter button to actually capture photos.
Capture a Photo and Preview It
Your camera feed is live on your phone. That is a great start, but a camera that can only show what it sees is not very useful.
In this step, you will add a shutter button that captures a photo and displays a full-screen preview. You will also build Retake and Save to Gallery buttons so the user can decide what to do with each shot. Along the way, you will discover something surprising about where captured photos actually end up.
In this step, get ready to:
- Wire up a camera ref and capture function to take photos.
- Build a shutter button and photo preview screen.
- Discover where captured photos actually go.
Set up the camera ref and capture function
To capture a photo, you need two things: a reference to the camera so you can tell it to snap, and a piece of state to hold the resulting photo. React Native provides useRef for the first job and useState for the second.
- In App.js, find the two import lines at the top of the file:
import { CameraView, useCameraPermissions } from 'expo-camera';
import { StyleSheet, Text, View, Button } from 'react-native';
- Replace both lines with these three import lines:
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useState, useRef } from 'react';
import { Button, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
What changed in the imports?
You added a new line that pulls in useState and useRef from React. These are hooks that let your component remember data (the photo) and hold a direct handle to the camera.
The react-native import now includes Image (to display the captured photo) and TouchableOpacity (to build tappable buttons that dim when pressed).
- Find the line const [permission, requestPermission] = useCameraPermissions(); and add these two lines directly below it:
const [photo, setPhoto] = useState(null);
const cameraRef = useRef(null);
- Now scroll down past the if (!permission.granted) block. Add the takePicture function right below that block's closing } by copying this code:
const takePicture = async () => {
if (cameraRef.current) {
// Snap a photo and store the file path
const result = await cameraRef.current.takePictureAsync();
setPhoto(result.uri);
}
};
What does this code do?
- photo holds the file path of the last captured image. It starts as null (no photo taken yet).
- cameraRef is a direct reference to the CameraView component. You need this so you can call its takePictureAsync() method.
- takePicture is an async function that snaps a photo and saves the returned uri (file path) into state.
- Save App.js.
- Check your phone. The app should still show the live camera feed with no errors.
Seeing a red error screen?
- Make sure the new import { useState, useRef } from 'react'; line is on its own line, separate from the expo-camera import.
- Check that Image and TouchableOpacity are inside the react-native import's curly braces, not on a separate import line.
- help me fix a red error screen in my Expo camera app after updating imports
Add the shutter button
The capture function is ready, but the learner has no way to trigger it. You need a tappable button overlaid on top of the camera feed, plus a ref prop on CameraView so the function can reach the camera.
- Find the return statement at the bottom of your component (the one that currently renders <CameraView style={{ flex: 1 }} />). Replace the entire return block with this code:
return (
<View style={styles.container}>
<CameraView style={styles.camera} ref={cameraRef}>
<View style={styles.captureContainer}>
<TouchableOpacity style={styles.captureButton} onPress={takePicture}>
<View style={styles.captureInner} />
</TouchableOpacity>
</View>
</CameraView>
</View>
);
- Inside the styles object, add these new style definitions after the existing message style:
camera: {
flex: 1,
},
captureContainer: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
paddingBottom: 40,
},
captureButton: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: 'rgba(255, 255, 255, 0.3)',
justifyContent: 'center',
alignItems: 'center',
},
captureInner: {
width: 65,
height: 65,
borderRadius: 33,
backgroundColor: '#fff',
},
What does this code do?
- CameraView now receives ref={cameraRef}, which connects the camera reference so takePicture can call takePictureAsync() on it.
- The shutter button is two nested circles. The outer captureButton is a translucent ring, and the inner captureInner is a solid white circle. This mimics the classic camera shutter look.
- captureContainer pushes the button to the bottom center of the screen using justifyContent: 'flex-end'.
- Save App.js and check your phone.
- You should see a white circular shutter button at the bottom of the camera view. Do not tap it yet.
Don't see the shutter button?
- Make sure you added all four new styles (camera, captureContainer, captureButton, captureInner) inside the StyleSheet.create({}) block, with commas between them.
- Check that the return block uses styles.camera instead of the old inline {{ flex: 1 }} style.
- help me figure out why my shutter button isn't showing up on the camera view
Build the preview screen
When the learner taps the shutter, the app needs to show the captured photo instead of the camera feed. You will build a conditional screen that displays the photo full-screen with Retake and Save to Gallery buttons.
- In App.js, find the takePicture function you just added. Directly below it (and above the final return statement), add this conditional block:
if (photo) {
return (
<View style={styles.container}>
<Image source={{ uri: photo }} style={styles.preview} />
<View style={styles.buttonRow}>
<TouchableOpacity style={styles.button} onPress={() => setPhoto(null)}>
<Text style={styles.buttonText}>Retake</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.button, styles.saveButton]} onPress={() => {}}>
<Text style={styles.buttonText}>Save to Gallery</Text>
</TouchableOpacity>
</View>
</View>
);
}
- Add these styles after the captureInner style you added earlier:
preview: {
flex: 1,
},
buttonRow: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 20,
},
button: {
backgroundColor: '#333',
paddingVertical: 15,
paddingHorizontal: 30,
borderRadius: 10,
},
saveButton: {
backgroundColor: '#2196F3',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
What does this code do?
- The if (photo) check runs before the camera return. When a photo exists, the component returns the preview screen instead of the camera view.
- Image displays the captured photo using the uri that takePictureAsync() returned.
- Retake clears the photo state (setPhoto(null)), which makes the if (photo) check fail, so the camera view renders again.
- Save to Gallery has an empty onPress handler for now. You will connect it in the next step.
- Save App.js and check your phone.
- Tap the shutter button. You should see your captured photo displayed full-screen, with Retake and Save to Gallery buttons below it.
- Tap Retake. You should return to the live camera view.
Photo preview not appearing?
- Make sure the if (photo) block is placed above the final return statement (the one with the camera view), not below it.
- Confirm you added the preview style. Without flex: 1, the image will have zero height and be invisible.
- help me debug why tapping the shutter button does not show a photo preview
✔️ Awesome, I've got everything!
Great. Double check you have saved App.js.
ⓧ I'd like to double check the full code
Your App.js is now too long for a single snippet. Scroll through your file and confirm these sections are present in order:
- Three import lines: expo-camera, react (useState/useRef), and react-native (Button, Image, StyleSheet, Text, TouchableOpacity, View).
- Inside App(): useCameraPermissions() hook, then useState(null) for photo, then useRef(null) for cameraRef.
- Two permission guard blocks (if (!permission) and if (!permission.granted)).
- The takePicture async function that calls cameraRef.current.takePictureAsync().
- The if (photo) conditional return showing the Image preview with Retake and Save to Gallery buttons.
- The final return with CameraView using ref={cameraRef} and the shutter button inside.
- Styles: container, message, camera, preview, captureContainer, captureButton, captureInner, buttonRow, button, saveButton, buttonText.
Check where your photos went
You have a working capture flow. Before you move on, take a moment to investigate what happens to the photos you just took.
- Take a photo using the shutter button.
- Tap Retake to go back to the camera.
- Take one more photo so you have at least two captured images.
Before you check, what do you think will happen if you open your phone's Photos app right now? Will the photos be there?
- Close or minimize SnapSave and open your phone's Photos app (iPhone) or Gallery app (Android).
- Look through your recent photos.
The photos are not there. Every photo you captured has vanished.
Why are my photos missing?
The takePictureAsync() method saves the photo to the app's temporary cache directory, not to your device's permanent photo gallery. This cached file gets deleted when the app closes or when the system reclaims storage.
To save photos permanently, you need a separate library that writes to the device's media gallery. That is exactly what you will add in the next step.
Your camera app can capture and preview photos, but they disappear the moment the app closes. Next up, you will connect the media library so that tapping Save to Gallery actually saves the photo to your phone forever.
Save Photos to Your Gallery
In the last step, you discovered a gap. Your captured photos live only in the app's temporary cache. Close the app and they vanish.
The fix is a separate library called expo-media-library. It gives your app the ability to write photos into your phone's permanent photo gallery, so every save survives app restarts and shows up alongside your regular photos.
In this step, get ready to:
- Install the expo-media-library package and add the new imports.
- Write a savePhoto function that persists photos to your device gallery.
- Test the full capture-and-save flow on your phone.
Install the media library
The expo-camera library handles the live preview and capture, but it has no ability to write to the device's photo gallery. A separate library handles that.
- Open a new terminal tab or window. Your Expo dev server is still running in the existing terminal, so you need a fresh one.
- Navigate to your SnapSave project folder by running:
cd ~/Desktop/SnapSave
- Install expo-media-library by running this command:
npx expo install expo-media-library
You should see the package install successfully. The output confirms the dependency was added to your project.
Install failed?
- Make sure you are inside the SnapSave directory. Run ls and confirm you see App.js and package.json in the output.
- Check your internet connection. This command downloads the package from the npm registry.
- Help me troubleshoot my expo-media-library install
Now you need to import the library in your code so your app can use it.
- Switch back to VS Code and open App.js.
- Find the import lines at the top of your file:
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useState, useRef } from 'react';
import { Button, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
- Add a new line import * as MediaLibrary from 'expo-media-library'; directly after the first import.
- Add Alert to the beginning of the react-native import, so the updated imports look like this:
import { CameraView, useCameraPermissions } from 'expo-camera';
import * as MediaLibrary from 'expo-media-library';
import { useState, useRef } from 'react';
import { Alert, Button, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
- Save App.js. The app should reload on your phone without crashing.
App crashing after adding imports?
- If you see a red error screen mentioning the expo-media-library module, stop the dev server with Ctrl+C and restart it with npx expo start. The server sometimes needs a restart to pick up newly installed packages.
- Double-check that Alert is spelled with a capital A and is inside the curly braces of the react-native import.
- Help me fix my import errors in App.js
Write the save function
The savePhoto function needs to do three things: ask for permission to access the gallery, save the photo file permanently, and confirm success to the user.
- In App.js, find the takePicture function. Add the following savePhoto function directly below it:
const savePhoto = async () => {
if (photo) {
// Request permission to access the photo gallery
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status === 'granted') {
// Save the cached photo file to the device permanently
await MediaLibrary.createAssetAsync(photo);
Alert.alert('Saved!', 'Photo saved to your gallery.');
// Return to the live camera view
setPhoto(null);
} else {
Alert.alert('Permission needed', 'Please allow access to save photos.');
}
}
};
What does this function do?
- MediaLibrary.requestPermissionsAsync() asks the user for permission to access their photo gallery. This is a separate permission from the camera access you granted earlier.
- MediaLibrary.createAssetAsync(photo) takes the temporary cached photo file and writes a permanent copy into the device's photo gallery. This is the line that makes photos persist.
- Alert.alert() shows a native popup confirming the save succeeded. After the alert, setPhoto(null) returns the user to the live camera view, ready for the next shot.
The function exists now, but the Save to Gallery button is still wired to an empty function. You need to connect it.
- Scroll down to the preview screen section and find this line:
<TouchableOpacity style={[styles.button, styles.saveButton]} onPress={() => {}}>
- Replace onPress={() => {}} with onPress={savePhoto}, so the line now looks like this:
<TouchableOpacity style={[styles.button, styles.saveButton]} onPress={savePhoto}>
- Save App.js.
Test the full save flow
Before you test, think about what you expect to happen when you tap Save to Gallery for the first time. Will the photo save immediately, or will something else happen first?
- On your phone, tap the shutter button to capture a photo.
- Tap Save to Gallery.
- Your phone asks for a new permission. This time it is gallery access, not camera access. Tap Allow.
- A popup appears with the message "Saved! Photo saved to your gallery." Tap OK.
The app returns to the live camera view. Now for the real test.
- Open your phone's Photos app (iPhone) or Gallery app (Android).
- Find the photo you just saved. It is right there in your camera roll, permanent and shareable.
Why two separate permissions?
Mobile operating systems treat the camera and the photo gallery as separate resources. Camera permission lets your app see through the lens. Gallery permission lets your app write files into the user's photo storage. Each one needs its own explicit approval.
Your app only asks for gallery permission the first time. After that, the OS remembers the choice and skips the prompt on future saves.
Photo not appearing in your gallery?
- If you see the "Permission needed" alert instead of "Saved!", you tapped Don't Allow on the gallery permission prompt. Go to your phone's Settings, find the Expo Go app, and enable photo library access.
- Make sure you tapped Save to Gallery, not Retake. Retake discards the photo and returns to the camera.
- If the dev server restarted after you installed the library, scan the QR code in your terminal again to reconnect.
- Help me figure out why my photo is not saving to the gallery
✔️ Awesome, I've got everything!
Great. Double check you have saved App.js and that your saved photo appears in your phone's gallery.
ⓧ I'd like to double check the full code
Your App.js file is longer than 30 lines, so here is a checklist instead. Scroll up and confirm each section is present in this order:
- The four import lines at the top, including import * as MediaLibrary from 'expo-media-library' and Alert in the react-native import.
- The permission, photo, and cameraRef declarations, unchanged from the previous step.
- The permission-not-granted screen with the Grant Permission button, unchanged.
- The takePicture function, unchanged.
- The new savePhoto function directly below takePicture, calling MediaLibrary.requestPermissionsAsync() and MediaLibrary.createAssetAsync(photo).
- The preview screen with onPress={savePhoto} on the Save to Gallery button (not onPress={() => {}}).
- The camera view and all styles, unchanged from the previous step.
Secret mission
Add a Camera Flip Button
Your SnapSave app only uses the back camera right now. Can you add a flip button that toggles between the front and back cameras so you can take selfies too?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally on your computer and phone, so there are no ongoing costs. Decide whether to keep your resources around, pause them to come back later, or delete them entirely.
Resources you used:
- The SnapSave project folder on your Desktop.
- The Expo Go app on your phone.
- Test photos saved to your phone's gallery during the project.
Keep everything running
No action needed. Choose this if you are still actively building or want to extend the app further.
- Your SnapSave folder stays on your Desktop, ready to open any time.
- Expo Go stays on your phone so you can scan a QR code and test instantly.
- Node.js remains installed on your computer. Other projects may depend on it.
Pause - I'll come back to this later
Stop the running dev server to free up your terminal, but keep all project files so you can pick up where you left off.
- If the Expo dev server is still running in your terminal, press Ctrl+C to stop it.
- Your SnapSave folder and all its code stay on your Desktop.
- When you are ready to resume, open your terminal, navigate back to the SnapSave folder, and run npx expo start again.
Delete - I don't want to use this again
Remove all project files and apps to start completely fresh.
- If the Expo dev server is still running, press Ctrl+C to stop it.
- Delete the SnapSave project folder by running this command in your terminal:
rm -rf ~/Desktop/SnapSave
- Confirm the folder is gone by running:
ls ~/Desktop | grep SnapSave
You should see no output, confirming the folder has been removed.
- Uninstall Expo Go from your phone. On iPhone, long-press the Expo Go icon, then tap Remove App. On Android, long-press the icon and drag it to Uninstall.
- Delete any test photos from your phone's Photos app. Open Photos, find the photos you took during this project, and delete them.
Nice Work!
Nice Work!
You just built a fully functional camera app with React Native and Expo. You can open SnapSave on your phone, snap a photo, preview it, and save it straight to your gallery.
You learned how to:
- Built a React Native camera app from scratch using Expo and expo-camera, handling camera permissions with useCameraPermissions so the app gracefully requests access on first launch.
- Captured photos using CameraView refs and takePictureAsync, then discovered that captured photos only live in the app's temporary cache and vanish when the app closes.
- Saved photos permanently to the device gallery using MediaLibrary.createAssetAsync, turning throwaway cached files into real photos in the phone's Photos app.
- Secret Mission: Added front/back camera switching with a flip button wired to the CameraView facing prop, so you can take selfies too.
Ready to quiz yourself?