JS code standards

Code style standards for JavaScript

This document outlines the rules for writing JavaScript across our codebase.

Progressive Enhancement

JavaScript should progressively enhance a page.

On all publicly accessible websites, core content and functionality should still be available without JavaScript. JavaScript applications written for a more specific audience which won’t be indexed by search engines may be more lax, but should still treat progressive enhancement as a guiding principle.

Performance

  • Cache DOM queries — only select an element once.
  • Use event delegation as much as possible to reduce the number of events bound to the page
  • Understand when you cause an element repaint and reduce inline style manipulations accordingly.
  • If possible, always use CSS for element transforms and transitions.

Element hooks

We should target page element in JavaScript using the data-js element attribute instead of using the class attribute. Classnames should be used for styling only.

e.g. <a data-js="index-link" href="/index">Index</a>.

Tooling

Build tools

Webpack is the preferred build tool for applications, and Rollup is preferred for libraries.

Rollup and Webpack for the most part have feature parity, so this is mostly a matter of taste and convenience, given Rollup is easier to configure for library builds.

Dependency management

We use Yarn for dependency management.

Packaging

Use the npm files array

Use the package.json files array to whitelist files for inclusion in your package. This is the safest approach, as it is explicit; using .npmignore is potentially dangerous, as you may end up packaging files unintentionally (credentials in a .env file being the worst case scenario!).

See Publishing what you mean to publish for further details.

Testing

We use the Jest unit test framework.

General Testing Advice

  • Don’t test external code/libraries.

Testing React

  • Tests should be collocated with their component in the same directory (MyComponent.js should have a corresponding MyComponent.test.js)
  • If a component seems difficult to test, it might indicate that it needs to be further decomposed into smaller components.
  • Unittests should test component behaviour, rather than explicit rendering. Typically you want to test state changes, that handlers are called with appropriate arguments, and any internal logic.
  • Jest snapshots are ideal for catching regressions in component output/rendering. If you are reviewing a branch, do take care to inspect the snapshot diffs. Snapshot tests should compliment unit tests rather than replace them, as unit tests better describe the intended behaviour and output of a component.
  • Favour testing components in isolation where possible (use enzyme’s shallow over mount where possible).
Redux
  • Connected components (redux containers) should be tested using redux-mock-store.
  • Containers that dispatch actions should test that component behaviours dispatch the expected actions.
  • redux-saga-test-plan is a helpful tool if you’re using sagas.
  • Reducers, action creators and selectors should all have simple functional tests.
  • Test composed Reselect selectors in isolation with resultFunc.

Code style & formatting

To ensure code formatting consistency across our codebase, we use Prettier.

JavaScript should also be written using AirBnB style. New projects can adhere to this by selecting ‘AirBnB’ during eslint init, and existing projects can find the relevant ESLint config in eslint-config-airbnb.

Commenting code

As a general rule, human-readable code is preferred over brevity.

Inline comments should be provided where logic or behaviour is unexpected or required due to external factors.

If library code, JSDoc comments should be provided as appropriate.

React

Starting a new project

When starting a new React application, we recommend create-react-app for bootstrapping the project. Avoid ejecting as long as feasible to make updating dependencies easier.

We recommend new projects use the hooks API rather than class based components. Hooks generally make components simpler, allow reuse of stateful non-visual code, and perform better.

Preferred libraries

All projects should generally use the following:

If you require routing, or state management:

Hooks

For hooks based projects, we recommend the following:

  • Hooks based components should still follow the class based component’s lifecycle ordering:
    1. computation/logic functions (typically best outside the component)
    2. state (useState)
    3. side-effects (useEffect)
    4. event handlers
    5. render
  • Use the redux hooks API and consider connect deprecated.
  • Use ES6 default parameters instead of defaultProps.
  • For expensive functions passed down to child components, consider using the useCallback hook.
  • Hooks introduce some coupling which means you’ll need to mount redux connected components when writing enzyme tests (previously you could export the unconnected component).

Redux specific

  • Selector composition and memoisation - Reselect
  • Immutable state management - Immer
  • Async/side-effects management (http, localstorage etc.) - redux-saga

If you’d like introduce a new library, or feel we should replace one of the above, please make a PR to start a discussion.

Reference projects

jaas-dashboard - project uses the hooks API and reflects current standards.

maas-ui - project uses the hooks API and reflects current standards.

crbs-ui (aka RBAC) - generally reflects current standards, but class based.

build.snapcraft.io - a bit dated, but first fully React-centred project in the webteam. Our first use of React, server-side rendering, Redux, Enzyme, etc. While it doesn’t fully follow our current standards, it may be a good place to check how some of these libraries were used.

File naming conventions

  • Component files should use CamelCase and match the name of the default export (e.g. MyComponent.js).
  • Files containing JSX should still have a .js extension. (See justification from Dan Abramov).

Redux

General Principals

Reducers

Selectors

  • Use selectors to access state in your components, ideally with Reselect which provides memoisation. Selectors can be elegantly combined to create different views of state.

Testing

  • Generally, it is best to test the component in its connected state, using redux-mock-store. In cases where you want to test the unconnected functionality of a connected component, it is okay to create a named export with a “Component” suffix. e.g. If your default export is export default connect(mapStateToProps, mapDispatchToProps)(UserList) you can also export export { UserList as UserListComponent } for testing.

References