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 pointA 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 state | Where it goes |
|---|---|
| Server data | React Query |
| Session / auth | Zustand |
| Form state | react-hook-form |
| Navigation state | React Navigation |
Start here, and reach for more ceremony only when the app demands it.