This document outlines the coding standards and best practices for our React project.
- JavaScript/JSX
- Component Structure
- State Management
- Styling with Tailwind
- Shadcn Components
- Testing
- Documentation
- Use ES6+ features
- Prefer arrow functions for component definitions and callbacks
- Use destructuring for props and state
- Use template literals instead of string concatenation
- Use optional chaining and nullish coalescing
- Components: PascalCase (e.g.,
ProfileCard.jsx) - Files: PascalCase for components, camelCase for utilities
- Functions: camelCase
- Variables: camelCase
- Constants: UPPER_SNAKE_CASE for truly constant values
- Custom Hooks: start with
useprefix (e.g.,useAuth.js)
We use ESLint and Prettier for code formatting. Ensure your editor is configured to use these tools.
// Good example
const ProfileCard = ({ user, isLoading }) => {
const { name, email } = user || {};
return (
<div className="card">
{isLoading ? (
<LoadingSpinner />
) : (
<>
<h2>{name}</h2>
<p>{email}</p>
</>
)}
</div>
);
};Use functional components with hooks instead of class components.
// Preferred
const Component = ({ prop1, prop2 }) => {
const [state, setState] = useState(initialValue);
return <div>...</div>;
};
// Avoid
class Component extends React.Component {
constructor(props) {
super(props);
this.state = { /* ... */ };
}
render() {
return <div>...</div>;
}
}- Imports
- Component definition
- Hooks
- Helper functions
- Effect hooks
- Return statement
- Prop types/exports
// Example organization
import React, { useState, useEffect } from 'react';
import { Button } from '../shadcn/button';
const ExampleComponent = ({ initialData }) => {
// State hooks
const [data, setData] = useState(initialData);
const [loading, setLoading] = useState(false);
// Helper functions
const handleClick = () => {
// Implementation
};
// Effects
useEffect(() => {
// Side effect
}, [data]);
// Render
return (
<div>
{/* JSX */}
</div>
);
};
export default ExampleComponent;- Create small, focused stores
- Use selectors for accessing state
- Implement actions within the store
- Document store purpose and API
// Example Zustand store
import { create } from 'zustand';
const useUserStore = create((set) => ({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
login: async (credentials) => {
set({ isLoading: true, error: null });
try {
const user = await loginService(credentials);
set({ user, isAuthenticated: true, isLoading: false });
return user;
} catch (error) {
set({ error: error.message, isLoading: false });
throw error;
}
},
logout: () => set({ user: null, isAuthenticated: false }),
}));
export default useUserStore;- Use
useStatefor component-specific state - Use
useReducerfor complex state logic - Avoid prop drilling by using context or Zustand
- Use Tailwind's utility classes directly in JSX
- Create reusable component patterns with consistent styling
- Use the
@applydirective in CSS files sparingly - Follow mobile-first responsive design
// Good example
<div className="p-4 mt-2 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
<h2 className="text-xl font-bold text-gray-800">Title</h2>
<p className="mt-2 text-gray-600">Content</p>
</div>For complex, reusable styles, define them in your CSS:
@layer components {
.card-container {
@apply p-4 mt-2 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow;
}
}- Import components from the shadcn directory
- Maintain the component API as defined by shadcn
- Use the variant and size props for customization
- Create wrapper components for frequently used configurations
// Good example
import { Button } from '../shadcn/button';
const SaveButton = ({ isLoading, ...props }) => (
<Button
variant="primary"
disabled={isLoading}
{...props}
>
{isLoading ? 'Saving...' : 'Save'}
</Button>
);- Write tests for all components and utilities
- Use React Testing Library for component tests
- Test user interactions and state changes
- Mock API calls and external dependencies
// Example test
import { render, screen, fireEvent } from '@testing-library/react';
import LoginForm from './LoginForm';
test('submits form with credentials', () => {
const mockSubmit = jest.fn();
render(<LoginForm onSubmit={mockSubmit} />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' }
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'password123' }
});
fireEvent.click(screen.getByRole('button', { name: /login/i }));
expect(mockSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});- Document all components with JSDoc comments
- Include propTypes or TypeScript interfaces
- Provide usage examples in comments
- Document non-obvious code with inline comments
/**
* ProfileCard component displays user information in a card format
*
* @param {Object} props
* @param {Object} props.user - User data object
* @param {string} props.user.name - User's name
* @param {string} props.user.email - User's email
* @param {boolean} [props.isLoading=false] - Loading state flag
*
* @example
* <ProfileCard
* user={{ name: 'John Doe', email: 'john@example.com' }}
* isLoading={false}
* />
*/
const ProfileCard = ({ user, isLoading = false }) => {
// Implementation
};