yeti logo icon
Close Icon
contact us
Yeti postage stamp
We'll reply within 24 hours.
Thank you! Your message has been received!
A yeti hand giving a thumb's up
Oops! Something went wrong while submitting the form.

How to Structure Your React / Redux Application

By
-
May 2, 2019

At Yeti, we create a large number of apps from the ground up due to the number of startups we work with. Every new app gives us the opportunity to improve our tech stack and one if its important pieces - folder structure.

Why does folder structure matter?

Imagine a scenario in which we have simple requirements, which warrants a simple app and a simple folder structure. Makes sense. However, as we start scaling our app, this simple folder structure becomes an issue. Let's say we place all of our components in a folder called components. Every component that lives under the components directory seems to have equal weighting, leaving sub components of other components and components that are really screens on the same level. Any developer trying to understand how everything connects will have a difficult time seeing the shape of the app.

Our redux code becomes a mess too. Our files containing our actions become monoliths. Our initial slices of redux state no longer represent what's inside of them. We're now unnecessarily digging for logic while holding too many files in our working memory. One tap on the shoulder from an unassuming colleague and all of the connections we've been forming between pieces of code and logic in our app evaporate.

Dealing with these problems has led us to create a set of folders that hold specialized purposes in our apps, allowing easier maintenance and less reliance on tribal knowledge.

The Five Folders

modules

To kick off the most important folder, there are two things to keep in mind - verticality and horizontality. Folders that have many files or folders within them one level deep are very horizontal, while folders that have many nested subfolders are very vertical. It is important to keep a balance between these two attributes since going too far in either direction will cause confusion and increase the complexity of the mental model of the application.

If we keep everything in one folder called components, for example, the shape of the application and how the code relates to each other will not be apparent. However, if we create too many nested folders and start sharing information between subparts of the application at too many different levels, we over-abstract and actually make the app more complex than necessary.

Each module represents a certain section or theme of the app. Common modules that can be frequently prevelant across apps are 'authentication', 'profile', and 'onboarding'. Used wisely, thematic modules like these can simplify the understanding the application and provide sufficient sandboxing from other, unrelated parts of the app.

Within each module, we only create three more folders: dux , a spin off of ducks modular redux where we export our reducer and actions, screens , which correspond to the different screens the router will be displaying, and a third for action creators or other side effects. For redux-thunk, we call it thunks , for redux saga , sagas , and for redux observable , epics .

Tip: Reducing Boilerplate

In order to reduce boilerplate and avoid the necessity to use numerous spread operators in our reducers, we use immer reducer. It also supports TypeScript and automatically creates actions.

redux_setup

The first thing you'll notice here is that there's an underscore. Why's that? To me, it's more readable than reduxSetup. It's also not a .js file, so we're not breaking any javascript naming conventions. It also allows us to create a rule: folders should have an underscore between letters when there are multiple words, which increases uniformity. The more uniformity there is in the app, the less you have to think.

At its simplest, this folder houses two files: rootReducer and store . To those familiar with redux, these should be pretty self explanatory. Our rootReducer combines all of our main reducers from our modules and our store creates the central brain for redux.

Tip: Side Effect Setup

If we're working with any side effect libraries like redux observable or redux saga , we'll need somewhere to put their setup boilerplate. This is a good place.

services

Services follow the same paradigm as modules in that they are modular. Each service is meant to provide specific functionality or business logic. Examples of services that we frequently use are an Http service and Navigation service, which work well in both our react native and react web projects. Our Http service is a base class, which is helpful for creating additional services like Backend that extend Http and connect with a backend like Django.

Fun fact: Before we were a React shop, we primarily developed in Angular, which is where the inspiration for how we utilize services comes from. The difference being we just import our services directly as ES6 modules instead of using dependency injection.

shared

Things that go in here have usually elevated from the ranks of single use and are now ready to be sent across the entire application. Some good examples of what to put in here are styles, typically with fonts and colors acting as files within, and components , with things like button and text . A good rule of thumb is that components should only go in here if we know that the design uses this element in multiple places or that they are not sub components of a component or screen . Another worthy mention is a utils folder or file, for those one off functions that have unique business logic.

types

We use TypeScript for all of our projects, so this folder acts as the single source of truth for our types. Even if you are using PropTypes, don't forget that those can be abstracted as well!

We've experimented with keeping types scattered throughout the other folders before, but have found that developers not acquainted with the code base struggle to find where types live. Types then start to become duplicated in multiple places. Having a code editor with Intellisense helps with both of these problems, but only when you're familiar with the names.

We actually create folders in the types folder that are very similar to Five Folders. These are services , state , and shared . state is just an abstraction over the different modules. This allows us to still keep the same mental model as the src folder, reducing cognitive load. This also has the increased benefit of knowing that importing from types/.. will always get you where you need to go.

The Real World

All of this idealistic philosophy is great, but does it actually scale?

In short, yes. We've created multiple projects that have scaled to over 100 components using this folder structure.

Is it modular?

Yup. One of the most common occurrences during a refactor is to repurpose a component to be shared. Since everything is in a folder, tests included, we simply just need to drag and drop it into the shared/components folder. Technically, it's actually not that simple, since we need to change the imports from where we're calling that component, but if we're using TypeScript the code won't even compile before those are fixed, so we'll be safe.

Is it extendable?

Yes. When adding functionality, the biggest pieces are usually new sections / flows within the app and integrating with new third party code. Our services folder can easily accommodate new services, as well as our modules, since we can just add a new module that also encompasses a new slice of state.

Next Steps

Want an example of this philosophy in action? Check out Glamper, a starter kit that we use at Yeti to scaffold new projects.

You Might also like...

colorful swirlsAn Introduction to Neural Networks

Join James McNamara in this insightful talk as he navigates the intricate world of neural networks, deep learning, and artificial intelligence. From the evolution of architectures like CNNs and RNNs to groundbreaking techniques like word embeddings and transformers, discover the transformative impact of AI in image recognition, natural language processing, and even coding assistance.

A keyboardThe Symbolicon: My Journey to an Ineffective 10-key Keyboard

Join developer Jonny in exploring the Symbolicon, a unique 10-key custom keyboard inspired by the Braille alphabet. Delve into the conceptualization, ideas, and the hands-on process of building this unique keyboard!

Cross-Domain Product Analytics with PostHog

Insightful product analytics can provide a treasure trove of valuable user information. Unfortunately, there are also a large number of roadblocks to obtaining accurate user data. In this article we explore PostHog and how it can help you improve your cross-domain user analytics.

Browse all Blog Articles

Ready for your new product adventure?

Let's Get Started