Edupala

Comprehensive Full Stack Development Tutorial: Learn Ionic, Angular, React, React Native, and Node.js with JavaScript

React Native expo image picker custom component

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.

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.

expo image picker custom 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 and buttonStyle 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, while ImagePicker.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 to true 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 to 1 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;
React Native expo image picker custom component

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top