10 Tips for Structuring a React Native Project
Tags: reactnative, typescript
Published: 2020-11-29 12:25:03

When starting a new project, there are plenty of choices to be made around code style, language, folder layout, and more. Consistency is the key for creating clean, maintainable codebases. Therefore once decided, you'd usually need to stick with these choices for a while.

Time and experience will teach you what works and what doesn't. But what if you don't have time? You can always use someone else's experience.

Here are my top 10 tips for structuring a React Native project:

## 1. Use TypeScript
Yes, there is a bit of a learning curve if you're used to plain JavaScript.

Yes, it's worth it.

Typed JavaScript makes refactoring a whole lot easier, and when done right, gives you a lot more confidence in your code. Use the guide in the [docs](https://reactnative.dev/docs/typescript) for setup instructions. Make sure to enable strict mode (`"strict": true` in the `compilerOptions`).

You can also add type checking in your CI with `tsc --noEmit`, so you can be confident in your types!

## 2. Set up a module alias to `/src`
Set up a single module alias to `/src` (and a separate one for `/assets` if needed), so instead of:

```js
import CustomButton from '../../../components/CustomButton';
```

you can do:

```js
import CustomButton from '@src/components/CustomButton';
```

I always use a `@` or a `~` in front of `src` to highlight it's an alias.

I've seen implementations where folks set up multiple type aliases - one for `@components`, one for `@screens`, one for `@util` etc, but I've found a single top level alias to be the clearest.

There's a handy guide for setting this up with TypeScript in the [React Native docs](https://reactnative.dev/docs/typescript#using-custom-path-aliases-with-typescript).

## 3. Use Inline Styles
You have an option for using the built in inline styles, or [Styled Components](https://styled-components.com/docs/basics#react-native).

I started off with Styled Components, then switched to inline styles, because there used to be a performance implication, though that's [negligible](https://medium.com/@jm90mm/a-quick-performance-comparison-of-styled-components-vs-inline-styles-in-react-native-21d8f6a561d7), so now it's just a preference.

## 4. One Style File Per Component
Each component should have their own style file with a `styles.ts` extension:

```
FirstComponent.tsx
FirstComponent.styles.ts
SecondComponent.tsx
SecondComponent.styles.tsx
```

Note, the `.styles.ts` in the filename is just a convention I use to indicate that the styles belong to the component, the TypeScript compiler will treat these as regular `.ts` files.

Each style file exports a single style object for the component:

```js
// FirstComponent.styles.ts

import { StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
});

export default styles;
```

Each component only imports only its own styles:
```js
// FirstComponent.tsx

import styles from './FirstComponent.styles';

...
```

## 5. Use Global Styles
Create a `globalStyles.ts` file at the top level of the `/src` directory, and import it to the `.styles.ts` as needed.

Always use constants for:
- colours
- fonts
- font sizes
- spacing

It may seem tedious at first, but handy in the long term. And if you find you're ending up creating constant for every single space, it's something to gently bring up with the Design team, as design guides would generally not want that.

## 6. Flatten Style Constants

Instead of:
```js
const globalStyles = {
  color: {
    blue: '#235789',
    red: '#C1292E',
    yellow: '#F1D302',
  },
};
```

Do this:
```js
const globalStyles = {
  colorBlue: '#235789',
  colorRed: '#C1292E',
  colorYellow: '#F1D302',
};
```

It can be tempting to group these, but I've found that keeping them flat can be more handy, e.g. if you wanted to replace all instances of `colorRed` in your codebase, you could do a find and replace, whereas with `colors.red` it'd be harder, since the colour could have been destructured. 

## 7. Use Numbers in Style Constants

Instead of:

```js
const globalStyles = {
  fontSize: {
    extraSmall: 8,
    small: 12,
    medium: 16,
    large: 18,
    extraLarge: 24,
  },
};
```

Do this:

```js
const globalStyles = {
  fontSize8: 8,
  fontSize12: 12,
  fontSize16: 16,
  fontSize18: 18,
  fontSize24: 24,
};
```

The first option may look nicer when writing it down, but during development, you don't tend to care about "medium" and "large", and just care about the number. And it will avoid the awkward naming when the designers inevitably add a font size 14 and you have to start calling your variables things like `mediumSmall`.

## 8. One Component Per File
Here's the template for a new component:

```js
import React from 'react';
import { View, Text } from 'react-native';
import styles from './App.styles';

const App = () => {
  return (
    <View style={styles.container}>
      <Text>Hello, world!</Text>
    </View>
  );
};

export default App;
```

Some things to note here:
- function components over class components: I'd always use function components and manage any state and side-effects using hooks
- I use constant functions, but both `const` and `function` are equally good here. In fact `function` might be better in the long term
- default export: I always use a default export, though there is an argument to be made that named exports are better since they'll be clearer to refactor, and I agree - that might be the next step

## 9. Separate Components and Screens
Here's a typical folder structure I end up with:

```
/assets
  /images
    image.png
    anotherImage.png
  /icons
    icon.svg
    anotherIcon.svg
/src
  /components
    Component1.tsx
    Component1.styles.ts
    Component1.test.ts
    Component2.tsx
    Component2.styles.ts
    Component2.test.ts
  /screens
    Screen.tsx
    Screen.styles.ts
    Modal.tsx
    Modal.styles.ts
  App.tsx
  globalStyles.ts
  types.ts
```

I always separate components in the `/components` directory and the screens and modals in the `/screens` directory. When using `react-navigation`, there is no structural difference between screens and modals, but I prefer to also differentiate the intent by naming the file `SomethingModal.tsx`.

Another thing to note is the file names - rather than creating a folder with the file name, and naming each file `index.tsx`, the filename should reflect the component name. That is mostly for convenience - in most editors, it'll get tedious to track down which file you're editing when they're all called `index.tsx`

I've also seen implementations where all components are imported to a single `index.ts` file and exported from there. I personally am not a fan of that solution and see it as an unnecessary extra step.

## 10. Lint Your Code
It's worth it. Trust me!

1. Use [eslint](https://eslint.org/) and [prettier](https://prettier.io/) - they actually come pre-installed when you initialise a new project
2. Set up a pre-commit hook - I usually set up a pre-commit hook for linting and pre-push hook for tests. There's a great guide [here](https://prettier.io/docs/en/precommit.html).
3. Check lint, test and TypeScript errors on CI! This is so important - the only way to ensure a consistent code style across the project lifecycle. Setting up CI is one of the first things I do when starting a new project.

Hope this helps! Got any tips of your own that I did't list here? Let me know in the comments! 👀