Alan Graton
Alan Graton

Reputation: 21

Expo-Router V2 - Stack screen inside Tab Route - [EDITED]

Edit - 03/02/2024

I've found a solution. Posting in here very soon...

Project

I built my project with React-Native with Expo SDK 49 while using Expo-Router V2 for my routes.

Problem

I'd like to have a Stack Route inside my Home screen, which is a Tab Screen. But for some reason only the new Stack Screen is being rendered once I navigate to Home.

OBS: All my routes folders have: index.tsx, _layout.tsx and a components folder

Folder Structure

src
  |-(app) // Both root Stack screens are working. They have: index.tsx & _layout.tsx
    |-(login) // Stack Screen
      |-...
    |-(signup) // Stack Screen
      |-...
    |-(tabs)
      |-(history)
        |-...
      |-(home)
        |-(exercise_details)
        |-components
        |-_layout.tsx
        |-index.tsx
      |-(profile)
        |-...
    |-_layout.tsx

app/_layout.tsx

import { Slot } from "expo-router";

import { StatusBar } from "expo-status-bar";

import { ExerciseProvider } from "@/contexts/ExerciseContext";

import { NativeBaseProvider, Box, Center } from "native-base";

import { AppLoader } from "@/components/AppLoader";

import { THEME } from "@/theme";
import {
  useFonts,
  Roboto_400Regular,
  Roboto_700Bold,
} from "@expo-google-fonts/roboto";

export default function RootLayout() {
  const [fontsLoaded] = useFonts({ Roboto_400Regular, Roboto_700Bold });

  const AppIsBuilding = () => {
    return (
      <Center flexGrow={1} bg="gray.700">
        <AppLoader size="lg" />
      </Center>
    );
  };

  const AppContent = () => {
    return (
      <Box flex={1} bg="gray.700">
        <ExerciseProvider>
          <Slot />
        </ExerciseProvider>
        <StatusBar
          animated
          translucent
          style="light"
          backgroundColor="transparent"
        />
      </Box>
    );
  };

  return (
    <>
      <NativeBaseProvider theme={THEME}>
        {fontsLoaded ? <AppContent /> : <AppIsBuilding />}
      </NativeBaseProvider>
    </>
  );
}

(tabs)/_layout.tsx

import { Tabs } from "expo-router";

import {
  House,
  UserCircle,
  ClockCounterClockwise,
} from "phosphor-react-native";

import { THEME } from "@/theme";

export default function TabsLayout() {
  return (
    <Tabs
      initialRouteName="(home)"
      screenOptions={{
        tabBarStyle: {
          height: 75,
          backgroundColor: THEME.colors.gray[600],
          borderColor: THEME.colors.gray[600],
        },
        tabBarActiveTintColor: THEME.colors.green[500],
        tabBarInactiveTintColor: THEME.colors.gray[300],
      }}
    >
      <Tabs.Screen
        name="(home)"
        options={{
          title: "",
          headerShown: false,
          tabBarIcon: ({ size, color }) => (
            <House
              size={size}
              color={color}
              weight={color === THEME.colors.green[500] ? "bold" : "regular"}
            />
          ),
        }}
      />
      <Tabs.Screen
        name="(history)"
        options={{
          title: "",
          headerShown: false,
          tabBarIcon: ({ size, color }) => (
            <ClockCounterClockwise
              size={size}
              color={color}
              weight={color === THEME.colors.green[500] ? "bold" : "regular"}
            />
          ),
        }}
      />
      <Tabs.Screen
        name="(profile)"
        options={{
          title: "",
          headerShown: false,
          tabBarIcon: ({ size, color }) => (
            <UserCircle
              size={size}
              color={color}
              weight={color === THEME.colors.green[500] ? "bold" : "regular"}
            />
          ),
        }}
      />
    </Tabs>
  );
}

(home)/_layout.tsx

import { router, Slot, Tabs } from "expo-router";

import { TouchableOpacity } from "react-native";
import { MaterialIcons } from "@expo/vector-icons";

import { HStack, VStack, Text, Icon, Heading } from "native-base";

import { AppUserPicture } from "@/components/AppUserPicture";

export default function HomeLayout() {
  function handleGoBack() {
    router.canGoBack() && router.back();
  }

  return (
    <>
      <Tabs.Screen
        options={{
          headerShown: true,
          header: () => (
            <HStack
              bg="gray.600"
              padding={6}
              height={117}
              alignItems="center"
              justifyContent="space-between"
              safeArea
            >
              <AppUserPicture w={50} h={50} />

              <VStack flex={1} ml={4}>
                <Text color="gray.100" fontSize="md">
                  Olá,
                </Text>
                <Heading color="gray.100" fontSize="md">
                  Alan Graton
                </Heading>
              </VStack>

              <TouchableOpacity onPress={handleGoBack}>
                <Icon
                  as={MaterialIcons}
                  name="logout"
                  color="gray.200"
                  size={6}
                />
              </TouchableOpacity>
            </HStack>
          ),
        }}
      />
    </>
  );
}

(home)/(exercise_details)/_layout.tsx

import { Slot, Stack } from "expo-router";

export default function ExerciseDetailsLayout() {
  return (
    <Stack initialRouteName="(home)" screenOptions={{ headerShown: false }} />
  );
}

I already tried doing something like so:

  1. (app)/_layout.tsx

import { Stack } from 'expo-router';

export default function RootLayout() {
  const [fontsLoaded] = useFonts({ Roboto_400Regular, Roboto_700Bold });

  const AppIsBuilding = () => {
    return (
      <Center flexGrow={1} bg="gray.700">
        <AppLoader size="lg" />
      </Center>
    );
  };

  const AppContent = () => {
    return (
      <Box flex={1} bg="gray.700">
        <ExerciseProvider>
          <Stack screenOptions={{ headerShown: false }}>
            <Stack.Screen name="(tabs)" />
          </Stack>
        </ExerciseProvider>
        <StatusBar
          animated
          translucent
          style="light"
          backgroundColor="transparent"
        />
      </Box>
    );
  };

  return (
    <>
      <NativeBaseProvider theme={THEME}>
        {fontsLoaded ? <AppContent /> : <AppIsBuilding />}
      </NativeBaseProvider>
    </>
  );
}

But it just returned the same result ;-;

  1. Also tried to remove <Slot /> from HomeLayout and ExerciseDetailsLayout and got an empty screen (Header and Bottom TabBar preserved)

  2. Tried declaring ExerciseDetails Stack directly in (tabs)/_layout.tsx like this but also did not work:

    <Tabs
      initialRouteName="(home)"
      screenOptions={{
        tabBarStyle: {
          height: 75,
          backgroundColor: THEME.colors.gray[600],
          borderColor: THEME.colors.gray[600],
        },
        tabBarActiveTintColor: THEME.colors.green[500],
        tabBarInactiveTintColor: THEME.colors.gray[300],
      }}
    >
      <Tabs.Screen
        name="(home)"
        options={{
          title: "",
          headerShown: false,
          tabBarIcon: ({ size, color }) => (
            <House
              size={size}
              color={color}
              weight={color === THEME.colors.green[500] ? "bold" : "regular"}
            />
          ),
        }}
      />
      <Stack.Screen
        name="(exercise_details)"
        options={{ headerShown: false }}
      />
      <Tabs.Screen
        name="(history)"
        options={{
          title: "",
          headerShown: false,
          tabBarIcon: ({ size, color }) => (
            <ClockCounterClockwise
              size={size}
              color={color}
              weight={color === THEME.colors.green[500] ? "bold" : "regular"}
            />
          ),
        }}
      />
      <Tabs.Screen
        name="(profile)"
        options={{
          title: "",
          headerShown: false,
          tabBarIcon: ({ size, color }) => (
            <UserCircle
              size={size}
              color={color}
              weight={color === THEME.colors.green[500] ? "bold" : "regular"}
            />
          ),
        }}
      />
    </Tabs>
  1. I've found this post and tried implementing this guys's structure, but also did not work: Expo-Router Bottom tabs with nested Stack Screen

Upvotes: 1

Views: 1416

Answers (1)

Luca
Luca

Reputation: 26

I recommend to have the <tabs></tabs> labels in (home)/_layout.tsx, in order to avoid confusing the router.

Upvotes: 0

Related Questions