This guide is based on best practices for writing clean, maintainable, and scalable TypeScript code. It includes specific rules and conventions to follow when writing TypeScript in React projects.
Consistency is key to maintaining a high-quality, scalable, and maintainable codebase. This TypeScript Style Guide enforces best practices using automated tools like ESLint, TypeScript, and Prettier, while also providing design and architectural conventions that developers should follow.
As projects grow, maintaining a uniform coding style and ensuring best practices become increasingly important. A well-defined style guide provides:
- A consistent codebase, reducing technical debt.
- Faster development cycles with fewer disputes over code style.
- Easier onboarding for new developers by minimizing learning curves.
- Improved readability and maintainability across the team.
Like any style guide, this one is opinionated and may not fit every project perfectly. Use it as a baseline and adapt it as necessary to fit your team’s needs while maintaining internal consistency.
This style guide assumes the use of:
- TypeScript v5 or later
- typescript-eslint with strict type-checking enabled
- Prettier for automatic formatting
- React for frontend conventions (though not required)
✅ Prefer const for immutability and readonly properties
✅ Use union types over enums when possible
✅ Avoid any and prefer unknown or proper type definitions
✅ Use PascalCase for components and camelCase for variables/functions
✅ Keep functions pure, stateless, and single-responsibility
✅ Follow consistent naming conventions across the codebase
✅ Prefer explicit return types for functions
By following these conventions, your TypeScript projects will be more predictable, efficient, and scalable. 🚀
- Basic Rules
- Type Annotations
- Interfaces vs. Types
- Class vs Functional Components
- Mixins
- Naming Conventions
- Declarations & Alignment
- Quotes & Spacing
- Props & State
- Refs & Parentheses
- Tags & Methods
- Component Ordering
- Enums
- Use TypeScript syntax and avoid
anyunless absolutely necessary. - Always define types for function parameters and return values.
- Enable strict mode in
tsconfig.json. - Use
constandletinstead ofvar. - Prefer
readonlyfor immutable object properties. - Do not use
namespace; use ES6 modules instead. - Avoid non-null assertions (
!), except when absolutely necessary.
- Always specify types explicitly when the type is not inferred.
- Avoid redundant type annotations where TypeScript can infer them.
// Bad
let age: number = 25;
// Good
let age = 25; // TypeScript infers `number`- Use
unknowninstead ofanywhere applicable. - Use
neverfor functions that never return.
// Bad
function throwError(message: string): void {
throw new Error(message);
}
// Good
function throwError(message: string): never {
throw new Error(message);
}- Use
interfacefor defining object shapes. - Use
typefor unions, intersections, or primitive-based types.
// Bad
type User = {
id: number;
name: string;
};
// Good
interface User {
id: number;
name: string;
}- Use
extendsfor interface inheritance.
// Bad: Using type for extending an object type
type AdminProps = UserProps & {
role: "admin" | "super-admin";
};
// Good: Using interface with extends
interface AdminProps extends UserProps {
role: "admin" | "super-admin";
}
const AdminCard: React.FC<AdminProps> = ({ id, name, role }) => {
return (
<div>
<h3>Admin ID: {id}</h3>
<p>Name: {name}</p>
<p>Role: {role}</p>
</div>
);
};
export default AdminCard;- Prefer function components over class components.
- Use
React.FC<T>for defining functional components with props.
// Bad
class UserProfile extends React.Component<{ name: string }> {
render() {
return <div>Hello, {this.props.name}</div>;
}
}
// Good
const UserProfile: React.FC<{ name: string }> = ({ name }) => {
return <div>Hello, {name}</div>;
};- Use hooks instead of class-based lifecycle methods.
- Do not use mixins.
- Prefer Higher-Order Components (HOCs) or custom hooks.
// Bad
const WithLogging = (Base: any) => class extends Base {
log() {
console.log("Logging...");
}
};
// Good
function useLogging() {
useEffect(() => {
console.log("Logging...");
}, []);
}- Use PascalCase for components.
// ❌ Bad: Using lowercase or camelCase for a component name
function userprofile() {
return <div>User Profile</div>;
}
export default userprofile;
// ✅ Good: Using PascalCase for the component name
function UserProfile() {
return <div>User Profile</div>;
}
export default UserProfile;- Use camelCase for variables and functions.
// ❌ Bad: Using uppercase or PascalCase for variables
const UserName = "Alice";
function GetUserAge() {
return 30;
}
// ✅ Good: Use camelCase for variables and functions
const userName = "Alice";
function getUserAge() {
return 30;
}- Use
Tprefix for generic type parameters.
// ❌ Bad: Generic type has no `T` prefix
function getFirstItem<Type>(arr: Type[]): Type {
return arr[0];
}
// ✅ Good: Generic type prefixed with `T`
function getFirstItem<T>(arr: T[]): T {
return arr[0];
}- Use meaningful names.
// ❌ Bad: Unclear variable names
const x = "John";
const d = new Date();
function doSomething() {
return "Hello";
}
// ✅ Good: Meaningful variable and function names
const userName = "John";
const currentDate = new Date();
function generateGreeting() {
return "Hello";
}- Naming Conventions in Props & State
// ❌ Bad: Non-standard prop names
interface UserProps {
User_Name: string;
AGE: number;
}
// ✅ Good: camelCase for props
interface UserProps {
userName: string;
age: number;
}- Use single responsibility per file.
// Bad: Multiple components in a single file
const Header = () => <header>Header</header>;
const Footer = () => <footer>Footer</footer>;
export { Header, Footer };
// Good: Separate files for each component
// Header.tsx
const Header = () => <header>Header</header>;
export default Header;
// Footer.tsx
const Footer = () => <footer>Footer</footer>;
export default Footer;- Use
export defaultfor components.
// Bad: Named export for a single component
export const Button = () => <button>Click me</button>;
// Good: Default export for a single component
const Button = () => <button>Click me</button>;
export default Button;- Maintain consistent indentation and spacing.
- Use single quotes for JavaScript/TypeScript.
// Bad
const message = "Hello World";
// Good
const message = 'Hello World';- Use double quotes for JSX attributes.
// Bad
<Button label='Click me' />;
// Good
<Button label="Click me" />;- Do not pad JSX curly braces.
// Bad
<Component prop={ someValue } />;
// Good
<Component prop={someValue} />;- Always use interfaces or types for props.
// Bad: No type definition
const Button = (props) => <button>{props.label}</button>;
// Good: Using an interface for props
interface ButtonProps {
label: string;
}
const Button: React.FC<ButtonProps> = ({ label }) => <button>{label}</button>;- Avoid
any; preferunknownwhen necessary.
// Bad
function processInput(input: any) {
console.log(input);
}
// Good
function processInput(input: unknown) {
if (typeof input === 'string') {
console.log(input.toUpperCase());
}
}- Use defaultProps or default function parameters for optional props.
// Bad
interface UserProps {
name?: string;
}
const User = ({ name }: UserProps) => <div>{name ? name : 'Guest'}</div>;
// Good
interface UserProps {
name?: string;
}
const User = ({ name = 'Guest' }: UserProps) => <div>{name}</div>;- Use ref callbacks over string refs.
// Bad
<input ref="myInput" />;
// Good
const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} />;- Wrap JSX in parentheses when multiline.
// Bad
return <div>
<Header />
<Main />
<Footer />
</div>;
// Good
return (
<div>
<Header />
<Main />
<Footer />
</div>
);- Use self-closing tags where applicable.
// Bad
<img src="image.jpg"></img>;
// Good
<img src="image.jpg" />;- Use arrow functions for event handlers.
// Bad
function handleClick() {
console.log('Clicked');
}
// Good
const handleClick = () => {
console.log('Clicked');
};For class components, order methods as follows:
- State and Refs (using useState and useRef)
- Derived Values and Computed Variables
- Effects (useEffect)
- Event Handlers
- Render Logic (JSX return statement)
const ExampleComponent: React.FC = () => {
// 1. State and Refs
const [count, setCount] = useState(0);
const buttonRef = useRef<HTMLButtonElement>(null);
// 2. Derived Values and Computed Variables
const buttonLabel = `Clicked ${count} times`;
// 3. Effects
useEffect(() => {
console.log('Component mounted');
}, []);
// 4. Event Handlers
const handleClick = () => {
setCount((prevCount) => prevCount + 1);
};
// 5. Render Logic
return (
<button ref={buttonRef} onClick={handleClick}>
{buttonLabel}
</button>
);
};
export default ExampleComponent;
```ts
### Why This Order?
- Keeps related logic grouped together
- Improves readability and maintainability
- Prevents unnecessary re-renders by optimizing state updates
---
## 13. Enums
Enums allow defining a set of named constants. Use them when a variable can take one of a few predefined values.
### Numeric Enums
```ts
enum Status {
Pending,
InProgress,
Completed
}enum Status {
Pending = "Pending",
InProgress = "InProgress",
Completed = "Completed"
}type Status = "Pending" | "InProgress" | "Completed";This style guide ensures your TypeScript code follows best practices while staying maintainable and scalable. Adhering to these conventions will improve consistency across projects and facilitate collaboration among developers.