//alexis.dev
Back to blog
1 min read

Every React Native project starts clean. Then the tenth feature lands, three developers join, and suddenly nobody knows where the business logic lives. This is the structure I reach for to keep that from happening.

Feature-first folders

Instead of grouping by technical role (components/, hooks/, screens/), group by feature:

src/
├── features/
│   ├── booking/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── api.ts
│   │   └── store.ts
│   └── profile/
├── shared/
│   ├── ui/
│   └── lib/
└── app/            # navigation, providers, entry point

A feature owns everything it needs. Deleting a feature is rm -rf instead of an archaeology project.

The test: can a new developer find all the code for "booking" without asking anyone? If yes, the structure is working.

Keep screens dumb

Screens compose; they don't decide. Business logic lives in hooks:

export function BookingScreen() {
  const { slots, selectSlot, confirm, isConfirming } = useBooking();
 
  return (
    <Screen>
      <SlotList slots={slots} onSelect={selectSlot} />
      <ConfirmButton onPress={confirm} loading={isConfirming} />
    </Screen>
  );
}

When the logic is in useBooking, you can test it without rendering a single pixel, and reuse it when the tablet layout inevitably arrives.

State: less global than you think

Most "global state" is really server cache. React Query (or SWR) handles that better than Redux ever did. What's left — session, theme, a couple of flags — fits in one small Zustand store.

Kind of stateWhere it goes
Server dataReact Query
Session / authZustand
Form statereact-hook-form
Navigation stateReact Navigation

Start here, and reach for more ceremony only when the app demands it.