Let built expo image picker custom component, so that we can reused it in all over our Application. The CustomImagePicker component, designed for React Native applications, utilizes the expo-image-picker library to enable users to either capture a new image using their camera or select a pre-existing one from their photo library.
Table of Contents
Expo image picker custom component, What we are learning here
This article delves into essential concepts and functionalities employed within the component, such as PropTypes, the useCallback hook, and various ImagePicker settings in expo Image Picker. These elements are vital for ensuring the component’s adaptability, efficiency, and overall performance.
Here is screenshot of React native custom image picker component.
Step 1: Installing Expo Image Picker
To create custom image picker using the Expo ImagePicker component, you first need to install the expo-image-picker
package. You can do this by running the following command in your project directory:
npx expo install expo-image-picker
We need to configure expo-image-picker
using its built-in config plugin if you use config plugins in your project (EAS Build or npx expo run:[android|ios]
). The plugin allows you to configure various properties that cannot be set at runtime and require building a new app binary to take effect.
...
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
[
"expo-image-picker",
{
"photosPermission": "The app accesses your photos to let you share them with your friends."
}
]
]
}
}
Creating Expo image picker custom component
To use the Expo ImagePicker component to create custom image picker, you first need to import it at the top of your file:
import * as ImagePicker from 'expo-image-picker';
Let create a file called CustomImagePicker.jsx inside component to demonstrate expo image picker example.
import React, { useState, useEffect, useCallback } from "react";
import { View, Button, Alert, StyleSheet } from "react-native";
import * as ImagePicker from "expo-image-picker";
import PropTypes from "prop-types"; // If you're not using TypeScript
const CustomImagePicker = ({ style, buttonStyle, onImagePicked }) => {
const [hasPermission, setHasPermission] = useState(null);
useEffect(() => {
(async () => {
const cameraStatus = await ImagePicker.requestCameraPermissionsAsync();
const libraryStatus =
await ImagePicker.requestMediaLibraryPermissionsAsync();
setHasPermission(
cameraStatus.status === "granted" && libraryStatus.status === "granted"
);
})();
}, []);
const handleImagePicked = useCallback(
(result) => {
if (!result.canceled) {
if (onImagePicked) {
onImagePicked(result.assets[0].uri);
}
}
},
[onImagePicked]
);
const takePhoto = async () => {
if (!hasPermission) {
Alert.alert(
"Permissions required",
"Sorry, we need camera and library permissions to make this work!"
);
return;
}
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
handleImagePicked(result);
};
const pickImage = async () => {
if (!hasPermission) {
Alert.alert(
"Permissions required",
"Sorry, we need camera and library permissions to make this work!"
);
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
handleImagePicked(result);
};
return (
<View style={[styles.container, style]}>
<Button
title="Take Photo"
onPress={takePhoto}
style={[styles.button, buttonStyle]}
/>
<Button
title="Pick Image"
onPress={pickImage}
style={[styles.button, buttonStyle]}
/>
</View>
);
};
// Updating Prop Types for validation
CustomImagePicker.propTypes = {
style: PropTypes.object,
buttonStyle: PropTypes.object,
onImagePicked: PropTypes.func.isRequired, // Made this prop required as it's essential for functionality
};
// Updating Default props
CustomImagePicker.defaultProps = {
style: {},
buttonStyle: {},
};
const styles = StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 5,
},
button: {
marginTop: 10,
},
});
export default CustomImagePicker;
We have to use the ImagePicker.launchImageLibraryAsync
method to open the device’s camera roll and select an image. This method returns a Promise that resolves to an object containing information about the selected image, including its URI (Uniform Resource Identifier).
Importants points to notes on above tutorial
PropTypes
PropTypes
in React (including React Native) is a library that helps in validating the types of the props passed to components. Use it in jsx file only but for typescript we can create type based on our needs. This is crucial for ensuring components are used correctly and receive the expected data types. In the CustomImagePicker
component, PropTypes
are used to validate that:
style
andbuttonStyle
are objects. This allows for custom styles to be applied to the component and its buttons.onImagePicked
is a function and is required. This prop function is called with the URI of the picked image, ensuring the parent component can handle the selected image.
Using PropTypes
helps prevent bugs and makes the component more predictable and easier to debug.
useCallback Hook
The useCallback
is a React hook that returns a memoized version of the callback function that only changes if one of the dependencies has changed. This is useful for passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.
In the CustomImagePicker
component, useCallback
wraps the handleImagePicked
function. This ensures that the function identity remains constant unless onImagePicked
changes, which helps in avoiding unnecessary re-renders and thereby optimizing performance.
MediaTypes and Other Options in Expo ImagePicker
The expo-image-picker
module provides options to customize how the image picker behaves. In the CustomImagePicker
component, these options are utilized in both launchCameraAsync
and launchImageLibraryAsync
methods:
mediaTypes
: Specifies the type of media to select. In this component,ImagePicker.MediaTypeOptions.All
allows any type of media to be selected when taking a new photo, whileImagePicker.MediaTypeOptions.Images
restricts the library selection to images only.allowsEditing
: Enables users to edit the image (e.g., crop) before confirming their selection. This is set totrue
to allow editing.aspect
: Sets the aspect ratio for the image cropper.[4, 3]
is used as the aspect ratio in this component.quality
: Determines the quality of the compressed image, ranging from 0 to 1. Setting this to1
ensures the highest quality.
These options provide significant flexibility, allowing the component to be tailored to the needs of different applications.
Permissions Handling
Before accessing the camera or media library, permission must be granted by the user. The component uses ImagePicker.requestCameraPermissionsAsync
and ImagePicker.requestMediaLibraryPermissionsAsync
to request these permissions asynchronously. If permissions are not granted, the component alerts the user and prevents the image picker from opening.
This approach ensures the application respects user privacy and system policies, providing a better user experience and compliance with platform guidelines.
Custom Image picker component using typescript
import React, { useState, useEffect } from "react";
import { View, Button, Image, Alert, StyleSheet } from "react-native";
import * as ImagePicker from "expo-image-picker";
interface ImagePickerButtonProps {
title: string;
onPress: () => void;
}
const ImagePickerButton: React.FC<ImagePickerButtonProps> = ({
title,
onPress,
}) => <Button title={title} onPress={onPress} />;
interface CustomImagePickerProps {
onImagePicked: (uri: string) => void;
}
const requestCameraPermission = async () => {
const { status } = await ImagePicker.requestCameraPermissionsAsync();
return status === 'granted';
};
const requestLibraryPermission = async () => {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
return status === 'granted';
};
const hasAllPermissionsGranted = async () => {
const [cameraPermission, libraryPermission] = await Promise.all([
requestCameraPermission(),
requestLibraryPermission(),
]);
return cameraPermission && libraryPermission;
};
const CustomImagePicker: React.FC<CustomImagePickerProps> = ({
onImagePicked,
}) => {
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
useEffect(() => {
(async () => {
const hasPermissions = await hasAllPermissionsGranted();
setHasPermission(hasPermissions);
})();
}, []);
const takePhoto = async () => {
if (!hasPermission) {
Alert.alert("Sorry, we need permissions to access camera and storage.");
return;
}
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.canceled && result.assets) {
onImagePicked(result.assets[0].uri);
}
};
const pickImage = async () => {
if (!hasPermission) {
Alert.alert("Sorry, we need permissions to access storage.");
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.canceled && result.assets) {
onImagePicked(result.assets[0].uri);
}
};
return (
<View style={styles.container}>
<ImagePickerButton title="Take Photo" onPress={takePhoto} />
<ImagePickerButton title="Pick Image" onPress={pickImage} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
},
image: {
width: 200,
height: 200,
},
button: {
// Define your button styles here
},
});
export default CustomImagePicker;