🔗
Table of contents
- Types should start with T:
type TComponentProps = {
name: string;
age: number;
};
- Interfaces should start with I:
GOOD
interface IComponentProps {
name: string;
age: number;
}
BAD
interface ComponentProps {
name: string;
age: number;
}
- Avoid Explicit Typing When It Can Be Inferred:
// Avoid this:
const [hex, setHex] = useState<string>('#FF0000');
// Use this instead:
const [hex, setHex] = useState('#FF0000');
- Use
interface
for Props and Name It withProps
Suffix:
// Use interface and name it descriptively with Props suffix
interface IContrastCheckerProps {
color: string;
onChange: (value: string) => void;
className?: string;
}
- Pages and Components use kebab-case
- Components: To improve file organization, you should create a folder for each component. The folder name should follow the kebab-case convention, and the main file for the component should be named
index.tsx
.- Folder:
button-component/
- Main file:
index.tsx
(inside the folder)
- Folder:
- Page file:
page.tsx
- Components: To improve file organization, you should create a folder for each component. The folder name should follow the kebab-case convention, and the main file for the component should be named
- File name must include its type:
- Utilities:
calculate-area.utils.ts
- Hooks:
use-auth.hooks.ts
- Utilities:
- Pages, Layout, Components and Hooks: Export as default (RAFCE):
const MainPage = () => {
return <div>Main Page</div>;
}
export default MainPage
const Box = () => {
return <div>Box Component</div>;
};
export default Box;
const useAuth = () => {
// Hook logic here
};
export default useAuth;
- Models and Utilities: Export with named export:
const calculateArea = (width: number, height: number): number => {
return width * height;
}
export {calculateArea}
type TRectangle = {
width: number;
height: number;
};
export type {TRectangle}
Component Example:
// file: button-component.tsx
interface IButtonProps {
label: string;
onClick: () => void;
};
const Button = ({ label, onClick }: IButtonProps) => {
return <button onClick={onClick}>{label}</button>;
};
export default Button;
Page Example:
// file: main-page.tsx
const MainPage = () => {
return <div>Main Page</div>;
}
export default MainPage
Hook Example:
// file: use-auth.hooks.ts
const useAuth = () => {
const login = (username: string, password: string) => {
// Logic for login
};
const logout = () => {
// Logic for logout
};
return { login, logout };
};
export default useAuth;
Utility Example:
// file: calculate-area.utils.ts
const calculateArea = (width: number, height: number): number => {
return width * height;
}
export { calculateArea };
Type Example:
// file: rectangle.models.ts
type TRectangle = {
width: number;
height: number;
};
export type {TRectangle};
Interface Example:
// file: user.models.ts
interface IUser {
id: string;
name: string;
email: string;
}
export type {IUser};
- Import Modules from React directly:
// Preferred
import { FC } from 'react';
const MyComponent: FC = () => <div>Hello</div>;
// Avoid
const MyComponent: React.FC = () => <div>Hello</div>;
- Order of Imports (implemented by default with third party):
- Standard library modules (e.g.,
react
,react-dom
,axios
). - Third-party libraries.
- Absolute imports (e.g., aliases like
@/components
). - Relative imports.
- Standard library modules (e.g.,
// Example
import { useState } from 'react'; // Standard library
import axios from 'axios'; // Third-party
import { Button } from '@/components/Button'; // Absolute imports
import { calculateArea } from '../../utils/calculate-area.utils'; // Higher-level relative
import './styles.css'; // Same-level relative
- Named vs. Default Imports:
- Use named imports for utilities and constants.
- Use default imports for components, hooks, or major modules.
// Named Import
import { calculateArea } from './utils';
// Default Import
import Header from './components/Header';
- Avoid Wildcard (
* as
) Imports:
// Avoid
import * as Utils from './utils';
const area = Utils.calculateArea(5, 10);
// Preferred
import { calculateArea } from './utils';
const area = calculateArea(5, 10);
- Access Props Individually:
import { FC } from 'react';
import { Slider } from '@dizno/shared/components';
// Define the interface for the props
export interface ISliderProps {
label: string;
name: string;
min: number;
max: number;
value: number;
onChange: (value: number) => void;
}
const SliderField: FC<ISliderProps> = ({
label,
name,
min,
max,
value,
onChange,
}) => (
<div>
<h4>{label}</h4> {/* Access individual prop */}
<Slider
name={name} {/* Access individual prop */}
min={min} {/* Access individual prop */}
max={max} {/* Access individual prop */}
value={[value]} {/* Access individual prop */}
onValueChange={(val) => onChange(val[0])} {/* Access individual prop */}
/>
</div>
);
export default SliderField;
- use ternary operators instead of short-circuit operators :
// Avoid short-circuit
const count = 0;
return <div>{count && <h1>Messages: {count}</h1>}</div>;
// Prefer ternary
return <div>{count ? <h1>Messages: {count}</h1> : null}</div>;
- Avoid Nested Ternary Operators:
// Avoid nested ternaries
isSubscribed ? (
<ArticleRecommendations />
) : isRegistered ? (
<SubscribeCallToAction />
) : (
<RegisterCallToAction />
);
// Use a dedicated component
const CallToActionWidget = ({ subscribed, registered }) => {
if (subscribed) return <ArticleRecommendations />;
if (registered) return <SubscribeCallToAction />;
return <RegisterCallToAction />;
}
return <CallToActionWidget subscribed={isSubscribed} registered={isRegistered} />;
- Prefer Arrow Functions Over Function Keywords:
//Avoid
// Function declaration
function greet(name) {
return `Hello, ${name}!`;
}
// Function expression
const greet = function (name) {
return `Hello, ${name}!`;
};
// React component
function MyComponent({ name }) {
return <div>Hello, {name}!</div>;
}
//Use
const greet = (name) => {
return `Hello, ${name}!`;
};
// Single-expression functions can omit braces and `return`
const add = (a, b) => a + b;
// React component example
const MyComponent = ({ name }) => <div>Hello, {name}!</div>;
- Avoid Inline Helper Functions for Rendering. Break it down to separate component:
//Avoid
const Colors = () => {
const renderColor = () => {
return <div>Color</div>
}
return (
<div>
{renderColor()}
</div>
);
};
export default Colors
-
Keep the
app
Folder in Next.js Focused on Core Pages and Layouts:In Next.js, the
app
folder should be reserved for the essential pages, layouts, Next.js default files and client-page file. Avoid placing non-essential files or business logic directly in this folder. -
Define PropsType exactly above the defined function for component:
import Link from 'next/link';
import { FC } from 'react';
import { Box } from '@dizno/shared/components';
import { copyToClipboard, isHex, isLightColor } from '@dizno/shared/utilities';
interface IColorPaletteCardProps {
colors: Array<string> | null;
href: string;
title: string;
description?: string;
}
const ColorPaletteCard: FC<IColorPaletteCardProps> = ({
colors,
href,
title,
description,
}) => {
return (
<Box width="100%" height="100%">
ColorPaletteCard
</Box>
);
};
export default ColorPaletteCard;
- keep the primary responsibility of
.tsx
files focused on rendering JSX elements. This means that.tsx
files should mainly handle the structure and presentation of the UI. Any complex logic, utility functions, or business rules should be abstracted into separate files (e.g., hooks, utilities, or models) to maintain a clear separation of concerns.