How I structure my React apps

Alejandro Roman
9 min readDec 28, 2018

This is a guide I started for my team at work and wanted to share it. This does not take into account Hooks or Suspense API as this a very general example of a project structure. I typically begin every project this way and then branch out.

I would love some feedback from the community. Also, it would be awesome to hear about any mistakes I’m making and/or any methods I can use to optimize my code and make it cleaner.

I’ve been picking up on typescript as well, so hopefully, that can be my next story :)

Update May 2020: https://youtu.be/K41DUe38Lc4 I hosted a workshop this weekend where I go over everything we have here in detail :)!

Update October 29, 2019: I am happy to say I still use this structure in all my react apps. What I love the most about it is how scalable it has proven to be. Hooks haven’t changed my file structure as much either.

Article Index:

  • My best practices and general structure
  • Getting started and file structure
  • Styling and State management
  • Linting

Some best practices that I implement as part of my personal preference:

  • Naming convention:
- PascalCase for component file name and folder name- Generally I want to indicate if my components are container or component. Containers will usually be class components (hooks might change that for me) that contain state and logic, whereas components
will house the actual content, styling and receive props from container. Example:
- `MyComponent.container.js`
- `MyComponent.component.js`
- `MyComponent.styles.js`
- lowerCamelCase for Higher Order Component file and folder name- lowercase for all other root directory folders. For example: `src`, `components`, `assets`
  • I destructure all objects.
<label>{ this.props.foo }</label> // wrongconst { foo } = this.props;
<label>{ foo }</label> // right
const someFunction = (arguments) =>
this.setState({
foo: arguments.bar
}); // wrong
const someFunction = ({ baz }) =>
this.setState({
foo: baz
}); // right

This might feel unnecessary at the moment but as your project grows this will save lines. Besides by making our code DRY and clean, it allows others to know what you are trying to access and/or what the object contains.

  • I use function expressions (and fat arrow) over function declarations
Function declaration:     // wrong
function(argument) {
// some action
};
Function expression: // right
const someFunction = function (arguments){
// some action
}
Fat arrow: //right
someFunction = argument => // some action

There are several benefits of using function expression (const someFunc = function(arg) => {}) over function declarations(function(arg) {}) besides making are code look awesome. For one, when the js file is loaded, function declarations are hoisted to the top of the code by the browser before any code is executed, were as function expressions do not hoist. This will help us enforce actually writing the function first. Also, JS is an idiomatic language, it sets information to variables behind the scenes, so if we think about it, it’s actually the more JS way of doing things.

Tree Structure:

Folders and files that I place in my src:

- public
- build
- node_modules
- src
|_ components
|_ MyComponentFolder
|_ MyComponent.container.js
|_ MyComponent.component.js // can be .jsx or .tsx
|_ MyComponent.styles.js
|_ MyComponent.test.js // like to bundle components
|_ index.js // export all components from one single source.
|_ actions
|_ assets
|_ images // folder
|_ fonts // folder
|_ context
|_ Mycontext.js // updated 10/22/19 with or without redux
|_ services
|_ utils
|_ styles
|_ global.js //I store all my global styles
|_ helpers.js
|_ tests
|_ helpers.js // I store handy functions here
|_ App.js
|_ index.js
|_ store.js
- .eslintrc
- .eslintignore
- .prettierrc
npx create-react-app myProjectName

When we use CRA to create a new project, our App.js will be a class component. At this moment we don't really need it to be a class component. Functional components have slightly better performance due to how react treats functional vs class components behind the scenes.
If you want to read more about how react treats functions vs classes:

https://overreacted.io/how-does-react-tell-a-class-from-a-function/ by Dan Abramov.

So, what does my App.js look like?

import React, { Fragment } from 'react'
import { SomeComponent, OtherComponent } from '../../components/index.js' // more about this later..
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { GlobalStyle } from './utils/styles/global';
const App = () => (
<Provider store={store}>
<Fragment>
<GlobalStyle />
<Router>
<Switch> // Will allow us to access nested routes.
// NOTE: if you add exact without <switch>,
// you wont be able to access those routes
<Route path="/" component={SomeComponent}>
<Route exact path="/" component={OtherComponent}>
</Switch>
</Router>
</Fragment>
</Provider>
);

I could easily have put <Provider store={store}></Provider> in myindex.js. However, because I try to keep maintainability in mind, it is easier to have as much of these providers in one single place.

Parent component:

MyComponent.container.js:import React, { Component } from 'react' 
// I try to tree-shake where I can, this will help our apps be lighter. If not, by just accessing React.Component, we import every
// item associated to the React Object
import ChildComponent from './MyComponent.component'
// this is one of the only times I use relative paths to import component. More of this later.
class Mycomponent extends Component { constructor(props) {
super(props)
this.state = {
weCool: true
}
};
render() {
const { weCool } = this.state
return (
<ChildComponent weCool={weCool} />
)
};
};
export default MyComponent;

Child Component

MyComponent.component.js:import React from 'react`;
import { StyledContainer } from './MyComponent.styles.js';
// As a child component it receives props as an argument. Here we are just destructuring it.
|
const MyComponent = ({ weCool }) => (
<StyledContainer>
Hello welcome to the child component.
Are we cool? {weCool && 'You bet'}
</StyledContainer>
);
export default MyComponent;

I use a central export file in the components directory. With this file we can just import all of our components into it and export them. This will allow us to import components into any file from the same place.

Good read on the topic: https://spin.atomicobject.com/2017/10/07/absolute-paths-javascript/

components/index.js:import MyComponent from './MyComponent/MyComponent.container';
import AnotherComponent from './AnotherComponent/AnotherComponent.component.js';
export { MyComponent, AnotherComponent };

Styling:

For styles, I choose to use the styled-components library. It has a huge community, it has become a top contender as aCSS-in-JS libraries.

- Docs: https://www.styled-components.com/docs
- Phenomenal read on `CSS-in-JS`: https://gist.github.com/threepointone/731b0c47e78d8350ae4e105c1a83867d
npm i styled-componentsMyComponent.styles.js:import styled from 'styled-components';// we create the style for the element we are trying to reachconst StyledContainer = styled.div`
color: blue;
`;
// you may notice all we are doing is appending the HTML tag to the styled object, from there on it's regular CSSexport { StyledContainer };

When I want to declare a global style sheet that will serve as underlying CSS foundation I do so by creating a global.js styles file and then append the element in App.js.

I save this file in the utils folder. Here is an example of how I format my global styles.

import { createGlobalStyle } from 'styled-components';export const GlobalStyle = createGlobalStyle`
@font-face {
font-family: 'Roboto';
src: url('../../assets/fonts) format('woff2');
};
body {
font-family: 'Roboto';
max-width: 100vw;
height: 100vh;
overflow-y: hidden;
margin: 0;
padding: 0;
}
`;

Update 11/2019:
I’ve been taking advantage of the theme provider from styled-compents . It has proven to be really handy!

I wrote more about it here -> https://dev.to/aromanarguello/how-to-use-themes-in-styled-components-49h

It is an alternative to the pattern below.

Update:

I usually create a helpers.js file inside my utils/styles . In this file, I house some common CSS properties that I will be using consistently in my styles across the app.

/*** Helper styles and themes for global usage*/export const colors = {  white: '#F5F6FD',  blue: '#214BEE',  darkNavy: '#04264C',  lightPurpleOne: '#A9A7EB',  lightPurpleTwo: '#7D74AF',  gray: '#54575E',  grey: '#9E9E9E',  cloudGray: '#8D99AE',  turqouise: '#2D728F',  lightBlue: '#B1DDF1',  pastelBlue: '#006494',  red: '#F44336',  green: '#4CAF50',  shadows: {    base: '0 10px 20px rgba(0,0,0,0.19), 
0 6px 6px rgba(0,0,0,0.23)',
light: '0px 0px 11px rgba(45, 45, 45, 0.11)'}, gradient: { lightPurple: 'linear-gradient(90deg, rgba(78,113,251,1) 0%,
rgba(158,155,255,1) 100%)'
}}

Then on any given styles. For example MyComponent.styles.js :

import styled from 'styled-components';import { colors } from '../../utils/styles/helpers'const { turquoise, shadows: { light } } = colors;const MyComponentContainer = styled.div`  width: 100%;
background-color: ${turquoise};
box-shadow: ${light};
`;export { MyComponentContainer };

STATE MANAGEMENT

For state management, I stick to redux because it has the largest community and support, as well as it has been around for as long state management libraries have been around. This provides many benefits.
Another library used in state management that has gained a lot of popularity is Apollo. I’ve personally used Apollo more for making requests to my graphql API, and have used it very little for state management.

The React context API has also been gaining traction as it has improved its functionality in the latest versions of react. REDUX uses the react.context API behind the scenes, so yea.

I will go into how I setup redux as it is what I use the most at work, and I actually really like it.

Redux tree structure:    src
|_
actions
|_ Actions.js // houses and exports actions and their logic
|_ ActionTypes.js // houses and exports action type
|_ reducers
|_ index.js
|_ MyReducer.reducer.js
|_ store.js // initialize and setup store

NOTE: The action type has to be evaluated to a string. We add them to one central source to be able to only manipulate one file at a time when we do changes and all-caps is the standard notation used to name constants.

ActionTypes:export const ACTION_CONSTANT = 'ACTION_CONSTANT'

Actions:

Actions:import { ACTION_CONSTANT } from './ActionTypes';                regular lowerCamelCase
|
export const someAction = () => ({
type: ACTION_CONSTANT,
someObject
});

Reducer

Here it’s where things start getting a bit more tricky. I opted out of using conditional switch statements for several reasons. Object lookup will have better performance, it is more flexible, it will have better readability and maintainability. And of course, we don’t need to add a break at the end of each switch statement.

const initialState = [];export default function (state = initialState, action) {
const { type } = action;
const reducer = {
[ACTION_CONSTANT]: {
...state,
// new state value
}
};

return reducer[type] || state;
};

Index:

Now, in the reducers/index.js is where all the reducers come together. We are going to import combineReducers from redux library, so that we are able to set up multiple reducers.

If you are only using one reducer you can omit this step and go directly to the store.js file

reducer/index.js:
import { combineReducers } from 'redux';
import OneReducer from '.../';
import AnotherReducer from '../';
export default combineReducers({
oneReducer: OneReducer,
anotherReducer: AnotherReducer
});

Store

In the store.js file is where I initialize and configure my store. The store is initialized with its middleware, reducers, and enhancers. It is the
location of the central state. Many developers will set up their store inside App.js. There is absolutely no problem with us doing it there.

However, thinking about the future is better to have it inside its own file.

We can now import it into App.js and add it to our provider

Store:import { createStore, applyMiddleware, compose } from 'redux';
import reduxThunk from 'redux-thunk';
import reduxPromise from 'redux-promise';
import logger from 'redux-logger';
import reducers from './reducers';
const configureStore = ( initialState = {} ) => {
const enhancer = compose(
applyMiddleware(
reduxPromise,
reduxThunk,
logger,
));
return createStore(reducers, initialState, enhancer);
};
const store = configureStore({});export default store;

Linting:

(Update)This are the dependencies I use for eslint as of CRA V2:

package.json:"dependencies": {                      // these are my linting
// related dependecies only
"eslint-config-airbnb": "^17.1.0",
"eslint-config-react-app": "^3.0.5", "eslint-loader": "^2.1.1", "eslint-plugin-flowtype": "^3.1.4", "eslint-plugin-import": "^2.14.0", "eslint-plugin-jsx-a11y": "^6.1.2", "eslint-plugin-prettier": "^3.0.0", "eslint-plugin-react": "^7.11.1"
},
"eslintConfig": { "extends": "react-app"},.eslintrc:{
"parser": "babel-eslint",
"extends": "airbnb",
"env": {
"browser": true,
"jest": true
},
"rules": {
"quote-props": [2, "as-needed"],
"no-return-assign": 0,
"comma-dangle": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }],
"no-debugger": "error",
"react/prop-types": 0,
"no-trailing-whitespace": [true, "ignore-jsdoc"],
"import/no-extraneous-dependencies": [
"error",
{ "devDependencies": true }
],
"object-curly-newline": ["error", { "multiline": true }],
"no-underscore-dangle": 0,
"jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/anchor-is-valid": 0,
"no-await-in-loop": 0,
"no-unused-expressions": [
"error",
{ "allowShortCircuit": true, "allowTernary": true }
],
"arrow-parens": ["error", "as-needed"],
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"import/prefer-default-export": 0,
"react/no-find-dom-node": 0,
"no-shadow": 0,
"max-len": 2,
"consistent-return": 0,
"no-plusplus": 0,
"spaced-comment": 0,
"react/react-in-jsx-scope": 0,
"react/jsx-filename-extension": 0,
"indent": ["error", 4],
"react/jsx-indent": ["error", 4],
"react/jsx-indent-props": ["error", 4],
"require-jsdoc": [
"error",
{
"require": {
"FunctionDeclaration": true,
"MethodDefinition": false,
"ClassDeclaration": false,
"ArrowFunctionExpression": true,
"FunctionExpression": true
}
}
],
"react/require-default-props": false
}
}
.eslintignore:src/serviceWorkers.js
node_modules
// anything else that you deem worth ignoring at a file/directory
// level
.prettierrc:{
"singleQuote": true,
"tabWidth": 2
}

📝 Read this story later in Journal.

🗞 Wake up every Sunday morning to the week’s most noteworthy Tech stories, opinions, and news waiting in your inbox: Get the noteworthy newsletter >

--

--