1

Notable reference: Can't perform a React state update on an unmounted component

My goal is to better understand how React re-render whether on every state update or after all of the state is updated.

My current understanding is React re-render on every state update which will cause Can't perform a react state update on an unmounted component if you have 2 state update one outside of an async operation, and one inside. This error happens if you update a state and then do another state update inside async operation try, catch, finally.

Example with Can't perform a react state update on an unmounted component because React does not wait to re-render although there is a state update inside the async operation. The current fix is not to do a state update.

features/Home/index.js

import React from 'react';
import Camera from '../../components/camera';
import {SafeAreaView, TouchableHighlight, Image} from 'react-native';

export default function Home() {
  const [img, setImg] = React.useState(null);

  function onPicture({uri}) {
    setImg(uri);
  }

  function onBackToCamera() {
    setImg(null);
  }

  return (
    <>
      <SafeAreaView style={{flex: 1}}>
        {img ? (
          <TouchableHighlight
            style={{flex: 1}}
            onPress={() => onBackToCamera()}>
            <Image source={{uri: img}} style={{flex: 1}} />
          </TouchableHighlight>
        ) : (
          <Camera onPicture={onPicture} />
        )}
      </SafeAreaView>
    </>
  );
}

components/camera.js

import React from 'react';
import {RNCamera} from 'react-native-camera';
import Icon from 'react-native-vector-icons/dist/FontAwesome';
import {TouchableOpacity, Alert, StyleSheet} from 'react-native';

export default function Camera({onPicture}) {
  let camera;
  const [isTakingPicture, setTakePicture] = React.useState(false);
  
  async function takePicture() {
    if (camera && !isTakingPicture) {
      let options = {
        quality: 0.85,
        fixOrientation: true,
        forceUpOrientation: true,
      };

      setTakePicture(true);

      try {
        const data = await camera.takePictureAsync(options);
        // Alert.alert('Success', JSON.stringify(data));
        onPicture(data);
      } catch (err) {
        Alert.alert('Error', 'Failed to take picture: ' + (err.message || err));
        return;
      } finally {
        setTakePicture(false);
      }
    }
  }

  return (
    <RNCamera
      ref={(ref) => (camera = ref)}
      captureAudio={false}
      style={{flex: 1}}
      type={RNCamera.Constants.Type.back}
      androidCameraPermissionOptions={{
        title: 'Permission to use camera',
        message: 'We need your permission to use your camera',
        buttonPositive: 'Ok',
        buttonNegative: 'Cancel',
      }}>
      <TouchableOpacity
        activeOpacity={0.5}
        style={styles.btnAlignment}
        onPress={takePicture}>
        <Icon name="camera" size={50} color="#fff" />
      </TouchableOpacity>
    </RNCamera>
  );
}

const styles = StyleSheet.create({
  btnAlignment: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'flex-end',
    alignItems: 'center',
    marginBottom: 20,
  },
});
Jason Rich Darmawan
  • 1,607
  • 3
  • 14
  • 31

1 Answers1

1

Let's try to explain about the re-render and the warning. I will start why re-render happens in your code.

So, you call the onBackToCamera function inside the onPress handler and that's why re-render is occupied immediately. To avoid it, you should rewrite the onPress handler.

Before:

onPress={() => onBackToCamera()}>

After:

onPress={onBackToCamera}>

You see this warning Can't perform a react state update on an unmounted component because during updating your state of Home Component you replace null with HTTP response.

I will try to explain step by step how to appear this warning in the console.

  1. The Camera component is shown before calling the takePicture function as the img is null.
  2. Then you update the state inside the try block replacing null value with HTTP response data.
  3. After updating, the TouchableHighlight component is shown instead of the Camera one because the img value doesn't equal to null.
  4. Now, the Camera component has been unmounted but the takePicture function isn't finished its execution and you try to update the state of the unmounted component in finally block.

The last action causes Can't perform a react state update on an unmounted component

Aleksei Korkoza
  • 427
  • 7
  • 22
  • [reference](https://stackoverflow.com/questions/54652908/behavior-of-arrow-function-vs-normal-function-in-onpress-method) Could you please explain why `() => onBackToCamera()` vs `onBackToCamera` makes a difference? – Jason Rich Darmawan Sep 21 '20 at 02:42