Cannot update a component while rendering a different component - React / React Native

I started working with React Native about four months ago and I must admit that so far it's been an amazing journey with a lot of ups and downs. I have some basic experience using React so it's not all new for me, but developing for mobile devices is a lot more challenging than web development in my opinion.

The other day I encountered an issue where React complained that it cannot update a component while rendering a different component. Here's what happened and how to fix it.

In my case the message looked like this:

Warning: Cannot update a component (`ForwardRef(BaseNavigationContainer)`) while rendering a different component (`HomeScreen`). To locate the bad setState() call inside `HomeScreen`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render

and here's the actual screenshot of the message:

Image

Your warning message may look a bit different, but in general, it will have the following pattern:

Warning: Cannot update a component * while rendering a different component *

where asterisks are component names.

Let's see what caused this warning and how to fix it. The following code samples are not from the actual app, they are oversimplified examples for educational purposes only.

Here's the HomeScreen component:

import React, {useState} from 'react';
import {Pressable, Text, View} from 'react-native';
import {useNavigation} from '@react-navigation/native';

function HomeScreen() {
  const [isSubmitted, setIsSubmitted] = useState(false);
  const navigation = useNavigation();

  const toggleIsSubmitted = () => {
    setIsSubmitted(value => !value);
  };

  if (isSubmitted === true) {
    navigation.navigate('ProfileScreen');
  }

  return (
    <View>
      <Pressable
        onPress={() => {
          toggleIsSubmitted();
        }}>
        <Text>Submit</Text>
      </Pressable>
    </View>
  );
}

export default HomeScreen;

Nothing fancy here, just one state variable, navigation, and the Pressable component. We have a condition check that will redirect us to the Profile screen if the state variable is equal to true.

Looks simple enough but there is one problem. Passing state setters to a child component is an absolutely fine thing to do -- nothing wrong with that. But the catch is that you should never call those setters while rendering. That will lead to a warning message.

In this example, we are calling navigation.navigate() while rendering the HomeScreen component. The navigate() under the hood changes a state and that causes the issue.

The fix is fairly simple. Just wrap the condition check in the useEffect. This hook basically tells React that the component has to do something after rendering.

import React, {useEffect, useState} from 'react';
import {Pressable, Text, View} from 'react-native';
import {useNavigation} from '@react-navigation/native';

function HomeScreen() {
  const [isSubmitted, setIsSubmitted] = useState(false);
  const navigation = useNavigation();

  const toggleIsSubmitted = () => {
    setIsSubmitted(value => !value);
  };

  useEffect(() => {
    if (isSubmitted === true) {
      navigation.navigate('ProfileScreen');
    }
  }, [isSubmitted]);

  return (
    <View>
      <Pressable
        onPress={() => {
          toggleIsSubmitted();
        }}>
        <Text>Submit</Text>
      </Pressable>
    </View>
  );
}

export default HomeScreen;

You'll get the same warning message if you write something like this:

<Pressable
  onPress={
    navigation.navigate('ProfileScreen')
  }>
  <Text>Submit</Text>
</Pressable>

In this case, it's obvious that the issue is that the onPress prop is not an arrow function, so to fix it just change it to something like this:

<Pressable
  onPress={
    () => navigation.navigate('ProfileScreen')
  }>
  <Text>Submit</Text>
</Pressable>

Please note that this warning was introduced in React version 16.3.1, so if you are using an older version you won't see the warning message.

So to conclude this article, everything boils down to this:

DON'T DO THIS!

const HomeScreen = (props) => {
    
  props.setSomeState(true);

  return (
    <Text>This is home screen</Text>
  );
};

DO SOMETHING LIKE THIS INSTEAD!

const HomeScreen = (props) => {

  useEffect(() => {
    props.setSomeState(true);
  }, []);

  return (
    <Text>This is home screen</Text>
  );
};

And that's a wrap. If you want to find more information about this check the following GitHub issue about this.

About the Author

Goran Nikolovski is an experienced web and AI developer skilled in Drupal, React, and React Native. He founded this website and enjoys sharing his knowledge.