JS apps style guide
Style guide for front-end application fashion at POLITICO.
The Stack
React/Preact
Redux
redux-orm
And on the backend, usually...
Django
Django Rest Framework
PostgreSQL
How we got here
We've been building apps at scale in React/Redux for over 3 years. Most of our backend code is written in Python and in large part we chose React/Redux because it makes our front-end code feel more Pythonic. React components are composable in a way we're used to working with Python's object-oriented model. Redux creates a data control layer that smacks of Django's ORM and redux-orm drives that point all the way home.
You may have other opinions. That's great! We've just got deadlines...
We've found this stack is highly reliable, composable and most importantly easy to reason about and read. React and Redux are also well-documented, supported, optimized and most importantly come with fantastic development tools that help us discover and isolate issues quickly.
In terms of performance benchmarks, this stack has supported the fastest election night results pages on internet™. That's good enough for us.
Folder structure
Top-level directories
Components all the way down
We prefer not to distinguish between containers/components, dumb/smart, fat/skinny in our top-level directory structure. Instead, all JSX components should go into a components/
directory at the root of your app.
Separate app state
We prefer app state, e.g., Redux actions, reducers and stores, be kept separately under a stores/
directory.
Styles
A theme/
directory should contain universal style rules for your app including colors, fonts, mixins, etc. We prefer SCSS be colocated with the components they style and loaded as CSS modules.
Content
Any text content should be loaded from a top-level directory. We do this so any content that needs copy-editing is more easily accessible. That includes meta data.
Components
Hierarchy
We use a combination of hierarchy by type/function and feature/domain. (On the difference.)
At the top level, we nest strictly by type under any routes or views, respectively.
(We consider routes and views as the highest divisions in single-page architecture. They're components that change the root of an app -- often not including page furniture like a header or footer. The difference between the two is that a route updates the window location in the URL and can be navigated back to directly. A view doesn't and can't.)
Thereafter, we encourage organizing components by feature/domain. The goal is to keep directories as shallow as possible but also to collocate related files so no one directory gets too large or unwieldy.
Split components only when they stop doing One Thing™
Divisions for divisions' sake can cause unnecessary bloat in a project. We want as shallow component directories as possible, but we also want components that are easy to read through. The latter is the more important, by far.
In general, split components that take on too much logic. For example, a component that both maintains a complex state and renders a complex UI representing that data could likely be split into two much more readable components, a "container" managing the state and a "component" responsible for rendering markup.
We prefer components with props over complicated if/then patterns. If you're nesting ternaries in your component's render function, it's a good clue you should be decomposing that component.
Again, the golden rule is to write code to be read. Elegance is less important than legibility is more important than conventions.
No useless folders
If a directory is simply a pass-through, we prefer to shorten the directory path instead of maintaining strict hierarchy.
Make component modules
Organizing components as modules is encouraged. Use an index.js
file to create a public entry for your component and write your base-level component directly in that file (rather than just importing it from a separate file).
It's a good idea that all peer modules only import directly from a module's index.js.
Hoist common components
Common components should be hoisted to the highest level that they are shared in your component directory. Use a common/
directory to contain them.
Separate layout components
Layout components are containers that either simply pass through props.children
or they compose components in a specific configuration. Generally, they should not create any new props for their children, merely pass through props from a parent component.
Styling components
We use the CSS Module spec to scope styles to the components they belong to, but we also use SCSS conventions to allow us to leverage complex class inheritance.
Our convention is to scope CSS Module styles to the component and then define all descendent styles as global definitions within that scope.
Generally, components should define their styles in a styles.scss
file that is scoped to a .component
class.
Components will then import those styles and attach the component class to the outermost wrapper element.
The CSS module spec makes cross-component inheritance hard. For example, it's almost impossible to define a conditional style on any component that's based on its parent component's class names. That's actually a feature! Pass props to components so you can define any classes they need in the component itself. It helps make your styles more explicit and easier to read.
Stores
Our structure for stores is ducks-ish in that we don't split Redux folders by type. Group them instead by branch of the app state, with one notable exception for redux-orm models.
Keep actions and constants separated
They can be more easily imported that way.
Write selectors
Always write selectors. Don't denormalize or query your app state in your components.
Wherever possible memoize selectors with reselect or even just lodash's memoize to improve performance.
Write reducers and selectors in the same file
Export the reducer by default and the selectors by name. Break this rule if your selectors become too large or, better yet, write more composable selectors.
Selectors should operate on the same state shape as is defined in the reducer. Prune the state in another place, if the reducer state is only a piece of the app state.
Keep models separate
Write these together as a close copy of the models directory in your API (usually Django).
Keep the data in your models normalized. (Use selectors to denormalize!)
Always use camelCase field names in your redux-orm models.
Connect multiple components
If you can, connect parts of your app state to multiple components. This will limit the number of pass-through props you have and can increase performance.
Theme
Define universal styles here, including fonts and default typography rules, color variables and classes, etc.
Don't define component styles here.
Content
Keep all copy-editable content external to your code in simple Markdown or txt files.
Use our Webpack Markdown loader to load paragraph text from Markdown files as React components.
Use the raw-loader to load strings from txt files for small text.
You're encouraged to use modules to group like content.
Last updated