Sentry Frontend Contribution Guide for the ReactJS Ecosystem

This guide explains how to contribute to Sentry's frontend codebase, covering repository layout, file naming, index usage, React component patterns, PropTypes, event‑handler conventions, Emotion styling, lint rules, TypeScript defaultProps, state management, testing with React Testing Library, Storybook setup, and migration from grid‑emotion to modern Emotion APIs.

Hacker Afternoon Tea
Hacker Afternoon Tea
Hacker Afternoon Tea
Sentry Frontend Contribution Guide for the ReactJS Ecosystem

Directory Structure

Frontend code lives under src/sentry/static/sentry/app and static/getsentry. Future work will align static/getsentry with static/sentry.

File Naming

Name files based on module functionality.

Avoid unnecessary prefixes/suffixes; prefer paths like dataScrubbing/editModal instead of dataScrubbingEditModal.

Index Files

An index file provides an implicit import entry point for a folder.

Use index only for true entry points (e.g., a folder that groups related components with a main component).

Do not create index for folders that contain unrelated components.

Do not use index merely to re‑export; import components directly.

React Component Guidelines

Defining Components

When a component needs this, use class syntax with arrow‑function class fields:

class Note extends React.Component {
  static propTypes = {
    author: PropTypes.object.isRequired,
    onEdit: PropTypes.func.isRequired,
  };

  handleChange = value => {
    const user = ConfigStore.get('user');
    if (user.isSuperuser) {
      this.props.onEdit(value);
    }
  };

  render() {
    const {content} = this.props;
    return <div onChange={this.handleChange}>{content}</div>;
  }
}
export default Note;

Older components may use createReactClass and mixins, but these are deprecated.

Components vs. Views

Place reusable UI components in app/components/ and UI views (typically not reused) in app/views. Each component should have an accompanying .stories.js file for Storybook.

PropTypes

Prefer specific validators: PropTypes.arrayOf over PropTypes.array, PropTypes.shape over PropTypes.object.

Define the shape of well‑defined objects explicitly.

PropTypes.shape({
  username: PropTypes.string.isRequired,
  email: PropTypes.string,
})

Import shared custom prop‑type collections from proptype rather than using a default lodash import.

Event Handlers

Prefix handler functions with handle (e.g., this.handleDelete) and event‑callback props with on (e.g., onClick={this.props.onDelete}).

CSS and Emotion

Use Emotion with the theme object ( props.theme) for styling.

Prefer CSS‑in‑JS over global selectors.

Access constants such as z-indexes, paddings, and colors from the theme.

import styled from 'react-emotion';
const SomeComponent = styled('div')`
  border-radius: 1.45em;
  font-weight: bold;
  z-index: ${p => p.theme.zIndex.modal};
  padding: ${p => p.theme.grid}px ${p => p.theme.grid * 2}px;
  border: 1px solid ${p => p.theme.borderLight};
  color: ${p => p.theme.purple};
  box-shadow: ${p => p.theme.dropShadowHeavy};
`;
export default SomeComponent;

Deprecated reflexbox components ( Flex, Box) should no longer be used.

Stylelint Errors

When using a styled component as a selector, add a comment to silence stylelint:

const ButtonBar = styled('div')`
  /* sc-selector */
  Button) { border-radius: 0; }
`;

State Management

The project currently uses Reflux (a Flux‑style library). Stores live under app/stores, actions under app/actions, and action creators under app/actionCreators. Alternative libraries are being explored.

References:

https://github.com/reflux/refluxjs

https://facebook.github.io/flux/docs/overview.html

Testing

Tests are migrating from Enzyme to React Testing Library (RTL). Test files must end with .spec.jsx for Jest to run them.

Key RTL guidelines:

Prefer getBy... queries; use queryBy... only to assert non‑existence.

Use await findBy... for elements that appear asynchronously.

Prefer getByRole with accessible names.

Use screen instead of destructuring query functions from the render result.

Avoid waitFor; use findBy... or waitForElementToBeRemoved for disappearance.

Prefer jest-dom assertions such as toBeInTheDocument and toHaveTextContent.

import {mountWithTheme, screen} from 'sentry-test/reactTestingLibrary';
mountWithTheme(<Example />);
expect(screen.getByRole('alert')).toBeInTheDocument();
expect(screen.getByRole('alert')).toHaveTextContent('abc');
expect(screen.queryByRole('button')).not.toBeInTheDocument();

Use userEvent instead of fireEvent for realistic interactions:

import {mountWithTheme, screen, userEvent} from 'sentry-test/reactTestingLibrary';
mountWithTheme(<Example />);
userEvent.type(screen.getByLabelText('Search by name'), 'sentry');

TypeScript DefaultProps

With TypeScript 3.0+, default props can be typed without Partial. Example for a class component:

type DefaultProps = {size: 'Small' | 'Medium' | 'Large'};
type Props = DefaultProps & {name: string; codename?: string};
class Planet extends React.Component<Props> {
  static defaultProps: DefaultProps = {size: 'Medium'};
  render() {
    const {name, size, codename} = this.props;
    return <p>{name} is a {size.toLowerCase()} planet.{codename && ` Its codename is ${codename}`}</p>;
  }
}

For function components, use default parameters and avoid defaultProps (deprecated):

type Props = {name: string; size?: 'Small' | 'Medium' | 'Large'; codename?: string};
const Planet = ({name, size = 'Medium', codename}: Props) => (
  <p>{name} is a {size.toLowerCase()} planet.{codename && ` Its codename is ${codename}`}</p>
);

Hooks Usage

Prefer library‑provided hooks when available. Use React built‑in hooks ( useState, useEffect, etc.) for simple state. Avoid long chains of useEffect; consider class components for highly stateful logic. Do not use useReducer unless a clear need exists.

When moving away from Reflux, share state via useContext and custom hooks.

Storybook

Run locally with npm run storybook from the repository root. Deployments are handled by Vercel per pull request; the main Storybook is at https://storybook.sentry.dev.

Migration – grid‑emotion

grid‑emotion

is deprecated; replace <Flex> and <Box> with Emotion‑styled components.

const Flex = styled('div')`display: flex;`;
const Box = styled('div')``;

Update margin, padding, and flexbox props to use CSS values (e.g., margin: ${space(2)}) instead of shorthand props ( m={2}, mx={2}, etc.). Ensure no remaining grid‑emotion imports.

Additional Resources

https://github.com/getsentry/eslint-config-sentry

https://emotion.sh/

https://testing-library.com/docs/react-testing-library/intro

https://storybook.js.org/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

FrontendTypeScriptReActTestingSentryStorybookLintingEmotion
Hacker Afternoon Tea
Written by

Hacker Afternoon Tea

You might find something interesting here ^_^

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.