Torch Component in React-Native/Expo

Create a torch component in React-Native/Expo for easy camera flash control.

4th March 2024

Time to read: 1 min

Logo

Introduction

Let's walk through the process of creating a simple yet effective torch component in React-Native/Expo. This component allows users to control the flash of their device's camera effortlessly.

Jump to Full Code

Prerequisites

Make sure you have a React-Native/Expo project set up.

To install expo-camera and @expo/vector-icons, run the following commands in your terminal:

npm install expo-camera
npm install @expo/vector-icons

Implementation

Let's break down the code step by step:

1. Dependencies

import React, { useState, useEffect, useRef } from "react";
import { View, Pressable, Text } from "react-native";
import { useFocusEffect } from "@react-navigation/native";
import { Camera } from "expo-camera";
import { MaterialIcons } from "@expo/vector-icons";

We import the necessary dependencies, including React, React Native components, and Expo Camera.

2. Component Setup

const Torch = () => {
  const [hasPermission, setHasPermission] = useState(null);
  const [torchOn, setTorchOn] = useState(false);
  const [flashMode, setFlashMode] = useState(0);
  const cameraRef = useRef(null);

We set up the state variables and a camera reference using the useRef hook.

3. Permission Handling

  useFocusEffect(
    React.useCallback(() => {
      const requestCameraPermission = async () => {
        const { status } = await Camera.requestCameraPermissionsAsync();
        setHasPermission(status === "granted");
      };

      setTorchOn(false);
      requestCameraPermission();

      return () => {
        Camera.defaultProps.flashMode = Camera.Constants.FlashMode.off;
        setHasPermission(null);
        setTorchOn(false);
      };
    }, [])
  );

We use the useFocusEffect hook to request camera permissions when the component is focused and clean up when it is unfocused.

4. Torch Toggle

  const handleToggleTorch = () => {
    setTorchOn((prevTorchState) => !prevTorchState);
  };

We implement a function to toggle the torch state.

5. Torch State Effect

  useEffect(() => {
    if (cameraRef) {
      torchOn
        ? (Camera.defaultProps.flashMode = Camera.Constants.FlashMode.torch)
        : (Camera.defaultProps.flashMode = Camera.Constants.FlashMode.off);
      setFlashMode(Camera.defaultProps.flashMode);
    }
  }, [torchOn]);

  useEffect(() => {
    if (cameraRef.current) {
      cameraRef.current.flashMode = flashMode;
    }
  }, [flashMode]);

We use two useEffect hooks to update the flashlight mode based on the torch state.

6. UI Rendering

  if (hasPermission === null) {
    return <View />;
  }

  if (hasPermission === false) {
    return <Text>No access to camera</Text>;
  }

  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Pressable onPress={handleToggleTorch}>
        <MaterialIcons
          name={torchOn ? "flashlight-on" : "flashlight-off"}
          size={120}
          color="black"
        />
      </Pressable>
      <Camera
        style={{ position: "absolute", top: 0, left: 0, width: 1, height: 1 }}
        ref={cameraRef}
      />
    </View>
  );
};

export default Torch;

We render the UI, including a toggle button and a hidden camera component.

Conclusion

By following these steps, you've successfully created a versatile torch component in React-Native/Expo for easy camera flash control. To implement this component in your project, create a file named Torch.js and paste the code provided below. Import and use where needed.

Full Code

import React, { useState, useEffect, useRef } from "react";
import { View, Pressable, Text } from "react-native";
import { useFocusEffect } from "@react-navigation/native";
import { Camera } from "expo-camera";
import { MaterialIcons } from "@expo/vector-icons";

const Torch = () => {
  const [hasPermission, setHasPermission] = useState(null);
  const [torchOn, setTorchOn] = useState(false);
  const [flashMode, setFlashMode] = useState(0);
  const cameraRef = useRef(null);

  useEffect(() => {
    if (cameraRef) {
      torchOn
        ? (Camera.defaultProps.flashMode = Camera.Constants.FlashMode.torch)
        : (Camera.defaultProps.flashMode = Camera.Constants.FlashMode.off);
      setFlashMode(Camera.defaultProps.flashMode);
    }
  }, [torchOn]);

  useEffect(() => {
    if (cameraRef.current) {
      cameraRef.current.flashMode = flashMode;
    }
  }, [flashMode]);

  useFocusEffect(
    React.useCallback(() => {
      const requestCameraPermission = async () => {
        const { status } = await Camera.requestCameraPermissionsAsync();
        setHasPermission(status === "granted");
      };
      setTorchOn(false);
      requestCameraPermission();

      return () => {
        Camera.defaultProps.flashMode = Camera.Constants.FlashMode.off;
        setHasPermission(null);
        setTorchOn(false);
      };
    }, [])
  );

  const handleToggleTorch = () => {
    setTorchOn((prevTorchState) => !prevTorchState);
  };

  if (hasPermission === null) {
    return <View />;
  }

  if (hasPermission === false) {
    return <Text>No access to camera</Text>;
  }

  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Pressable onPress={handleToggleTorch}>
        <MaterialIcons
          name={torchOn ? "flashlight-on" : "flashlight-off"}
          size={120}
          color="black"
        />
      </Pressable>
      <Camera
        style={{ position: "absolute", top: 0, left: 0, width: 1, height: 1 }}
        ref={cameraRef}
      />
    </View>
  );
};

export default Torch;