How to Structure Your Project and Manage Static Resources in React Native

As a React Native developer, one of the first decisions you face when starting a new project is how to organize your files and folders. Unlike some frameworks that enforce strict conventions, React Native gives you flexibility in structuring your project. While this freedom is powerful, it can also be overwhelming, especially for beginners.

In this article, we‘ll dive into best practices for architecting your React Native project and efficiently managing static assets. We‘ll cover the pros and cons of different approaches and provide practical examples you can apply in your own apps. By the end, you‘ll have a solid understanding of how to create a maintainable and scalable project structure.

Type vs Feature-Based Folder Organization

One common question is whether to organize files by type or by feature. In the type-based approach, you would group files into folders like components, screens, actions, reducers, etc. The feature-based structure involves creating directories for each feature or screen in your app and colocating related files.

While both can work, I recommend the feature-based approach for most React Native projects. It keeps related files together, making your project easier to navigate and modify as it grows. For example, your folder structure might look like this:

src/
  screens/
    Login/
      LoginScreen.js
      LoginForm.js
      LoginStyles.js
    Profile/
      ProfileScreen.js
      ProfileHeader.js
      ProfileStyles.js
  library/
    components/
      Button.js
      Input.js
    utils/
      api.js
  res/
    images/
    fonts/
    strings.js
    colors.js

Grouping files by feature makes it clear where to find the code for each part of your app. It also avoids long import paths and makes refactoring easier.

Using a src Folder

I recommend putting all your source files in a src directory at the root of your project. This keeps them separate from configuration files and makes it clear where your app code lives. It also allows tools like Babel to easily transpile your code when building for release.

Screens and Navigation

In the screens folder, each subfolder represents a screen in your app. If a screen has multiple related subcomponents, you can create additional files in that directory. For example, LoginScreen.js would contain the top-level component for the login screen, and LoginForm.js would define the form UI and logic.

I also suggest creating a separate file for the StyleSheet of each screen, like LoginStyles.js. This keeps your component files focused and readable.

If your app has complex navigation, you may want to create NavigationStack.js files in your screen directories to configure the React Navigation options. Alternatively, you could have a top-level navigation folder to define all your routes and parameters.

The library Folder

For components and utilities that are reused across features, create a library folder. This could include custom buttons, form inputs, data formatting functions, and API client modules. Keeping shared code in a centralized location makes it easier to maintain consistency and avoid duplication.

Inside library, I recommend creating subdirectories like components and utils to further categorize your modules. As your library grows, consider splitting it into a separate package that can be imported into multiple projects.

Managing Static Assets

Static assets like images, fonts, color palettes, and text strings are an essential part of any app. Instead of hardcoding these values throughout your project, define them in a central location and import them as needed. I suggest a res folder for this purpose.

For images, you can create an images directory and an images.js file that exports an object with keys mapping to each image file:

// res/images.js
const images = {
  logo: require(‘./images/logo.png‘),
  avatar: require(‘./images/avatar.jpg‘),
};

export default images;

You can then import and use these images in your components:

import images from ‘res/images‘;

<Image source={images.logo} />

To avoid manually updating images.js whenever you add or remove files, I recommend writing a script to generate it automatically. Here‘s an example using Node:

// scripts/generateImages.js
const fs = require(‘fs‘);
const path = require(‘path‘);

const imageDir = ‘src/res/images‘;
const outputFile = ‘src/res/images.js‘;

const imageFileNames = fs
  .readdirSync(imageDir)
  .filter((fileName) => /\.(png|jpe?g)$/.test(fileName))
  .map((fileName) => `  ${path.parse(fileName).name}: require(‘./images/${fileName}‘),`);

const fileContent = `const images = {
${imageFileNames.join(‘\n‘)}
};

export default images;
`;

fs.writeFileSync(outputFile, fileContent, ‘utf8‘);

Running this script will regenerate src/res/images.js with the latest files in src/res/images.

You can follow a similar pattern for other asset types like fonts, storing font files in src/res/fonts and generating a fonts.js file. For colors and strings, simply define the values in colors.js and strings.js files respectively.

The R Namespace

To make importing assets even more convenient, I like to create an R.js file in the res directory that re-exports everything:

// res/R.js
import strings from ‘./strings‘;
import images from ‘./images‘;
import colors from ‘./colors‘;

const R = {
  strings,
  images, 
  colors,
};

export default R;

Now you can import the R object in any file and access your static resources like R.images.logo, R.colors.primary, etc. Having a single namespace keeps your code clean and readable.

Modularizing Folders with package.json

To avoid long relative import paths like ../../../library/utils, you can create a package.json file in directories like src/library and src/res:

// src/library/package.json
{
  "name": "library"
}

This allows you to import modules from those folders using absolute paths, as if they were packages:

import Button from ‘library/components/Button‘;
import { Colors, Images } from ‘res‘;

No more getting lost in a sea of dots! Just add an "export" field to the package.json if you need to expose deeper modules.

Using index.js Files

Some developers advocate putting an index.js file in each feature folder that exports the main component, allowing imports like import LoginScreen from ‘./Login‘. I personally prefer explicitly naming the component files, but using index.js is a valid approach, especially if you have nested components in each directory.

Grouping Styles in palette.js

In addition to colors.js, I find it helpful to define reusable styles in a palette.js file. You can export style fragments for text, buttons, headers, etc:

// res/palette.js
import Colors from ‘./colors‘;

export default {
  text: {
    regular: {
      fontFamily: ‘Helvetica‘,
      fontSize: 16,
      color: Colors.darkGray,
    },
    title: {
      fontFamily: ‘Helvetica-Bold‘,
      fontSize: 24,
      color: Colors.black,
    },
  },
  button: {
    primary: {
      backgroundColor: Colors.blue,
      paddingVertical: 12,
      paddingHorizontal: 24,
      borderRadius: 8,
    },
    secondary: {
      backgroundColor: Colors.lightGray,
    },
  },
};

When defining styles in your components, you can spread these presets and override properties as needed:

import { StyleSheet } from ‘react-native‘;
import { Palette } from ‘res‘;

const styles = StyleSheet.create({
  text: {
    ...Palette.text.regular,
    color: ‘red‘,
  },
});

This convention makes it easy to maintain consistency and tweak your app‘s design.

Adding Custom Fonts

Custom fonts really make your app stand out, but configuring them can be tricky. First, make sure to include the font files (e.g. ttf or otf) in your project, for example in src/res/fonts.

On iOS, you need to add the font references to your Info.plist file:

<key>UIAppFonts</key>
<array>
  <string>FontName.ttf</string>
</array>

For Android, create a fonts folder under android/app/src/main/assets and place your font files there. React Native will bundle them automatically.

To use the custom fonts in your code, I recommend defining the font family names in your fonts.js file:

const Fonts = {
  regular: ‘FontName-Regular‘,
  bold: ‘FontName-Bold‘,
};

export default Fonts;

Then you can apply them to your Text components:

import { Fonts } from ‘res‘;

<Text style={{ fontFamily: Fonts.bold }}>Hello</Text>

By centralizing your font references, you avoid misspelling the names and can easily update them across your app.

Conclusion

Structuring a React Native project involves striking a balance between separation of concerns and ease of navigation. By organizing files into screens, library, and res folders, using absolute imports, and centralizing static resources, you can create a foundation that scales well as your app grows in complexity.

The conventions covered in this article are a solid starting point, but don‘t be afraid to adapt them to your specific needs. The "right" project structure is the one that makes you and your team productive. As you gain experience, you‘ll develop a sense for what works best in different situations.

Remember, the goal is to create a codebase that is easy to reason about, maintain, and extend over time. By being intentional about your project structure from the beginning, you‘ll save yourself countless headaches down the road.

Happy coding! And if you found this article helpful, you can find more React Native tips and best practices on my blog at [link]. Feel free to reach out on Twitter [@username] if you have any questions or suggestions.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *