Getting "Can't perform a React state update on an unmounted component" only the first time

Sagor Mahtab

I am creating a ToDo app. This app has two screens: Todos and Done. I'm using BottomTabNavigator to switch between these screens. These two screens has list of todos. The todos component shows the undone todos and the Done component shows the done todos. There's a checkbox on the left and Trash icon on the right of every single todo. When a todo from Todos page is checked then it moves to the Done page. The issue is: after switching to the Done screen from Todos for the first time then after unchecking the todo there gives this warning:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. in SingleTodo (at Done.tsx:74) After this, the app is running perfectly. As I'm not sure which component is causing this error that'w why I'm sharing the minimal version of the code.

I have set up Bottom Tab navigator component like this:

import stuff..
...

const HomeTab = () => {
  return (
    <Tab.Navigator
      screenOptions={({route}) => ({
        headerShown: false,
        tabBarIcon: ({focused, color, size}) => {
          let iconName = '';
          size = focused ? 25 : 20;
          if (route.name === 'To-Do') {
            iconName = 'clipboard-list';
          } else if (route.name === 'Done') {
            iconName = 'clipboard-check';
          }

          return <FontAwesome5Icon name={iconName} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#0080ff',
        tabBarInactiveTintColor: '#777777',
        tabBarLabelStyle: {fontSize: 15, fontWeight: 'bold'},
      })}>
      <Tab.Screen name="To-Do" component={Todos} />
      <Tab.Screen name="Done" component={Done} />
    </Tab.Navigator>
  );
};

export default HomeTab;

As you can see, there are 2 components here. One is Todos. The code for this component is as follows:

import stuff...
...

const Todos = ({navigation}) => {
  const dispatch = useAppDispatch();
  const {todos}: {todos: TodoInterface[]} = useAppSelector(
    state => state.todoReducer,
  );

  useEffect(() => {
    loadTodos();
  }, []);

  const loadTodos = () => {
    AsyncStorage.getItem('todos').then(todos => {
      const parsedTodos: TodoInterface[] = JSON.parse(todos || '{}');
      dispatch(setAllTodo(parsedTodos));
    });
  };

  return (
    <HideKeyboard>
      <View style={styles.body}>
        <FlatList
          data={todos.filter(todo => todo.done !== true)}
          renderItem={({item, index}) => {
            const firstChild = index == 0 ? {marginTop: 5} : {};
            return (
              <TouchableOpacity
                style={[styles.todoWrp, firstChild]}
                onPress={() => todoPressHandler(item.todoId)}>
                <SingleTodo  // ***The code for this one is given below***
                  title={item.title}
                  subtitle={item.subTitle}
                  done={item?.done}
                  todoId={item.todoId}
                />
              </TouchableOpacity>
            );
          }}
          keyExtractor={(item, i) => item.todoId}
        />
        <TouchableOpacity style={styles.addBtn} onPress={addBtnHandler}>
          <FontAwesome5 name="plus" color="#fff" size={25} />
        </TouchableOpacity>
      </View>
    </HideKeyboard>
  );
}

The code for SingleTodo is as follows:

const SingleTodo = ({title, subtitle, done: doneProp, todoId}: Props) => {
  const [done, setDone] = useState(doneProp);
  const dispatch = useAppDispatch();
  const {todos}: TodosType = useAppSelector(state => state.todoReducer);

  const checkBoxHandler = (val: boolean) => {
    const todoList: TodoInterface[] = [...todos];
    const index = todos.findIndex(todo => todo.todoId === todoId);
    todoList[index].done = val;
    AsyncStorage.setItem('todos', JSON.stringify(todoList)).then(() => {
      dispatch(setAllTodo(todoList));
      setDone(val);
    });
  };

  const deleteHandler = () => {
    const todoList: TodoInterface[] = [...todos];
    const index = todos.findIndex(todo => todo.todoId === todoId);
    todoList.splice(index, 1);
    AsyncStorage.setItem('todos', JSON.stringify(todoList)).then(() => {
      dispatch(setAllTodo(todoList));
    });
  };

  return (
    <View style={styles.body}>
      <CheckBox
        value={done}
        onValueChange={val => checkBoxHandler(val)}
        style={styles.checkbox}
      />
      <View>
        <Text style={[styles.title, GlobalStyle.IndieFont]}>{title}</Text>
        <Text style={[styles.subtitle, GlobalStyle.IndieFont]}>{subtitle}</Text>
      </View>
      <View style={styles.trashWrp}>
        <TouchableOpacity onPress={deleteHandler}>
          <FontAwesome5Icon
            style={styles.trashIcon}
            name="trash"
            color="#e74c3c"
            size={20}
          />
        </TouchableOpacity>
      </View>
    </View>
  );
};

export default SingleTodo;

The code for Done component is similar to Todos component. The only changes is on the data property of the component

<FlatList
  data={todos.filter(todo => todo.done === true)}
  ...
  other props...
  ...
  />
SlothOverlord

It's happening every time you use this, it is just shown once to not spam the console.

 const checkBoxHandler = (val: boolean) => {
    const todoList: TodoInterface[] = [...todos];
    const index = todos.findIndex(todo => todo.todoId === todoId);
    todoList[index].done = val;
    AsyncStorage.setItem('todos', JSON.stringify(todoList)).then(() => {
      dispatch(setAllTodo(todoList));
      setDone(val);
    });
  };

  const deleteHandler = () => {
    const todoList: TodoInterface[] = [...todos];
    const index = todos.findIndex(todo => todo.todoId === todoId);
    todoList.splice(index, 1);
    AsyncStorage.setItem('todos', JSON.stringify(todoList)).then(() => {
      dispatch(setAllTodo(todoList));
    });
  };

Basically, you call the function, and the todo is unmounted from the state, but the function is not completed yet and you get that warning.

The solution is to lift everything related to the deleteHandler and checkBoxHandler from your children (Todo) to your parent (Todos), and pass it to Todo as props. Since parent is always mounted, deleting the todo will not unmount the parent and therefore, delete function will not be interrupted.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Getting Warning: Can't perform a React state update on an unmounted component

I am getting the error message Warning: Can't perform a React state update on an unmounted component

Getting this error when replying to comment: Can't perform a React state update on an unmounted component

Getting "Can't perform a React state update on an unmounted component" when switching between screens

Next.js render video only on desktop: useEffect can't perform a React state update on an unmounted component

Can't perform a React state update on an unmounted component theme provider

can't perform a react state update on an unmounted component issue with useEffect

React Can't perform a React state update on an unmounted component error

Can't perform a React state update on an unmounted component in react

useEffect - Can't perform a React state update on an unmounted component

Can't perform a React state update on an unmounted component in reactjs

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

Can't perform a react state update on an unmounted component error

React - Can't perform a React state update on an unmounted component

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

ReactJS & Redux: Can't perform a React state update on an unmounted component

Can't perform a React state update on an unmounted component?

React Spring "Can't perform a React state update on an unmounted component"

Fix "Can't perform a React state update on an unmounted component" error

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

arning: Can't perform a react state update on an unmounted component

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

Can't perform a React state update on an unmounted component useEffect error

Can't perform a React state update on an` unmounted` component

Can't perform a React state update on an unmounted component even with axios cancel token in place

React Native - Can't perform a React state update on an unmounted component, useEffect cleanup function

Can't perform a React state update on an unmounted component when using useEffect hook

How to fix Can't perform a React state update on an unmounted component warning in reactjs

UseEffect hook error, Can't perform a React state update on an unmounted component