Your JavaScript Comments Are Secretly Holding You Back
One of the most important principles in software design is that:
“Code is read more than written.” - Raymond Chen
And comments are a great way to improve the readability and understandability of your code.
So in this video, you're going to learn how to write good comments in JavaScript and TypeScript.
This video starts with a deep dive into the syntax for writing comments. And here you can see a quick overview of the different types of comments.
Comment Type | Syntax | Description | Scope |
---|---|---|---|
Single-Line Comment | // comment | Brief comment or inline explanation | Single line |
Multi-Line Comment | /* comment */ | Block comment spanning multiple lines | Multiple lines |
Documentation Comment (JSDoc & TSDoc) | /** comment */ | Structured comments for documentation generation | Functions, classes, etc. |
Triple-Slash Directive | /// <reference ... /> | TypeScript-specific compiler directives | File-level |
Compiler Directive (@ts-ignore etc.) | // @ts-ignore | Instructs TypeScript compiler to alter behavior | Next line or file |
Tooling-Specific Comment | // eslint-disable etc. | Controls behavior of linters and formatters | Line or file |
If you're already familiar with all of these comment types, you can skip to the Best Practices section. The timestamp is on screen right now.
JavaScript supports the following three types of comments:
1. Single-line Comments
You use //
to start a single-line comment. Everything after //
on that line is ignored by the JavaScript engine.
// This is a single-line comment
const x = 10; // This is an inline comment
When a comment appears on the same line as code, it's called an inline comment.
You can put these comments anywhere in your code.
Note to Cheta: First write the add
function without comments. Then add comments to the code.
// Outside of a function
function add(a, b) {
// Inside a function
if (a > b) { // On the same line as a condition
// Inside an if statement
return a;
}
return b;
}
Single-line comments can be nested. In other words, subsequent delimiters (//
) are ignored.
// // This is a single-line comment
This can for example happen if you use cmd + /
to comment out a block of code that already contains a single-line comment.
Tooling-Specific Comments
You control some tools by using tooling-specific comments. For example if you're using ESLint or Prettier, you can use single-line comments to disable rules for a specific line of code.
// eslint-disable-next-line no-console
console.log('Hello, world!');
Tooling specific comments only passively impact the readability and understandability of your code by tweaking the behavior of your tools.
But sometimes you want to use comments to clarify why you are modifying the behavior of your tools.
Take a look at this Timer
component that effectively acts as a simple stopwatch-like timer.
import { useEffect, useState } from 'react';
export function Timer({ interval }) {
const [time, setTime] = useState(0);
useEffect(() => {
const timerId = setInterval(() => {
setTime(previousTime => previousTime + interval);
}, interval);
return () => clearInterval(timerId);
// We intentionally omit `interval` from dependencies to prevent resetting
// the timer if `interval` changes. This is by design as changing `interval`
// mid-timer would break the timer's behavior.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div>Elapsed time: {time}ms</div>;
}
You use a comment to explain future developers why you are intentionally omitting interval
from the dependencies array. This can avoid accidental rule re-enforcement without understanding the intended behavior.
2. Multi-line Comments
Use /*
to start and */
to end a multi-line comment. Everything between /*
and */
is ignored by the JavaScript engine.
/*
This is a multi-line comment.
It spans multiple lines.
*/
const y = 20;
The space at the beginning of the comment is optional. Some developers prefer it, others don't.
/*
This is a multi-line comment without a space at the beginning.
It still spans multiple lines.
*/
const y = 20;
Multi-line comments can NOT be nested. So the following code will cause problems, which you can tell by the messed up syntax highlighting.
/*
Outer comment
/*
Inner comment
*/
*/
But, you can nest single-line comments with multi-line comments and vice versa.
/*
Outer comment
// Inner comment
*/
// /*
// Outer comment
// // Inner comment
// */
If you try using a multi-line comment to modify the behavior of your tools, it will not work:
/*
eslint-disable-next-line no-console
*/
console.log('This will still trigger an ESLint warning.');
Tools like ESLint are designed to parse specific single-line comments (//
) for directives. Multi-line comments (/* */
) are treated purely as descriptive text by these tools and are ignored.
For disabling a rule for multiple lines, you can use eslint-disable
with a block:
/* eslint-disable no-console */
console.log('No warning here.');
console.log('Still no warning.');
/* eslint-enable no-console */
Sometimes people prefer multiple //
for each line, instead of multiline comments.
// This is a multi-line comment by using multiple single-line comments.
// It also still spans multiple lines.
const y = 20;
I prefer this method because you can easily comment many lines with cmd + /
. However, use what works best for you and your team.
3. Documentation Comments (JSDoc & TSDoc)
There is a specific type of multi-line comment used for documenting code called JSDoc. JSDoc comments start with /**
and end with */
. Each line inside the comment block has a *
at the beginning. They include tags like @param
and @returns
to describe code elements.
Note to Cheta: Again, first write the add
function without comments. Then add comments to the code.
/**
* Adds two numbers.
*
* @param {number} a - The first number.
* @param {number} b - The second number.
* @returns {number} The sum of the two numbers.
*/
function add(a, b) {
return a + b;
}
As you can probably guess, @param
defines the type and name of a parameter. @returns
defines the type of the return value.
These documentation comments are recognized by your tooling. For example, IntelliSense in VSCode shows you your comments and the types of the parameters and return value when you hover over a function decorated with JSDoc. Everywhere where you use the function, you can simply hover over it to see your JSDoc comments and description. This is only possible with JSDoc and TSDoc.
JSDoc supports all primitive types as well as objects and arrays.
If a function takes in an object, you can define the type of each key in the object. For example, if a function takes in a user
object, you can the type of each key and write a short description of what the key is for.
/**
* Processes a user object.
* @param {Object} user - The user object to process.
* @param {number} user.id - The unique identifier for the user.
* @param {string} user.email - The email address of the user.
* @param {number} user.age - The age of the user. Must be a positive integer.
* @param {boolean} user.isActive - Indicates whether the user has verified
* their email.
* @returns {string} A message about the user's status.
*/
function processUser(user) {
return user.isActive
? `User ${user.email} is active.`
: `User ${user.email} is not active.`;
}
When refactors are too hard, use comments to clarify unclear variable names. If a code base is large enough, there is a high likelihood that you will encounter poorly named variables which can cause confusion.
The example above illustrates how you can use JSDoc to clarify what properties stand for if the name of the property doesn't make it obvious. isActive
could mean anything, but the comment makes it clear that it means whether the user has verified their email.
But JSDoc also allows you to define custom types. This is useful if you want to reuse a type in multiple places.
/**
* @typedef {Object} User
* @property {number} id - The unique identifier for the user.
* @property {string} email - The email address of the user.
* @property {number} age - The age of the user. Must be a positive integer.
* @property {boolean} isActive - Indicates whether the user has verified
* their email.
*/
You can now use this type, for example in the definition of a function.
/**
* Processes a user object.
* @param {User} user - The user object to process.
* @returns {string} A message about the user's status.
*/
function processUser(user) {
return user.isActive
? `User ${user.email} is active.`
: `User ${user.email} is not active.`;
}
Again, IntelliSense works and will also show you the types of the variables inside of the function when you hover over the variables.
TSDoc
TSDoc is a documentation standard for TypeScript. It uses the JSDoc syntax. It allows TypeScript-specific types in tags and helps with rich IntelliSense in TypeScript.
When you use TSDoc, you can usually leave out the type definitions in the comments because you add those via the native TypeScript types.
/**
* Adds two numbers.
*
* @param a - The first number.
* @param b - The second number.
* @returns The sum of the two numbers.
*/
function add(a: number, b: number) {
return a + b;
}
Whether you write JSDoc or TSDoc only depends on the file ending. If it ends in .js
or .jsx
, you use JSDoc. If it ends in .ts
or .tsx
, you use TSDoc.
TSDoc also allows you to describe keys of custom types.
/**
* Represents a user in the system.
*/
type User = {
/**
* The unique identifier for the user.
*/
id: string;
/**
* The user's email address.
*/
email: string;
/**
* The user's age.
* Must be a positive integer.
*/
age: number;
/**
* Indicates whether the user has verified their email.
*/
isActive: boolean;
};
When you now create an object with the User
type, it will show you the comments when hovering over the object's properties. This technique works for both type
and interface
.
Similar to multi-line comments, you can neither use JSDoc nor TSDoc to control directives for your formatting tools.
Quick Documentation Comment API Tips
Keep Complex Descriptions in the Summary
Complex descriptions for code in your documentation comments should go into the first part, instead of the @return
value.
/**
* Gets the relevant date key (createdAt or updatedAt) based on the intent type.
* For create intents, returns createdAt. For all other intents, returns
* updatedAt.
*
* @param intent - The intent type to get the relevant date key for.
* @returns The date key to use for sorting cards with this intent.
* @throws Error if the intent is not a valid card-modifying intent.
*/
const getRelevantDateKeyByIntent = (
intent: IntentModifyingCardsOnTheBoard,
): keyof Pick<PartialContentCard, 'createdAt' | 'updatedAt'> => {
if (!INTENTS_MODIFYING_CARDS_ON_THE_BOARD.includes(intent)) {
throw new Error(`Unhandled intent: ${intent}`);
}
if (intent === BOARD_INTENTS.createContentCard) {
return 'createdAt';
}
return 'updatedAt';
};
Other Useful TSDoc Tags
You can check out the tags reference of the TSDoc documentation to learn about all the available tags.
But I wanted to highlight 4 more tags that I use frequently for you.
@deprecated
: Indicates the function is outdated and provides a suggestion.@throws
: Documents the type of error thrown and under what conditions.@example
: Gives example usage of the function. It can include correct and incorrect cases.@see
: Links to a related resource or an improved version of the function. It can be combined with@link
to link to a specific variable or function. It is not intended for URLs, although it can technically include them.
Note to Cheta: After you've written the code below, hover over the add
function. Show the deprecation message. Then click the addNumbers
function to show that the link takes the user to that function. (You might want to define the addNumbers
function in a seperate file to make it even clearer.)
/**
* Adds two numbers.
*
* @deprecated Use the `addNumbers` function instead for better performance.
* @param {number} a - The first number.
* @param {number} b - The second number.
* @returns {number} The sum of the two numbers.
*
* @throws {TypeError} Throws an error if either argument is not a number.
*
* @example
* // Correct usage:
* const result = add(5, 3);
* console.log(result); // 8
*
* // Incorrect usage:
* add("5", 3); // Throws TypeError
*
* @see {@link addNumbers} for the improved version.
*/
function add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Both arguments must be numbers.');
}
return a + b;
}
add(1, 2);
function addNumbers(a, b) {
return a + b;
}
addNumbers(1, 2);
Functions that are marked as deprecated will be crossed out in your IDE. And there will be a tooltip that shows you the deprecation message.
Additionally, you can click on the addNumbers
in the popup and your IDE will take you to that function.
TypeScript Specific Comments
There are also two types of TypeScript specific comments.
Triple-Slash Directives
Triple-slash directives are special single-line comments that provide compiler directives or references. They must appear at the top of the file.
/// <reference path="path/to/file.ts" />
/// <reference types="node" />
/// <reference path="..." />
: Includes an additional file in the compilation context./// <reference types="..." />
: Includes type definitions from@types
.
You use triple-slash directives to manage dependencies and type references in TypeScript projects. And they can ensure that certain files are included before others during compilation.
Compiler Directive Comments
Compiler directive comments are special single-line comments that instruct the TypeScript compiler to alter its behavior for specific lines of code.
The most popular is @ts-ignore
that ignores TypeScript errors on the next line.
// @ts-ignore
const z: number = "This is a string"; // Normally causes a type error
@ts-ignore
suppresses any TypeScript errors on the next line, even if the code works and there is no error.
Therefore, it's usually better to use @ts-expect-error
instead of @ts-ignore
. @ts-expect-error
expects a TypeScript error on the next line. If no error occurs, the compiler will emit an error.
// @ts-expect-error
const a: number = "Another string"; // Should cause a type error
Best Practices
Now I want to share with you some best practices for writing comments that I picked up troughout my career together with some examples.
I considered using real production code or creating simple examples. Production code often has extra details, while simple examples are more focused. To give you a feel for the real world, I chose to use actual code. I will make it concise by removing unnecessary parts with inline comments.
Some Code Is Self-Explanatory
First of all, some code is self-explanatory and doesn't need comments. If a function is so short and simple that every developer can understand what it does, you don't need to add comments to it.
The add
function that you saw earlier is a good example for a trivial function.
function add(a, b) {
return a + b;
}
Another good example are simple factory functions, such as action creators in Redux without Redux Toolkit.
export const addTodo = ({ text }) => ({
type: 'ADD_TODO',
payload: { text },
});
If your comment is a one-liner or a short description of a function or variable and differs from its name, it might be a code smell suggesting it is misnamed.
The following code was from a real code base. It contained a NotificationCenter
model that contained the settings and notifications for the notification panel, which pops up if you click the bell in the top right corner.
/// Contains the notifications and settings for the notification panel.
model NotificationCenter {
// some properties
}
The comment on the model clarified what it was for. But the comment was unneccesary because you can make clear what the model is for by giving it a more descriptive name.
model NotificationPanelMetaData {
// some properties
}
Redundancy Is Okay
However, on the other hand, redundancy is okay, too. Sometimes you might think that a function is obvious, but other team members or your future self might disagree.
For example, in many teams that I lead, we always add TSDoc to our database facades by convention.
import type { UserProfile } from '@prisma/client';
import { prisma } from '~/database.server';
/**
* Retrieves the first user profile record from the database based on the
* user's name.
*
* @param name - The name of the user profile to retrieve.
* @returns The first user profile with a given name or null if no such user
* was found.
*/
export async function retrieveUserProfileFromDatabaseByName(
name: UserProfile['name'],
) {
return prisma.userProfile.findFirst({ where: { name } });
}
Our reasoning is that it enforces a good habit since facades can sometimes get complex, especially when they include custom SQL queries. Additionally, new team members might be unfamiliar with Prisma, so comments help clarify the code.
As a rule of thumb for beginners: if you're unsure whether a comment is needed, just add it.
For example, you might know that 8 * 1024 * 1024
is the number of bytes in 8MB. But someone else who didn't study computer science might NOT.
const MAX_FILE_SIZE = 8 * 1024 * 1024; // 8MB
Besides, sometimes comments are just there to reduce the reader's cognitive load, even if technically everyone could figure it out.
Clarity Beats Brevity
Use full sentences. And avoid abbreviations. Using complete words results in more readable code. When you write code, no one wants to guess whether res
means response
or result
or residue
, and whether e
means error
or event
or element
etc. The same is true for your comments.
Keep Comments Up-to-Date
When you modify and refactor your code, always ensure your comments are updated as well. Pay special attention to this during code reviews. If the comments don't align with the actual behavior of the code, it can cause confusion about the source of truth. Did the developer forget to update the comments? Or did they make a mistake in the implementation?
I'll show you how you can use LLMs to update comments after you've refactored your code later in this video.
Directives Are NOT Fixes
Use TypeScript compiler directives consciously. Overuse can mask genuine type issues. As mentioned above, favor @ts-expect-error
over @ts-ignore
.
Generally using directives is a code smell for code that is insufficiently typed. Of course, there are exceptions where you expect a type error. In those cases, you can always add a reason behind @ts-expect-error
to clarify why you are expecting an error.
For example, if you have a function where you want to test an invalid input type, you can use @ts-expect-error
to ignore the error.
import { describe, expect, test } from 'vitest';
function isState(value: string): value is 'active' | 'inactive' {
return value === 'active' || value === 'inactive';
}
describe('isState()', () => {
// ... other test cases
test('given invalid input type: handles type error', () => {
// @ts-expect-error - Testing with a number to ensure invalid input is handled
const actual = isState(123);
const expected = false;
expect(actual).toEqual(expected);
});
});
Provide Context with Links
Use the @see
tag to link to external resources where appropriate. For example, link to documentations, articles, GitHub issues, etc. that give context to your code. Sometimes typing everything a developer needs to understand the code is too much for a comment. Or a develper can get the gist of your code from your comments, but for a full understanding they need to read the documentation.
I recently helped to build a KanbanBoard
component for a project. That board implements drag and drop via the DataTransfer
API. Here is a trimmed down version of the code for the KanbanBoardProvider
component.
export const KanbanBoardProvider = ({
announcements,
screenReaderInstructions,
container,
children,
}: KanbanBoardProviderProps) => {
const draggableDescribedById = useId();
const monitorsRef = useRef<DndMonitorEventHandler[]>([]);
// Store the activeId in a ref to avoid re-rendering when it changes.
// This is useful for announcing the drag start and end when you lack access
// to the active ID, e.g. because you're using `onDragOver` from the
// `DataTransfer` API. When trying to access data during the dragover event
// using getData(), it will always return an empty string. This is a security
// restriction in the HTML5 Drag and Drop API - you cannot access the data
// during the dragover event, only during the drop event.
// @see https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
const activeIdRef = useRef<string>('');
// ... more code
const contextValue = useMemo(
() => ({
activeIdRef,
draggableDescribedById,
registerMonitor,
unregisterMonitor,
triggerEvent,
}),
[
activeIdRef,
draggableDescribedById,
registerMonitor,
unregisterMonitor,
triggerEvent,
],
);
return (
// ... more code
);
};
When you create an accessible kanban board, screen readers must announce certain details, such as the item being dragged. The native HTML API does NOT provide this information during a drag. Therefore, you must manually track the dragged item. In this example, you do it in the activeIdRef
. The comment above this code explains why you can't access the item's ID during the drag and why it's stored in a ref. It also includes a link to the MDN documentation for the DataTransfer
API for further reading.
Split Comments When Needed
Sometimes, it's fine to have a comment and its sibling in different places.
For example, you might have comments for a form and a loader in Remix, or a helper function and a saga in Redux, etc. These comments don't need to be adjacent. They can reference each other's variables to help locating related code.
Note to Cheta: Show two example files here. If you need help creating the example, ping me and we create one together.
Note to editors: Show the two files side by side.
When To Use Which
The most important metric to decide when to use which comment type is:
“When in Rome, do as the Romans do.” - Julius Caesar
In other words, if you're working in a team, you should follow the conventions of your team. If your team lacks a convention, bring it up with your team and start a discussion. Capture your team's preferences in your project's README.md
.
That being said, in our teams we usually adhere to the following conventions:
- Add
JSDoc
orTSDoc
to all complex functions, types, objects, components, and hooks above their definitions. - Add single-line comments inside of functions to explain the immediate context.
- Add multi-line comments as delimiters between different sections of code.
- Add tool specific comments at the top of the file or at their corresponding lines / codeblocks.
- Add larger blobs of context at the top of a file - for example in a script file - if the file has an overarching purpose that is worth explaining.
- In complex sequences, add numbers to your comments in the corresponding scope to clarify the order of operations.
- If necessary, add feature specific READMEs in feature folders to document the bigger picture architecture of a feature.
Here is some more of the kanban board code to show you how you can use this convention in practice.
/* eslint-disable jsx-a11y/heading-has-content */
import type {
ChangeEvent,
ComponentProps,
KeyboardEvent,
MutableRefObject,
} from 'react';
// ... more imports
import { cn } from '~/utils/shadcn-ui';
/*
Accessibility
*/
type DndMonitorEventHandler = {
onDragStart?: (activeId: string) => void;
onDragMove?: (activeId: string, overId?: string) => void;
onDragOver?: (activeId: string, overId?: string) => void;
onDragEnd?: (activeId: string, overId?: string) => void;
onDragCancel?: (activeId: string) => void;
};
// ...more types and hooks and components
/*
Constants
*/
/**
* Event data transfer types
* @see https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
*/
const DATA_TRANSFER_TYPES = {
CARD: 'kanban-board-card',
};
// ... more constants
/*
Board
*/
export const KanbanBoard = forwardRef<HTMLDivElement, ComponentProps<'div'>>(
({ className, ...props }, ref) => {
return (
<div
className={cn(
'flex h-full flex-grow items-start gap-x-2 overflow-x-auto py-1',
className,
)}
ref={ref}
{...props}
/>
);
},
);
KanbanBoard.displayName = 'KanbanBoard';
/**
* Add some extra margin to the right of the container to allow for scrolling
* when adding a new column.
*/
export const KanbanBoardExtraMargin = forwardRef<
HTMLDivElement,
ComponentProps<'div'>
>(({ className, ...props }, ref) => {
return (
<div
className={cn('h-1 w-8 flex-shrink-0', className)}
ref={ref}
{...props}
/>
);
});
KanbanBoardExtraMargin.displayName = 'KanbanBoardExtraMargin';
/*
Column
*/
type KanbanBoardColumnProps = {
columnId: string;
onDropOverColumn?: (dataTransferData: string) => void;
};
// ... more types and components
At the top of the file, you can disable all tooling that might interfere with your code.
Next we use multi-line comments to separate the code into different sections, e.g. Accessibility
, Constants
, Board
, Column
, etc. Why multiline comments? Because they are easy to find when you rapidly scroll through your code. Some people also like to add a bunch of markers like =========
or ---
to further visually separate the sections.
// ==============================
// Constants
// ==============================
Again, use the same style as your team uses.
Lastly, if a components need some extra context, we add it as TSDoc. The KanbanBoardExtraMargin
might seem unnecesssary, so we clarify when you should use it in a comment.
Write Code Like A Tutorial
My favorite guiding principle to keep code readable and easy to understand, and therefore also for comments, is to:
“Write code like a tutorial.” - Jan Hesters
Save your reader's brain capacity with your comments. Guide them through your code and answer the relevant questions:
- What? - If the code is complex and hard to follow, or if it's unintuitive to read (e.g. Regex), clearly state the functionality or outcome of the code. You might also want to provide examples or edge cases.
- How? - Show the steps, order and logic of your approach or algorithm. Make it easier for your readers to understand the implementation details.
- Why? - Sometimes code is easy to understand, but it's unclear why or in what context you need it. Explain the trade-offs and reasons behind the code. You might also want to offer insights into alternative methods and why they were NOT selected.
You don't always need to answer all of these questions. Focus only on the ones that matter for clarity.
As an example, check out this DragAndDrop
component for file uploads.
import { ImageIcon } from 'lucide-react';
import { forwardRef, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { cn } from '~/utils/shadcn-ui';
export type DragAndDropProps = JSX.IntrinsicElements['input'] & {
dragAndDropLabel?: string;
fileTypesLabel?: string;
/** Fires with the chosen file as soon as the user choses a file. */
onFileChosen?: (file: File) => void;
/** Fires with the files cleaned name as soon as a user choses a file. */
onFilenameChosen?: (filename: string) => void;
uploadLabel?: string;
};
export const DragAndDrop = forwardRef<HTMLInputElement, DragAndDropProps>(
function DragAndDrop(
{
className,
dragAndDropLabel,
fileTypesLabel,
id,
name,
onFileChosen,
onFilenameChosen,
uploadLabel,
...props
},
forwardedRef,
) {
const { t } = useTranslation('drag-and-drop');
// Show the name of the file you want to upload in the user interface.
const [filename, setFilename] = useState<string | undefined>();
const fileInputRef = useRef<HTMLInputElement>(null);
// Update the forwarded ref when the new ref changes.
useEffect(() => {
if (forwardedRef) {
if (typeof forwardedRef === 'function') {
forwardedRef(fileInputRef.current);
} else {
forwardedRef.current = fileInputRef.current;
}
}
}, [forwardedRef]);
// Drag and drop handling.
const [dragIsActive, setDragIsActive] = useState(false);
return (
<div
className={cn(
'mt-2 flex justify-center rounded-lg border border-dashed border-border px-6 py-10',
className,
dragIsActive && 'relative',
)}
onDragEnter={event => {
// 1. Show the overlay.
event.preventDefault();
event.stopPropagation();
setDragIsActive(true);
}}
onDrop={event => {
// 4. Prevent file from opening in the browser when dropped and
// close the overlay.
event.preventDefault();
event.stopPropagation();
setDragIsActive(false);
// 5. Set the file as the input value and show its name in the
// user interface.
if (fileInputRef.current) {
fileInputRef.current.files = event.dataTransfer.files;
onFileChosen?.(event.dataTransfer.files[0]);
const newFilename = event.dataTransfer.files[0].name;
setFilename(newFilename);
onFilenameChosen?.(newFilename);
}
}}
>
<div className="flex flex-col items-center text-center">
{/* ... more code ... */}
</div>
{dragIsActive && (
<div
className="absolute top-0 h-full w-full bg-background opacity-50"
onDragOver={event => {
// 2. Prevent file from opening in the browser when dropped.
event.preventDefault();
event.stopPropagation();
}}
onDragLeave={event => {
// 3. Remove overlay when dragging outside the border.
event.preventDefault();
event.stopPropagation();
setDragIsActive(false);
}}
/>
)}
</div>
);
},
);
In it, you use TSDoc on the type of the DragAndDropComponentProps
to explain what the two on
handlers do. This makes them easy to use because when another developer sees the DragAndDrop
component with those props and hovers over the them, the comments will show up and explain what the they do.
You can clarify what the hooks track and do with comments inside of the component.
And lastly, you can guide the reader with numbered comments in the JSX by giving context on the order in which the events fire, how they work and why they are needed.
Use LLMs To Generate Comments
You can use LLMs to help you generate comments. You typically want to include existing code with comments in your prompt, so that the LLM can copy your style. You can use the same technique to update comments.
Let's say you have a function that you refactored. You can include the previous function with its comments, as well as the new function and ask the LLM to update the comments.
Note to Cheta: Use our company ChatGPT account to record the following in the browser.
I have rewritten my function to count the number of user profiles in the database. Previously it was this:
```ts
import type { UserProfile } from '@prisma/client';
import { prisma } from '~/database.server';
/**
* Retrieves the first user profile record from the database based on the
* user's name.
*
* @param name - The name of the user profile to retrieve.
* @returns The first user profile with a given name or null if no such user
* was found.
*/
export async function retrieveUserProfileFromDatabaseByName(
name: UserProfile['name'],
) {
return prisma.userProfile.findFirst({ where: { name } });
}
```
Now it looks like this:
```ts
export async function countUserProfilesInDatabaseByName(
name: UserProfile['name'],
) {
return prisma.userProfile.count({ where: { name } });
}
```
Please give me the updated TSDoc, so I can copy it.
It will answer something like this:
Here's the updated TSDoc comment for your revised function:
```ts
/**
* Counts the number of user profiles in the database with a given name.
*
* @param name - The name of the user profiles to count.
* @returns The count of user profiles with the specified name.
*/
```
You can copy this comment and place it above your `countUserProfilesInDatabaseByName` function.
When you use LLMs for comments, always double-check the generated comments for accuracy. Sometimes, you need to guide the LLM in your prompt to include or highlight specific behaviors of your code.
Read Code To Learn What Readable Code Is
You will have to read a lot of code to understand what readable code looks like. Whenever you join a new codebase and feel frustrated because the code is difficult to follow, or when you revisit code you wrote months or years ago and curse your former self for its poor readability, take those moments as learning opportunities. Think about how you would have preferred the code to be written.
Leave Code Cleaner Than You Found It
In such situations, pay special attention to code that lacks comments and is difficult or frustrating to understand. As you figure out what it does, and as your coworkers answer your questions, start adding comments to make it easier for future teammates to understand what the code does and where it is used.
You Still Need Real Documentation
Even if you have excellent inline documentation, you still need guide documentation. Comments, JSDoc, TSDoc, and TypeScript are additions to your documentation toolkit rather than replacements. Make sure to have a README.md
file that makes it easy to onboard to your project. And consider adding more README.md
files in the directories that need them.
You Still Need To Write Clean Code
IntelliSense is only in your IDE. But you'll often read code in places where you might lack IntelliSense.
Additionally, IntelliSense interrupts your flow. It requires you to hover over a function and wait for a popup before you can read the hints.
While it’s impossible for sufficiently complex codebases to be entirely understandable without context through comments or documentation, you should still strive to make your code as modular as possible.
“If you can understand your code without context, your code is modular.” - Jan Hesters
Furthermore, code that follows established patterns is easy to pick up. On one hand, "established" means patterns already in use by your team in the codebase. On the other hand, it refers to patterns that are well-documented and widely used in the broader community. For example, packages like React Query, Tailwind CSS, or Redux, and abstract data types such as tries, functors, monads, and transducers
One of the main techniques in JavaScript to make your code more modular is to use pure functions and functional programming. I teach you how to do that in "Unleash JavaScript's Potential with Functional Programming", so go watch that next.
I want leave you with a quote from Eric Elliott:
“Junior developers think they have to write a lot of code to produce a lot of value.
Senior developers understand the value of the code that nobody needed to write.” - Eric Elliott
I love you very much. Thank you so much for watching. And subscribe to this channel for more advanced JavaScript tutorials. Peace.
Split stuff.
The add
function that you saw in the first video is a good emaple for a travial function. If you're already familiar with the different types of comments in JavaScript, don't worry about the first video. This video is the only one you want to watch.