Code Structure

Represents the ideal state of shift react applications

Code Structure

For all of our application development, we use a standardized architecture in order to speed up the development process and reduce re-work when creating new applications. This architecture is an opinionated take on best practices when building modern React applications.

At it's highest level, here's how we organize our applications:

src
├── app
│ ├── index.tsx # The app (component) entry-point.
│ ├── login # Login feature.
│ │ ├── components # Additional components used by login feature.
│ │ ├── index.ts # Login feature top-level exports.
│ ├── users # Users feature.
│ │ ├── components # Additional components used by users feature.
│ │ ├── index.ts # Users feature top-level exports.
│ ├── reducer.ts # Root-level reducer composed from feature reducers.
│ ├── store.ts # Constructs the redux store from root reducer.
├── ui # Standalone components containing no business logic.
│ ├── button # Component directory
│ │ ├── Button.jsx # Standalone component
│ │ │ ├──assets # Any static assets needed by the Button component
│ │ │ │ ├──icon.svg # A static asset needed by the Button component
│ │ ├── index.js # Exports button.
│ └── index.ts # Exports all UI components.
├── data # Shared and external state.
│ ├── users.ts # Functions for fetching and mutating user data.
│ ├── asset.ts # Functions for fetching and mutating asset data.
│ └── index.ts # Exports all elements.
├── etc # Configuration, theme, etc.
├── index.html # The application entry-point HTML file.
├── index.tsx # The application entry-point TypeScript file.
├── opt # Here be dragons.
├── root.tsx # The root component rendered to the root div on the page. Contains providers.
└── util # Utilities.

Let's break this down a bit further from the top level.

• The app directory contains our features

• The elements directory contains our shared presentation components

• The data directory contains feature specific shared state and external state

Features

Features are parts of the application that provide some useful functionality to our users. Features vary in size and can represent large and complex modules like Upload or Feed. Features can also be small in size and complexity like Team Switcher. Features can be contained within features and will often sit adjacent to one another like Asset List View and Asset Grid View.

Feature Scope

Each feature is responsible for it's own business and view logic within it's domain. All problems within the features domain should be solved in that feature and not elsewhere.

Let's consider an example case. Assume that all assets are contained within projects. As an rule, we want to truncate any asset descriptions longer than 100 characters in length.

Feature API

Features have a top-level index.ts file which defines public exports for that particular feature. By doing this we create a clear API through which we can access the features functionality while keeping the implementation encapsulated.

Features should never reach into each other's internals (actions, selectors, components, reducers, etc). Further, adjacent features should never

Components

Components are the building blocks from which will build our features. They range in size and complexity. There are two core types of components in our application: presentation and composed.

Presentation Components

Presentation components are the most basic building blocks of our features. They have no shared state and are built for reuse. Examples are buttons, form inputs, tiles, headers, and footers.

Presentation components should have a clear API that focuses on broad reuse. They should be highly customizable and support style overrides.

Composed Components

Composed components are those components that implement some feature by constructing several presentation and composed components together. They may have shared state and may depend on external data.

State

Our application has three types of state: local state, shared state, and external state.

Local State

Local state is scoped to the component itself and cannot be accessed or mutated outside of the component. Using local state promotes component reuse because it does not depend on external state. Prefer local state whenever possible.

Shared State

Shared state is state that can be access across components throughout the application and is used when we need one component to know the state of another. We prefer to limit the use of shared state to store external state. However, there are some other cases where it makes sense to use shared state. As an example, consider that you have a FileUpload component and a FileNavigation component. When we upload a new file using the FileUpload component we want the FileNavigator to display it. So, the two components share state so that when the upload completes the file can be immediately displayed in the file navigation.

We rely on Redux for accessing and mutating shared state. For an full example have a look at src/data/asset

External State

External state is also shared state. However, the distinction is that external state is state that is derived from an external source. The most common example of this is state that is derived from the Shift API. Accessing and mutating external state managed in exactly the same way as shared state with the exception that we derive the data using http.

Styles

We use CSS Modules for styling in our codebases. We intentionally break the CSS cascade by using CSS modules for the purpose of writing more portable and maintainable styling. While the same modularity can be achieved with CSS in JS tools (Radium, Aphrodite, Styled Components, Glamorous, etc), we like the expressiveness and stylesheets. CSS Modules allow us to write regular CSS while importing the CSS as JavaScript objects. CSS also has the added benefit on style composition which us to be concise and reuse styles.

Our build system handles the processing of our CSS files using a combination of css-loader for module selector generation, and style loader to generate importable styles

Example usage:

--styles.scss
.main {
background-color: red
height: 100px;
width: 100px;
}
--component.jsx
import React, { Component } from
import styles from './styles.scss';
const Example = () => <div className={styles.main} />

Always avoid making style changes globally an instead prefer classes wherever possible. As an example, use

.input { padding: 40 }

instead of...

input { padding: 40 }

Exports

We prefer named exports wherever possible. Named exports allow use to create a simpler import. Consider the following import assuming a default export is used:

import Sprocket from './ui/sprokets/Sprocket'

The above example requires that we know the location of every component we want to import. Instead consider the following:

import { Sprocket } from './ui'

In this example we only need to know where our ui components live and we can bring in anything we need in a single import.

Assets

Assets such as icons or images should be adjacent to the component itself in an assets directory. Avoid placing assets far away from the component. If assets are shared then they should be placed at the lowest common parent directory.