React Coding Standards#
Reference: airbnbReact
React Documentation: reactDocs
Table of Contents#
File Structure:#
- public/src
- assets
global.css file
images/svgs, etc
- components
components that are reused globally across multiple features and pages, not pages or specific components. Group different components in folders if necessary like a “form” folder can have input components, select components, checkbox components, etc. You would have the specfic form component located in the feature folder, and then import the form elements if needed.
test folder for components, and a test folder within each folder in components
- context
for any contexts in app
- test folder for context files
tests related to anything in “context” folder
- data
json data
constant variables
- features
- folders for each feature added to the app
each of these folders would have a similar folder structure of components, hooks, services, context etc like the src/public folder.
each folder will have an index.ts file with the sole purpose of exporting everything used in this feature outside of this feature, so we only import from
- hooks
custom hooks that are globally used, specific hooks for specific features should be located within the appropriate feature folder.
tests folder for hooks
- layouts
specific for components that are related to layouts, like sidebar, navbar, page containers, etc.
test folder for layouts
- lib
for 3rd party libraries and api calls. create pure functions following the facade pattern. For indexeddb calls, fetch requests, axios requests, etc.
tests for lib
- pages
one .ts file for each page, that are purely used for import from features folder and implement them where they belong.
- services
for integrating service based api calls
tests for services
- utils
global utility functions that are used by several different components across multiple features, should be pure functions (no side affects and will always give you the same output with the same input) that can be used for different calculations and things like that.
tests
App.tsx
index.tsx
test files should be located in folders, as close to the components and functions that are being tested as possible.
Component Structure and Organization#
Basic Rules#
- Only include one React component per file.
You can use multiple stateless components per file.
Always use JSX syntax.
Do not use
React.createElementunless you are initializing the app from a file that is not JSX.Use function React Components instead of class React Components
Naming Conventions#
Extensions: use
.jsx(JavaScript) and.tsx(TypeScript) extensions for React Components.Filename: Use PascalCase for filenames. Ex:
ExactControlsComponent.jsx.Reference Naming: Use PascalCase for React Components and camelCase for their instances.
// TypeScript Component Definition
const ExactControlsComponent = ({ props }: PropsType) => {
return <h1>Hello World</h1>;
};
// JavaScript Component Definition
const ExactControlsComponent = ({ props }) => {
return <h1>Hello World</h1>;
};
// Component Reference
import ExactControlsComponent from './ExactControlsComponent';
// Instance
const exactControlsComponent = <ExactControlsComponent />;
Component Naming: Use descriptive names that indicate the component’s purpose
Higher-order Component Naming: Use a composite of the higher-order component’s name and the passed-in component’s name as the displayName on the generated component. For example, the higher-order component withFoo(), when passed a component Bar should produce a component with a displayName of withFoo(Bar).
Display Names#
Set explicit display names for debugging purposes, especially for HOCs and wrapped components
Use descriptive names that match the component’s purpose
Always set display names for components created with
React.memo,React.forwardRef, or other React APIs
// For regular components, the display name is inferred
const MyComponent = () => <div>Hello</div>;
// For HOCs, set explicit display names
const withData = (WrappedComponent) => {
const WithData = (props) => {
// ... component logic
return <WrappedComponent {...props} />;
};
WithData.displayName = `WithData(${getDisplayName(WrappedComponent)})`;
return WithData;
};
// For React.memo
const MemoizedComponent = React.memo(function MyComponent() {
return <div>Hello</div>;
});
MemoizedComponent.displayName = 'MemoizedComponent';
// Helper function to get component name
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
Alignment#
// ❌ Bad
<Foo superLongParam="bar"
anotherSuperLongParam="baz" />
// ✅ Good
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
// if props fit in one line then keep it on the same line
<Foo bar="bar" />
// children get indented normally
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
>
<Quux />
</Foo>
// ❌ Bad
{showButton &&
<Button />
}
// ❌ Bad
{
showButton &&
<Button />
}
// ✅ Good
{showButton && (
<Button />
)}
// ✅ Good
{showButton && <Button />}
// ✅ Good
{someReallyLongConditional
&& anotherLongConditional
&& (
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
)
}
// ✅ Good
{someConditional ? (
<Foo />
) : (
<Foo
superLongParam="bar"
anotherSuperLongParam="baz"
/>
)}
Quotes#
Always use double quotes (
") for JSX attributes and property values, not single quotes (').HTML attributes typically use double quotes, so we should follow that convention.
Spacing#
Always include a single space between the tag and the attributes.
// ❌ Bad
<Foo/>
// ❌ Very Bad
<Foo />
// ❌ Bad
<Foo
/>
// ✅ Good
<Foo />
Do not pad JSX curly braces with spaces.
// ❌ Bad
<Foo bar={ baz } />
// ✅ Good
<Foo bar={baz} />
State Management#
Props and State Naming Conventions#
Props#
Always use camelCase for prop names, or PascalCase if the prop value is a React component.
Omit the value of the prop when it is explicitly true.
// ❌ Bad
<Foo
hidden={true}
/>
// ✅ Good
<Foo
hidden
/>
// ✅ Good
<Foo hidden />
Always include an alt prop on <img> tags. If the image is presentational, alt can be an empty string or the <img> must have role=”presentation”.
Do not use words like “image”, “photo”, or “picture” in <img> alt props.
- Avoid using an array index as key prop, prefer a stable ID.
Not using a stable ID is an anti-pattern because it can negatively impact performance and cause issues with component state.
Especially don’t use indexes for keys if the order of items may change.
Refs#
Always use ref callbacks.
// ❌ Bad
<Foo
ref="myRef"
/>
// ✅ Good
<Foo
ref={(ref) => { this.myRef = ref; }}
/>
Parentheses#
Wrap JSX tags in parentheses when they span multiple lines.
// ❌ Bad
return (
<MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>
);
// ✅ Good
return (
<MyComponent variant="long body" foo="bar">
<MyChild />
</MyComponent>
);
// ✅ Good, when single line
return (
const body = <div>hello</div>;
<MyComponent>{body}</MyComponent>
)
Hooks Usage#
Naming Conventions#
Custom hooks must start with “use” prefix (e.g.,
useCustomHook)Use camelCase for hook names
Name should clearly indicate the hook’s purpose (e.g.,
useWindowSize,useFetchUser)
Basic Rules#
Only call hooks at the top level of your function
Only call hooks from React function components or custom hooks
Don’t call hooks inside loops, conditions, or nested functions
// ❌ Bad - Hook inside condition
function Component() {
if (condition) {
useEffect(() => {}, []);
}
}
// ✅ Good - Condition inside hook
function Component() {
useEffect(() => {
if (condition) {
// Do something
}
}, [condition]);
}
Dependencies#
Always specify dependencies array for
useEffect,useCallback, anduseMemoInclude all values from the component scope that change over time and are used by the effect
Use ESLint plugin
eslint-plugin-react-hookswithexhaustive-depsrule
// ❌ Bad - Missing dependency
function Component({ id }) {
useEffect(() => {
fetchData(id);
}, []); // id is missing in deps
// ✅ Good - All dependencies included
function Component({ id }) {
useEffect(() => {
fetchData(id);
}, [id]);
}
Custom Hooks#
Extract complex logic into custom hooks for reusability
Keep hooks focused on a single responsibility
Document hook parameters and return values
// ✅ Good - Custom hook example
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
State Management#
Use
useStatefor simple state managementPrefer
useReducerfor complex state logicSplit state into multiple
useStatecalls if values are unrelated
// ❌ Bad - Combining unrelated state
const [state, setState] = useState({
isLoading: false,
error: null,
user: null,
theme: 'light'
});
// ✅ Good - Separate state hooks
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
Performance Optimization#
Use
useMemofor expensive computationsUse
useCallbackfor functions passed as props to child componentsDon’t overuse these hooks for simple operations
// ❌ Bad - Unnecessary use of useMemo
const fullName = useMemo(() => {
return firstName + ' ' + lastName;
}, [firstName, lastName]);
// ✅ Good - Complex computation
const sortedAndFilteredItems = useMemo(() => {
return items
.filter(item => item.active)
.sort((a, b) => b.price - a.price);
}, [items]);
Cleanup#
Always clean up side effects in
useEffectReturn cleanup function for subscriptions, timers, and event listeners
// ✅ Good - Proper cleanup
useEffect(() => {
const subscription = api.subscribe(data => {
// Handle data
});
// Cleanup function
return () => {
subscription.unsubscribe();
};
}, [api]);
Styling#
Component Style Organization#
- CSS Modules (Recommended)
Keep component styles in separate
.module.cssfiles next to componentsUse meaningful class names that match component structure
Import styles with descriptive names
// Button/
// ├── Button.tsx
// ├── Button.module.css
// └── index.ts
// Button.tsx
import styles from './Button.module.css';
const Button = ({ variant = 'primary' }) => (
<button className={`${styles.button} ${styles[variant]}`}>
Click me
</button>
);
- Inline Styles
Avoid inline styles except for dynamic, computed values
Use style objects for complex dynamic styles
// ❌ Bad
<div style={{ padding: '20px', backgroundColor: 'blue' }} />
// ✅ Good - Dynamic values
<div style={{ height: `${size}px` }} />
// ✅ Good - Complex dynamic styles
const dynamicStyles = {
transform: `translateX(${position}px)`,
opacity: isVisible ? 1 : 0
};
<div style={dynamicStyles} />
- className Props
Use template literals for conditional classes
Consider utility libraries like
classnamesfor complex conditions
// ✅ Good - Simple conditional
<div className={`card ${isActive ? 'active' : ''}`} />
// ✅ Good - Using classnames library
import classNames from 'classnames';
<div className={classNames({
'card': true,
'active': isActive,
'disabled': isDisabled
})} />
Performance Considerations#
Component Optimization#
Memoization - Use
React.memo()for expensive components that re-render frequently with the same props - Implement custom comparison functions when needed
// ✅ Good - Basic memoization
const ExpensiveComponent = React.memo(({ data }) => {
// Complex rendering logic
return <div>{/* ... */}</div>;
});
// ✅ Good - Custom comparison
const areEqual = (prevProps, nextProps) => {
return prevProps.data.id === nextProps.data.id;
};
const ExpensiveComponent = React.memo(({ data }) => {
// Complex rendering logic
return <div>{/* ... */}</div>;
}, areEqual);
Code Splitting - Use dynamic imports for route-based code splitting - Lazy load components that are not immediately needed
// ✅ Good - Route-based code splitting
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}
// ✅ Good - Component lazy loading
const HeavyChart = React.lazy(() => import('./components/HeavyChart'));
List Rendering#
Virtual Lists - Use virtualization for long lists (react-window or react-virtualized) - Implement infinite scrolling for large datasets
import { FixedSizeList } from 'react-window';
// ✅ Good - Virtualized list
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index]}
</div>
);
return (
<FixedSizeList
height={400}
width={300}
itemCount={items.length}
itemSize={35}
>
{Row}
</FixedSizeList>
);
}
Key Management - Use stable, unique keys for list items - Avoid using array index as key when list order can change
// ❌ Bad - Using index as key
{items.map((item, index) => (
<Item key={index} data={item} />
))}
// ✅ Good - Using stable ID
{items.map((item) => (
<Item key={item.id} data={item} />
))}
State and Props Management#
State Colocation - Keep state as close as possible to where it’s used - Avoid lifting state unnecessarily
// ❌ Bad - State too high in tree
function Parent() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<OtherComponent />
<Modal isOpen={isOpen} setIsOpen={setIsOpen} />
</div>
);
}
// ✅ Good - State colocated with usage
function Parent() {
return (
<div>
<OtherComponent />
<Modal />
</div>
);
}
function Modal() {
const [isOpen, setIsOpen] = useState(false);
// ... modal logic
}
Expensive Calculations - Use
useMemofor expensive computations - UseuseCallbackfor function props that trigger child re-renders
// ✅ Good - Memoized calculation
function DataGrid({ data }) {
const sortedData = useMemo(() => {
return [...data].sort((a, b) => b.value - a.value);
}, [data]);
const handleSort = useCallback((column) => {
// Sort logic
}, []);
return <Table data={sortedData} onSort={handleSort} />;
}
Event Handling#
Debouncing and Throttling - Debounce search inputs and form submissions - Throttle scroll and resize event handlers
// ✅ Good - Debounced search
function SearchInput() {
const debouncedSearch = useCallback(
debounce((term) => {
// API call
}, 300),
[]
);
return (
<input
type="text"
onChange={(e) => debouncedSearch(e.target.value)}
/>
);
}
Event Delegation - Use event delegation for lists of similar items - Avoid attaching handlers to every item
// ❌ Bad - Handler per item
function List({ items }) {
return items.map(item => (
<div key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</div>
));
}
// ✅ Good - Event delegation
function List({ items }) {
const handleClick = (e) => {
const id = e.target.dataset.id;
if (id) {
// Handle click
}
};
return (
<div onClick={handleClick}>
{items.map(item => (
<div key={item.id} data-id={item.id}>
{item.name}
</div>
))}
</div>
);
}
Rendering Optimization#
Avoid Unnecessary Renders - Use React DevTools Profiler to identify unnecessary renders - Break down complex components into smaller, focused ones
Batch Updates - Group state updates together - Use callback form of setState for dependent updates
// ❌ Bad - Multiple separate updates
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
setCount(count + 1); // Won't work as expected
};
}
// ✅ Good - Batched update with callback
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prev => prev + 2);
};
}
Testing#
Testing is not yet implemented, this is a recommendeded list of best practices
Testing Libraries#
Use React Testing Library as the primary testing tool
Jest as the test runner
Mock Service Worker (MSW) for API mocking
User-event for simulating user interactions
Component Testing#
Test Component Behavior - Test what users see and interact with - Avoid testing implementation details - Use semantic queries
// ❌ Bad - Testing implementation details
test('sets loading state', () => {
const { getByTestId } = render(<UserProfile />);
expect(getByTestId('loading-state')).toBeInTheDocument();
});
// ✅ Good - Testing user behavior
test('displays user information after loading', async () => {
const { getByRole, findByText } = render(<UserProfile />);
// User sees loading state
expect(getByRole('status')).toBeInTheDocument();
// User sees their information
expect(await findByText('John Doe')).toBeInTheDocument();
});
Query Priority - Follow Testing Library’s query priority recommendations - Prefer accessible queries over test IDs
// Query priority (best to worst):
// 1. getByRole
// 2. getByLabelText
// 3. getByPlaceholderText
// 4. getByText
// 5. getByDisplayValue
// 6. getByAltText
// 7. getByTitle
// 8. getByTestId
// ❌ Bad - Using test ID
const submitButton = getByTestId('submit-button');
// ✅ Good - Using role and accessible name
const submitButton = getByRole('button', { name: /submit/i });
Hook Testing#
Testing Custom Hooks - Use
@testing-library/react-hooks- Test the behavior, not the implementation - Test error cases and edge conditions
// useCounter.test.tsx
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';
test('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
test('handles negative values', () => {
const { result } = renderHook(() => useCounter(-1));
expect(result.current.count).toBe(0); // Assuming minimum value is 0
});
Integration Testing#
API Mocking - Use MSW to intercept and mock API calls - Test success and error scenarios - Keep mock responses realistic
// handlers.ts
import { rest } from 'msw';
export const handlers = [
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
])
);
}),
rest.get('/api/users/:id', (req, res, ctx) => {
const { id } = req.params;
return res(
ctx.status(200),
ctx.json({ id: Number(id), name: 'John' })
);
})
];
// UserList.test.tsx
import { screen, render } from '@testing-library/react';
import { UserList } from './UserList';
test('displays user list', async () => {
render(<UserList />);
expect(await screen.findByText('John')).toBeInTheDocument();
expect(await screen.findByText('Jane')).toBeInTheDocument();
});
User Interactions - Use
@testing-library/user-eventoverfireEvent- Test complete user workflows - Include keyboard navigation tests
// Form.test.tsx
import userEvent from '@testing-library/user-event';
test('submits form with user data', async () => {
const user = userEvent.setup();
const handleSubmit = jest.fn();
render(<UserForm onSubmit={handleSubmit} />);
await user.type(
screen.getByRole('textbox', { name: /name/i }),
'John Doe'
);
await user.type(
screen.getByRole('textbox', { name: /email/i }),
'john@example.com'
);
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(handleSubmit).toHaveBeenCalledWith({
name: 'John Doe',
email: 'john@example.com'
});
});
Test Organization#
File Structure - Keep test files close to implementation - Use descriptive test file names - Group related tests together
/components
/UserProfile
UserProfile.tsx
UserProfile.test.tsx
UserProfile.module.css
Test Structure - Use descriptive test names - Follow Arrange-Act-Assert pattern - Group related tests with
describe
describe('UserProfile', () => {
describe('when user is logged in', () => {
test('displays user information', async () => {
// Arrange
const user = { name: 'John', email: 'john@example.com' };
render(<UserProfile user={user} />);
// Act - nothing needed for this test
// Assert
expect(screen.getByText(user.name)).toBeInTheDocument();
expect(screen.getByText(user.email)).toBeInTheDocument();
});
test('allows editing profile', async () => {
// Test implementation
});
});
describe('when user is logged out', () => {
test('displays login prompt', () => {
// Test implementation
});
});
});
Best Practices#
General Guidelines - Write tests that resemble how users use the application - Test component behavior, not implementation details - Keep tests focused and concise - Use meaningful test descriptions - Clean up after each test
Avoid Testing - Implementation details - Third-party library functionality - Non-deterministic behavior without proper mocking - Static content that rarely changes
Common Patterns - Setup reusable test utilities - Use custom render functions for common providers - Create test data factories - Mock complex dependencies
// test-utils.tsx
import { render } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
const customRender = (ui, options = {}) => {
return render(ui, {
wrapper: ({ children }) => (
<ThemeProvider>{children}</ThemeProvider>
),
...options
});
};
// Re-export everything
export * from '@testing-library/react';
export { customRender as render };