Home > other >  Prevent Render of Camera After useState update
Prevent Render of Camera After useState update

Time:12-04

I have a camera with an overlaying template set up. It all works, the issue is that every time you change the slider to manage the opacity of the template it causes a full re-render of the screen. This makes the camera screen go black for a second or two then everything returns to normal. If you move the slider too much it will cause the app to crash completely.

Does anyone know how I can get the desired effect without re-rendering all the time? Here is the code:

import React, { useRef, useState, useEffect, useContext } from "react";
import { Camera } from "expo-camera";
import styled from "styled-components";
import { View, TouchableOpacity, ImageBackground, Image, Dimensions } from 'react-native';
import { Button } from "react-native-paper";
import Slider from '@react-native-community/slider';

import { Text } from "../../components/typography/text.component";

const CameraBackground = styled(View)`
    background-color: #222;
`;

const ImageContainer = styled(View)`
    flex: 1;
    background-color: #000;
`;
      
const SnapButtonContainer = styled(TouchableOpacity)`
    position: absolute;
    z-index: 10000;
    top: 50%;
    margin-top: -40px;
    right: 10px;     
`;

const SnapButton = styled(Button)`
    border: 1px #b22222 solid;
    width: 60px;
    height: 60px;
    background-color: #8b0000;
    border-radius: 50px;
`;

const OpacitySlider = styled(Slider)`
    z-index: 1000;
    width: 400px;
    position: absolute;
    top: 10px;
    left: 50%;
    margin-left: -200px;
`;

export const CameraScreen = ({ route, navigation }) => {
    const cameraRef = useRef();
    const [hasPermission, setHasPermission] = useState(null);
    const [photo, setPhoto] = useState(null);
    const [opacity, setOpacity] = useState(0.5);
    
    const PlaceHolderImageContainer = styled(Image)`
        width: 100%;
        height: 100%;
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        right: 0;
        z-index: 999;
        opacity: ${opacity};
        flex: 1;
    `;

    const CameraContainer = styled(Camera)`
        height: 100%;
        margin: 0 auto;
        width: 100%
    `;
    
    const snap = async () => {
        if (cameraRef) {
            let photoTaken = await cameraRef.current.takePictureAsync();
            setPhoto(photoTaken);
        }
    };

    useEffect(() => {
        (async () => {
            const { status } = await Camera.requestPermissionsAsync();
            setHasPermission(status === 'granted');
        })();
    }, []);

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

    if (hasPermission === false) {
        return <Text>This app does not have access to your camera</Text>
    }

    return (
        <CameraBackground>
            <SnapButtonContainer onPress={snap}>
                <SnapButton  />
            </SnapButtonContainer>
           
            <OpacitySlider
                minimumValue={0}
                maximumValue={1}
                step={0.1}
                minimumTrackTintColor="#FFFFFF"
                maximumTrackTintColor="#000000"
                value={opacity}
                onValueChange={(v) => {setOpacity(v)}}
            />
            <PlaceHolderImageContainer />

            <CameraContainer
                ref={(camera) => (cameraRef.current = camera)}
                type={Camera.Constants.Type.back}
            />
        </CameraBackground>
    );
};

CodePudding user response:

The problem is you are updating state in the main component which triggers a re render for all others (like camera) what you should do is extract both the Image & slider into their own component like:

const ImageSlider = () => {
  const [opacity, setOpacity] = useState(0.5);

  const PlaceHolderImageContainer = styled(Image)`
        width: 100%;
        height: 100%;
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        right: 0;
        z-index: 999;
        opacity: ${opacity};
        flex: 1;
    `;

  return (
    <>
      <OpacitySlider
        minimumValue={0}
        maximumValue={1}
        step={0.1}
        minimumTrackTintColor="#FFFFFF"
        maximumTrackTintColor="#000000"
        value={opacity}
        onValueChange={(v) => {
          console.log(v);
          setOpacity(v);
        }}
      />

      <PlaceHolderImageContainer
        //source={} //add source here
      />
    </>
  );
};

And use it back in your own component like:

<CameraBackground>
  //...
  <ImageSlider />
  //...
</CameraBackground>

See the example Snack: https://snack.expo.dev/SYtePkOAH

  • Related