Blog

May 28, 2026 · 6 min read

How to Structure Reusable Components in React Native with TypeScript

One decision made at the start of a React Native project determines whether the codebase stays maintainable at scale — or becomes a folder of one-off components nobody wants to touch.

Why This Decision Matters Early

React Native projects tend to start fast. A screen here, a card there, a button copied from the previous screen with one prop changed. Six weeks in, the same card component exists in nine different files — each with slightly different padding, a slightly different shadow, a slightly different border radius. Changing the design means hunting down every copy.

The fix is not discipline. It is structure. When there is a clear folder for every type of component, the right decision becomes the obvious one.

Three Folders — Everything Fits Somewhere

components/
  ui/               ← no business logic, pure display
    Card.tsx
    Button.tsx
    Badge.tsx
    Avatar.tsx

  layout/           ← structure shared across screens
    ScreenWrapper.tsx
    Header.tsx
    TabBar.tsx

  features/         ← specific to one domain
    listings/
      ListingCard.tsx
      ListingGrid.tsx
    profile/
      ProfileHeader.tsx
      ProfileStats.tsx

ui/ — components that know nothing about your application. They receive data via props and render it. No API calls, no navigation, no business logic. A Card is just a styled container. A Button is just a pressable element with a label. They work anywhere.

layout/ — components that define the structure of a screen. A ScreenWrapper handles safe area insets and scroll behaviour consistently. A Header renders the same back button and title pattern across every screen. These components don't know what content surrounds them.

features/ — components tied to a specific domain. A ListingCard knows about property listings. A ProfileHeader knows about user profiles. They live close to the feature they belong to and can use data-fetching hooks, navigation, or application state.

The One Rule

If a component appears on more than one screen, it belongs in ui/.

That's it. Apply it consistently and the structure stays clean without any active effort.

What a Reusable Card Component Looks Like in TypeScript

In React Native there is no className. Flexibility comes from accepting a style prop typed as StyleProp<ViewStyle> — which allows any valid React Native style, including arrays of styles, to be passed in and merged at the call site.

import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';

interface CardProps {
  children: React.ReactNode;
  style?: StyleProp<ViewStyle>;
}

export function Card({ children, style }: CardProps) {
  return (
    <View style={[styles.card, style]}>
      {children}
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    borderRadius: 12,
    padding: 16,
    backgroundColor: '#fff',
    shadowColor: '#000',
    shadowOpacity: 0.08,
    shadowRadius: 8,
    shadowOffset: { width: 0, height: 2 },
    elevation: 2,
  },
});

Now the same Card works across every screen. You override spacing or background at the call site by passing a style prop — no new props required inside the component.

// Listing screen
<Card style={{ marginBottom: 12 }}>
  <ListingContent listing={listing} />
</Card>

// Profile screen
<Card style={{ backgroundColor: '#f9f9f9' }}>
  <ProfileStats user={user} />
</Card>

One component. Two completely different screens. Zero new props inside Card.

Keep Prop Interfaces Focused

A component accumulating conditional props is a component doing too much. The moment you find yourself adding showBadge, variant, and onLongPress to the same component, it needs to be split.

// ❌ Too many responsibilities in one component
<ListingCard
  listing={listing}
  showFavourite
  showBadge
  variant="featured"
  onPress={handlePress}
  onLongPress={handleLongPress}
/>

// ✓ Each piece has one job
<ListingCard listing={listing} onPress={handlePress} />
<FavouriteBadge listingId={listing.id} />
<FeaturedLabel />

Each piece is independently testable, independently reusable, and independently updatable. Changing the favourite behaviour doesn't require touching ListingCard at all.

Co-locate TypeScript Interfaces With the Component

Keep the props interface in the same file as the component. A separate types/ folder for component props adds indirection without benefit — when you update the component, you have to update two files instead of one.

// ListingCard.tsx
interface ListingCardProps {
  listing: {
    id: string;
    title: string;
    price: number;
    imageUrl: string;
    location: string;
  };
  onPress: (id: string) => void;
}

export function ListingCard({ listing, onPress }: ListingCardProps) {
  return (
    <Pressable onPress={() => onPress(listing.id)}>
      <Card>
        <ListingImage uri={listing.imageUrl} />
        <ListingDetails listing={listing} />
      </Card>
    </Pressable>
  );
}

The interface is right next to the component. If the listing shape changes, the interface is the first thing you see when you open the file.

When to Extract a Component

The heuristic is the same regardless of framework: if you are about to copy and paste JSX, stop and ask whether it should be a component instead.

Extract it when:

  • The same JSX appears on more than one screen
  • The block has a clear, nameable responsibility
  • You want to test or update it in isolation

Leave it inline when:

  • It only appears in one place and is unlikely to be reused
  • Extracting it requires so many props that it becomes harder to read than the original
  • The component is a one-liner that adds nothing by being named

The Payoff at Week Eight

None of this feels significant on day one. The folder structure looks like overhead.

Week eight is when it pays off. The designer changes the card shadow across the entire app. In a well-structured project, you update Card.tsx once — every screen updates. In an unstructured project, you spend a morning finding every place a card was copied and hoping you didn't miss the one in the onboarding flow.

Build components like you are building a library — even when you are the only one using it. Future you, two months from now, will not remember which card was the "correct" one. The folder structure makes that question irrelevant.

If you are starting a React Native project and want to get the architecture right before writing the first screen, get in touch. The right structure at the start is far cheaper than refactoring it later.