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 for login feature.
│ │ │ ├── SomeModal # SomeModal compolex component.
│ │ │ │ ├── SomeModal.tsx # Main SomeModal feature component.
│ │ │ │ ├── [SomeModal.css] # Css for SomeModal component.
│ │ │ │ ├── [container.tsx] # Redux and other external bindings to login feature .
│ │ │ │ ├── [container.stub.tsx] # Stub container for prototyping / storybook mode.
│ │ │ │ ├── [SomeModal.story.tsx] # Storybook configuration for SomeModal component.
│ │ │ │ └── index.ts # SomeModal component top-level exports.
│ │ │ └── index.tsx # Exports all additional components.
│ │ ├── Login.tsx # Main login feature component.
│ │ ├── [Login.css] # Css for Login feature.
│ │ ├── container.tsx # Redux and other external bindings to login feature .
│ │ ├── [container.stub.tsx] # Stub container for prototyping / storybook mode.
│ │ ├── [Login.story.tsx] # Storybook configuration for Login feature.
│ │ └── index.ts # Login feature top-level exports.
├── data # Shared and external state.
│ ├── authentication # Authentication feature.
│ │ ├── reducer.ts # Reducer for module
│ │ ├── saga.ts # Saga for module
│ │ ├── selectors.ts # Selector functions for module
│ │ ├── actions.ts # Action creators for module
│ │ ├── index.ts # Exports for authentication data module
│ │ └── __test__ # All tests live here.
│ │ ├── mocks.ts # Support files to create moks for unit tests
│ │ ├── reducer.test.ts # Tests for reducer file.
│ │ ├── [subReducer.test.ts] # Some support reducer, in case you need it.
│ │ ├── saga.test.ts # Tests for saga file.
│ │ ├── selectors.test.ts # Tests for selectors file.
│ │ └── actions.test.ts # Tests for actions file.
│ ├── reducer.ts # Root application reducer.
│ ├── saga.ts # Root application saga.
│ ├── store.ts # Redux store configuration.
│ └── index.ts # Exports all elements.
├── ui # Standalone components containing no business logic.
│ ├── Icon # Component directory
│ │ ├── components # Additional components for icon component.
│ │ │ ├── Check.ts # Check icon svg component.
│ │ │ └── Plus.ts # Plus icon svg component.
│ │ ├── Icon.tsx # Standalone component
│ │ ├── Icon.story.tsx # Storybook configuration for Icon component.
│ │ └── index.ts # Exports icon.
│ ├── Button # Component directory
│ │ ├── Button.tsx # Standalone component
│ │ ├── Button.story.tsx # Storybook configuration for Button component.
│ │ ├── [Button.css] # Css for Button component.
│ │ ├── assets # Any static assets needed by the Button component
│ │ │ └──icon.svg # A static asset needed by the Button component
│ │ └── index.ts # Exports button.
│ └── index.ts # Exports all UI components.
├── utils # Support utilities.
│ ├── sagas # utilities for redux-saga.
│ │ ├── takeFirst.ts # takeFirst utility.
│ │ ├── takeUntil.ts # takeUntil utility.
│ │ ├── index.ts # takeUntil utility.
│ │ └── __tests__ # utilities tests.
│ │ ├── takeUntil.ts # takeUntil utility tests.
│ │ ├── takeFirst.ts # takeFirst utility tests.
│ │ └── index.ts # Exports saga utils.
│ ├── hooks # utilities for hooks (same layout as saga).
│ └── index.ts # Top level exports for utilities.
├── config # Configrations.
│ ├── theme # theme configuration.
│ ├── environment # environments (dev, qa, production).
│ └── platform # platform (web/desktop) related config and utilities.
├── index.html # The application entry-point HTML file.
├── index.tsx # The application entry-point TypeScript file.
desktop # Code related to desktop shell (TBD).

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

• The app directory contains our features

• The ui directory contains our shared presentation components

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

• The utils directory contains support functions for app modules

• The config directory contains configurations and utilities related to different environments / platforms.

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.