grafana/contribute/style-guides/frontend.md
Joseph Perez 8cc95a7459
Docs: Edit contribute/style-guides (part 11 of doc improvement project) (#92169)
* Docs: edit contribute/style-guides

* Add missing 'is'

* Improve grammar of code comment

* Prettier fixes

* Minor fix

* Minor fix

---------

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>
2024-08-23 08:08:04 -07:00

7.8 KiB

Frontend style guide

Grafana Labs follows the Airbnb React/JSX Style Guide in matters pertaining to React. This guide provides highlights of the style rules we follow.

Basic rules

  • Try to keep files small and focused.
  • Break large components up into sub-components.
  • Use spaces for indentation.

Naming conventions

Follow these guidelines when naming elements of your code.

Class names

Use PascalCase. For example:

// bad
class dataLink {
  //...
}

// good
class DataLink {
  //...
}

Constants

Use ALL CAPS for constants. For example:

// bad
const constantValue = "This string won't change";
// bad
const constant_value = "This string won't change";

// good
const CONSTANT_VALUE = "This string won't change";

Emotion class names

Use camelCase. For example:

const getStyles = (theme: GrafanaTheme2) => ({
  // bad
  ElementWrapper: css`...`,
  // bad
  ['element-wrapper']: css`...`,

  // good
  elementWrapper: css({
    padding: theme.spacing(1, 2),
    background: theme.colors.background.secondary,
  }),
});

Use hook useStyles2(getStyles) to memoize the styles generation and try to avoid passing props to the getStyles function and instead compose classes using Emotion CX function.

Enums

Use PascalCase. For example:

// bad
enum buttonVariant {
 //...
}

// good
enum ButtonVariant {
 //...
}

Files and directories

Name files according to the primary export:

  • When the primary export is a class or React component, use PascalCase.
  • When the primary export is a function, use camelCase.

For files that export multiple utility functions, use the name that describes the responsibility of grouped utilities. For example, a file that exports math utilities should be named math.ts.

  • Use constants.ts for files that export constants.
  • Use actions.ts for files that export Redux actions.
  • Use reducers.ts for Redux reducers.
  • Use *.test.ts(x) for test files.

For directory names, use dash-case (sometimes called kebab-case).

  • Use features/new-important-feature/utils.ts

Functions

Use PascalCase. For example:

Use camelCase.

// bad
const CalculatePercentage = () => { ... }
// bad
const calculate_percentage = () => { ... }

// good
const calculatePercentage = () => { ... }

Interfaces

Use PascalCase. For example:

// bad
interface buttonProps {
  //...
}
// bad
interface button_props {
  //...
}
// bad
interface IButtonProps {
  //...
}

// good
interface ButtonProps {
  //...
}

// bad
type requestInfo = ...
// bad
type request_info = ...

// good
type RequestInfo = ...

Methods

Use PascalCase. For example:

Use camelCase.

class DateCalculator {
  // bad
  CalculateTimeRange () {...}
}
class DateCalculator {
  // bad
  calculate_time_range () {...}
}

class DateCalculator {
  // good
  calculateTimeRange () {...}
}

React components

Follow these guidelines for naming React components.

React callback props and handlers

Name callback props and handlers with an on prefix. For example:

// bad
handleChange = () => {

};

render() {
  return (
    <MyComponent changed={this.handleChange} />
  );
}

// good
onChange = () => {

};

render() {
  return (
    <MyComponent onChange={this.onChange} />
  );
}

React component constructor

Use the following convention when implementing these React components:

// bad
constructor(props) {...}

// good
constructor(props: Props) {...}

React component defaultProps

Use the following convention when implementing these React components:

// bad
static defaultProps = { ... }

// good
static defaultProps: Partial<Props> = { ... }

React component definitions

Use the following convention when implementing these React components:

// bad
export class YourClass extends PureComponent { ... }

// good
export class YourClass extends PureComponent<{},{}> { ... }

React state and properties

Use camelCase. For example:

interface ModalState {
  // bad
  IsActive: boolean;
  // bad
  is_active: boolean;

  // good
  isActive: boolean;
}

SASS

SASS styles are deprecated. You should migrate to Emotion whenever you need to modify SASS styles.

Types

In general, you should let TypeScript infer the types so that there's no need to explicitly define the type for each variable.

There are some exceptions to this:

// TypeScript needs to know the type of arrays or objects; otherwise, it infers type as an array of any

// bad
const stringArray = [];

// good
const stringArray: string[] = [];

Specify function return types explicitly in new code. This improves readability by being able to tell what a function returns just by looking at the signature. It also prevents errors when a function's return type is broader than expected by the author.

Note: Linting is not enabled for this issue because there is old code that needs to be fixed first.

// bad
function transform(value?: string) {
  if (!value) {
    return undefined;
  }
  return applyTransform(value);
}

// good
function transform(value?: string): TransformedValue | undefined {
  if (!value) {
    return undefined;
  }
  return applyTransform(value);
}

Variables

Use PascalCase. For example:

Use camelCase.

// bad
const QueryTargets = [];
// bad
const query_targets = [];

// good
const queryTargets = [];

Code organization

Organize your code in a directory that encloses feature code:

  • Put Redux state and domain logic code in the state directory (for example, features/my-feature/state/actions.ts).
  • Put React components in the components directory (for example, features/my-feature/components/ButtonPeopleDreamOf.tsx).
  • Put test files next to the test subject.
  • Put containers (pages) in the feature root (for example, features/my-feature/DashboardPage.tsx).
  • Put API function calls that aren't a Redux thunk in an api.ts file within the same directory.
  • Subcomponents should live in the component folders. Small components don't need their own folder.
  • Component SASS styles should live in the same folder as component code.

For code that needs to be used by an external plugin:

  • Put components and types in @grafana/ui.
  • Put data models and data utilities in @grafana/data.
  • Put runtime services interfaces in @grafana/runtime.

Exports

  • Use named exports for all code you want to export from a file.
  • Use declaration exports (that is, export const foo = ...).
  • Avoid using default exports (for example, export default foo).
  • Export only the code that is meant to be used outside the module.

Code comments

  • Use TSDoc comments to document your code.
  • Use react-docgen comments (/** ... */) for props documentation.
  • Use inline comments for comments inside functions, classes, etc.
  • Please try to follow the code comment guidelines when adding comments.

Linting

Linting is performed using @grafana/eslint-config.

Functional components

Use function declarations instead of function expressions when creating a new React functional component. For example:

// bad
export const Component = (props: Props) => { ... }

// bad
export const Component: React.FC<Props> = (props) => { ... }

// good
export function Component(props: Props) { ... }

State management

  • Don't mutate state in reducers or thunks.
  • Use createSlice. See Redux Toolkit for more details.
  • Use reducerTester to test reducers. See Redux framework for more details.
  • Use state selectors to access state instead of accessing state directly.