<aside> β¬ οΈ For previous article in the series, see Starting new side project ... for a few times.
</aside>
You are starting a new project, and you know the drill. You usually have some basic template provided for you, in this case provided by the expo init my-app
command. But how to structure your project files from there? I was just about to wing it and dive head first into creating code, but then a weak nagging feeling of βthis is not the wayβ creeped up on me π. So I attempted to do a little research. The first few articles (Best Folder Structure for React Native Project, An efficient way to structure React Native projects) talked about grouping components according to categories from Atomic Design. This seemed interesting, but then I stumbled upon an article that seemed to go a little deeper.
I wonβt rewrite everything here, but the premise is that grouping by feature is more natural, and it also offers benefits such as increasing co-location of code and making refactoring easier. This seemed to ring true to me, and it was also different from the grouping by type that I was used to from other projects.
Of course, you will have some common code that will be used between more components. The solution to this is a separate common
directory. For shared React components we can even use the previously mentioned Atomic Design principles to flesh out important shared details with more structure.
Example of an project with this structure might look like this:
src
βββ common
βΒ Β βββ components
βΒ Β βΒ Β βββ LoadingScreen.tsx
βΒ Β βββ constants.ts
βΒ Β βββ helpers.ts
βΒ Β βββ hooks
βΒ Β βΒ Β βββ useIsMounted.tsx
βΒ Β βββ layout
βΒ Β βΒ Β βββ SafeAreaLayout.tsx
βΒ Β βββ navigation
βΒ Β βΒ Β βββ RootNavigator.tsx
βΒ Β βββ state
βΒ Β βββ theme.ts
βΒ Β βββ types.ts
βββ screens
βββ settings
βΒ Β βββ component
βΒ Β βΒ Β βββ SettingsComponent.tsx
βΒ Β βββ SettingsScreen.tsx
βΒ Β βββ state
βΒ Β βββ settingsAtoms.ts
βββ today
βΒ Β βββ actions
βΒ Β βΒ Β βββ completeTask.ts
βΒ Β βΒ Β βββ createTask.ts
βΒ Β βΒ Β βββ planHabits.ts
βΒ Β βββ components
βΒ Β βΒ Β βββ NotionTaskCard.tsx
βΒ Β βΒ Β βββ TaskCard.tsx
βΒ Β βββ constants
βΒ Β βΒ Β βββ taskContants.ts
βΒ Β βββ state
βΒ Β βΒ Β βββ habitsAtoms.ts
βΒ Β βΒ Β βββ tasksAtoms.ts
βΒ Β βΒ Β βββ timeBlockAtoms.ts
βΒ Β βββ TodayScreen.tsx
βΒ Β βββ todayTypes.ts
βββ wakeUp
βββ actions
βΒ Β βββ createDailyQuest.ts
βββ state
βΒ Β βββ dailyQuestAtoms.ts
βββ svg
βΒ Β βββ SunriseSvg.tsx
βββ WakeUpScreen.tsx
βββ wakeUpTypes.ts
One of the other things mentioned more times was setting up your project so that import paths would not be relative, but absolute. The difference is:
import { RootStackParams } from '../../library/navigation/RootNavigator';
import { SettingsComponent } from './component/SettingsComponent';
vs this:
import { RootStackParams } from 'library/navigation/RootNavigator';
import { SettingsComponent } from 'screens/settings/component/SettingsComponent';
At first I looked at this as a cosmetic issue, thinking βWho the hell writes imports by hand? You just press enter for automatic import in your IDE and youβre goldenβ. But after a few days it clicked, when I came upon a concrete example. Suppose you have the folder structure described above, with a library
for common code, and a separate directory for each feature, and that your feature folder has a component name that overlaps with that of common component. Of course you should strive to name things uniquely to avoid this confusion, but it happens. With relative imports, you are faced with the question, βam I importing what I think I am?β
import { SettingsComponent } from './component/SettingsComponent';
import { SettingsComponent } from '../../component/SettingsComponent';