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.
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‑emotionis 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/
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
