Skip to content
Muhammet Şafak
tr
Journal 4 min read

Navigation and screen architecture in React Native

Practical decisions and screen architecture approaches for setting up React Native Navigation Stack, Tab, and Drawer structures in the context of Looplio.


While building the mobile side of Looplio with React Native and Expo, I ran into my first real decision fast: how should the navigation structure look? On the web, I had accumulated dozens of decisions around URL routing; mobile required a completely different mindset.

The de facto standard for navigation in React Native is react-navigation. There are alternatives, but the ecosystem has coalesced around this library. The question isn’t “what should I use?” — it’s “how should I set it up?”

The core building blocks: Stack, Tab, Drawer

react-navigation is built on three main navigator types:

Stack Navigator: Screens stack on top of each other; pressing the back button returns to the screen below. The most natural back-navigation behavior in mobile apps comes from here. It’s a perfect fit for hierarchical flows like list → detail → edit.

Tab Navigator: Bottom or top tabs. Used for the app’s main sections — in Looplio, things like Calendar, Tasks, and Settings. This is for when the user switches context entirely, leaving one stack to move to a completely separate tab.

Drawer Navigator: A side menu. More common in tablet apps or apps with a large number of top-level sections. Instead of cramming too many items into a bottom tab bar on small phone screens, a Drawer can be the better choice.

Real-world apps combine these. In Looplio, the outermost layer is a Tab Navigator; each tab hosts its own Stack Navigator. This way, when I switch back to the “Calendar” tab, I return to the calendar view exactly where I left off — a memory behavior similar to browser history on the web.

// App.tsx — simplified structure
import { NavigationContainer } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { CalendarStack } from "./navigation/CalendarStack";
import { TaskStack } from "./navigation/TaskStack";
import { SettingsStack } from "./navigation/SettingsStack";

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Calendar" component={CalendarStack} />
        <Tab.Screen name="Tasks" component={TaskStack} />
        <Tab.Screen name="Settings" component={SettingsStack} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
// navigation/CalendarStack.tsx
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { CalendarScreen } from "../screens/CalendarScreen";
import { EventDetailScreen } from "../screens/EventDetailScreen";

const Stack = createNativeStackNavigator();

export function CalendarStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="CalendarHome" component={CalendarScreen} />
      <Stack.Screen name="EventDetail" component={EventDetailScreen} />
    </Stack.Navigator>
  );
}

Screen component architecture

The second question after setting up navigation: how do I organize the screens?

On mobile, each screen component has a natural tendency to bloat. On the web, a page is usually composed of several components; on mobile, it can feel like a screen has to carry everything at once — layout, data fetching, local state, and user interaction. Resisting that illusion matters.

I’ve adopted the following distinction: a Screen coordinates — it fetches data, manages state, and passes things down to child components. Pure components only receive props and render. I think of a Screen as a page controller.

screens/
  CalendarScreen.tsx    → data fetching + state
  EventDetailScreen.tsx → detail view for a single event
components/
  EventCard.tsx         → renders an event, knows nothing about context
  DayColumn.tsx         → calendar day view

This separation also improves testability: if EventCard is a pure component, you can control its behavior entirely through props.

Passing parameters between screens

For passing data between screens within a stack, route.params is the way to go. It works fine for simple IDs, but passing complex objects should be avoided. When navigating to an event detail screen, passing just the eventId rather than the entire event object — and re-fetching from the API inside the detail screen — is the safer approach.

// Navigating from CalendarScreen to EventDetail
navigation.navigate("EventDetail", { eventId: event.id });

// Inside EventDetailScreen
const { eventId } = route.params;
const { data: event } = useEvent(eventId);

This approach adds a small extra network request, but it guarantees that the screen owns its own data freshness. It’s particularly safe against the case where data passed from the calendar screen might change in the background while the user is on the detail screen.

Type-safe navigation with TypeScript

react-navigation has solid TypeScript support; you can define route parameter types for each navigator. This gives you compile-time guarantees for accesses like route.params.eventId.

type CalendarStackParamList = {
  CalendarHome: undefined;
  EventDetail: { eventId: string };
};

Setting this up requires a bit of upfront investment, but it pays back in reduced debugging time in the weeks that follow. When a parameter name changes, TypeScript finds every usage site for you.

Changing the navigation structure later is costly. The number of tabs, which screens belong in the same stack, which screens should open as modals — these are decisions that shape the user experience of your application.

When I was setting up Looplio, I started by sketching a user flow on paper: what does the user want to see, where do they want to go, what do they expect when they press “back”? The answers to those questions determined the navigation structure; the code came after.

I’d strongly recommend not skipping that step.

Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

Related Posts

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind