Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Show bottom tab bagge according screen info

What I need is to show a badge according to some info located in a screen tab, this is what I have:

export const ProductsScreenLogic = ({ navigation }) => {
  const products = [{ id: '1' }];

  React.useLayoutEffect(() => {
    navigation.setOptions({
      tabBarBadge: products.length,
      title: 'Products requested',
      headerStyle: {
        backgroundColor: '#f4f4f4',
      },
      headerTitleStyle: {
        color: '#000000',
        fontSize: 24,
      },
      headerTitleAlign: 'left',
    });
  }, [navigation, tours.length]);

  return <ProductsScreenLayout products={products} />;
};

Works as expected, when the user goes to the tab that renders the screen the badge is displayed. What I want is that this badge is always visible, not only when the screen is rendered/focused. I can achieve this by placing the tabBarBadge option in the screen like so: <MyNavigator.Screen options={{tabBarBadge: tours.length}}/>, but I would not have access to the component data, products in this case.

How could I always render the badge no matter if the screen is focused and also be able to access the screen state?

like image 378
Cristian Flórez Avatar asked Oct 31 '25 19:10

Cristian Flórez


1 Answers

You could implement this by decoupling products from your UI logic. This could be done using a Context or an application cache encapsulated inside a custom hook.

Here is the first solution using the Context API. First, I have created a file called AppContext containing the following content.

import React from 'react';

export const AppContext = React.createContext({
  products: [{}],
  setProducts: () => {},
});

Then, create the context value in form of a state and add the ContextProvider in the root application. I have added an effect that performs some initial data fetching. I have just added some values for this demo.

const Tab = createBottomTabNavigator();

export default function App() {
  const [products, setProducts] = React.useState();
  const appContextValue = React.useMemo(
    () => ({
      products,
      setProducts,
    }),
    [products]
  );

  // initial data fetching (backend call.. whatever)
  // I just set a value here for demonstration
  React.useEffect(() => {
    setProducts([{id: '1'}, {id: '2'}])
  }, [])

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <AppContext.Provider value={appContextValue}>
        <NavigationContainer>
          <Tab.Navigator>
            <Tab.Screen name="ScreenA" component={ScreenA} options={{tabBarBadge: appContextValue.products?.length}} />
            <Tab.Screen name="ScreenB" component={ScreenB} />
          </Tab.Navigator>
        </NavigationContainer>
      </AppContext.Provider>
    </SafeAreaView>
  );
}

You are now able to always show the tabBarBadge and additionally access this state in any other screen. For example, we can access it in ScreenB as follows.

import React from 'react';

import { Text, View } from 'react-native';
import { AppContext } from './AppContext';
export function ScreenB() {
  const {products, setProducts} = React.useContext(AppContext);

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>ScreenB!</Text>
      <Text>Product Count: {products?.length}</Text>
    </View>
  );
}

Here is a little snack for this implementation.

However, the above solution might not be ideal in all use cases, especially when fetching data from some remote API. Thus, I have implemented a second solution using SWR hooks.

First, I have implemented a custom hook called useProducts. It contains the following code.

import React from 'react';
import useSWR from 'swr'

export function useProducts() {
  
  const { data, error, mutate } = useSWR('/api/products', () => {
    // your backend call must be implemented in here.
    // I just add some dummy data for this demo.

    return [{id: '1'}, {id: '2'}]
  })

  const refresh = React.useCallback(() => {
    mutate()
  }, [mutate])

  return {
    products: data,
    error,
    refresh
  };
}

Notice, that the above is specifically designed for caching data from an API.

We use it as usual.

import { useProducts } from './useProducts'

const Tab = createBottomTabNavigator();

export default function App() {

  const {products} = useProducts();

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <NavigationContainer>
        <Tab.Navigator>
          <Tab.Screen
            name="ScreenA"
            component={ScreenA}
            options={{ tabBarBadge: products?.length }}
          />
          <Tab.Screen name="ScreenB" component={ScreenB} />
        </Tab.Navigator>
      </NavigationContainer>
    </SafeAreaView>
  );
}

Or in any other screen, e.g. ScreenB.

import { useProducts } from './useProducts'

export function ScreenB() {
  const {products} = useProducts();

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>ScreenB!</Text>
      <Text>Product Count: {products?.length}</Text>
    </View>
  );
}

Here is a snack for the above version.

like image 178
David Scholz Avatar answered Nov 03 '25 11:11

David Scholz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!