Home > front end >  React-native: Use same component on two "Bottom Tab Navigation Screens"
React-native: Use same component on two "Bottom Tab Navigation Screens"

Time:07-02

I have two screens named Todos and Done. I am showing them using Bottom Tab Navigator.

<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>

But these two screens are almost identical to each other with a change in the data property of FlatList component. That's why to remove duplicity I was thinking to use a single component Todos on both screens.

When I was trying to do that I notice Todos screen renders as an empty screen while the Done screen renders properly with the desired data. The console log indeed shows an empty array on Todos. If I make a random change in the code and save it, then after the automatic refresh I observe that it loads the data correctly. I am using route.name to check the screen name. The code for Todos screen is as follows:

import stuff...
...
const Todos = ({navigation}: TodosPageProps) => {
  const dispatch = useAppDispatch();
  const {todos}: {todos: TodoInterface[]} = useAppSelector(
    state => state.todoReducer,
  );
  const route = useRoute<RouteProp<RootTabParamList, 'To-Do' | 'Done'>>();
  const [data, setData] = useState<TodoInterface[]>([]);

  useEffect(() => {
    loadTodos();
    console.log('bruh');
  }, []);

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

      // *************************** START *************************************
      if (route.name === 'To-Do') {
        const filteredData = todos.filter(todo => todo.done !== true);
        console.log('aisi', filteredData);
        setData(filteredData);
      } else if (route.name === 'Done') {
        const filteredData = todos.filter(todo => todo.done === true);
        setData(filteredData);
      }
      // *************************** END *************************************
    });
  };
... some other methods
...

  return (
    <HideKeyboard>
      <View style={styles.body}>
        <FlatList
          data={data}
          ...other props
          ...
        />
      </View>
    </HideKeyboard>
  );
};

The star (*) sign marked area shows the newest and problematic code that I added on the screen to remove duplicity. Any help?

CodePudding user response:

This seems to be a render error. (the component isnt rendering again when the tab is changed)

A posible solution could be to validate the route in the useEffect and setting a state, therefore whenever the state changes the component will re-render and we will use the state to validate in loadTodos function

import React from 'react'

export const LoadTodos = () => {


const [selectedPage, setselectedPage] = useState('Done')


    useEffect(() => {
        if (route.name === 'To-Do' && selectedPage === 'Done'){
            setselectedPage('To-Do')
        }
        if (route.name === 'Done' && selectedPage === 'To-Do'){
            setselectedPage('Done')
        }
        console.log('bruh');
      }, []);


      const loadTodos = () => {
        AsyncStorage.getItem('todos').then(fetchedTodos => {
          const parsedTodos: TodoInterface[] = JSON.parse(fetchedTodos || '[]');
          dispatch(setAllTodo(parsedTodos));
    
          // *************************** START *************************************
          if (selectedPage === 'To-Do') {
            const filteredData = todos.filter(todo => todo.done !== true);
            console.log('aisi', filteredData);
            setData(filteredData);
          } else if (selectedPage === 'Done') {
            const filteredData = todos.filter(todo => todo.done === true);
            setData(filteredData);
          }
          // *************************** END *************************************
        });
      };
  return (
    <HideKeyboard>
      <View style={styles.body}>
        <FlatList
          data={data}
          ...other props
          ...
        />
      </View>
    </HideKeyboard>
  )
}
export default loadTodos

CodePudding user response:

I have fixed this problem by replacing todos with parsedTodos on loadTodos method. I guess on initial render the empty redux store todos was not populated with the data from parsedTodos on dispatch(setAllTodo(parsed)). It passed empty store to the filter method. That's why the screen was blank. My fixed code:

const loadTodos = async () => {
    const fetchedTodos = await AsyncStorage.getItem('todos');
    const parsedTodos: TodoInterface[] = JSON.parse(fetchedTodos || '[]');
    if (parsedTodos.length !== todos.length) {
      dispatch(setAllTodo(parsedTodos));
    }
    // *************************** START *************************************
    if (route.name === 'To-Do') {
      const filteredData = parsedTodos.filter(todo => todo.done !== true);
      setData(filteredData);
    } else if (route.name === 'Done') {
      const filteredData = parsedTodos.filter(todo => todo.done === true);
      setData(filteredData);
    }
    // *************************** END *************************************
  };
  • Related