| name | frontend-code-style |
| description | Use when writing or reviewing JavaScript/TypeScript code for style patterns like concise arrows, inline handlers, expression formatting, or when tempted to use eslint-disable |
Frontend: Code Style
Preferred code style patterns for consistent, readable JavaScript/TypeScript code.
Note: These are conventions, not enforced rules. Existing code may not follow these patterns, which is acceptable. When writing new code, prefer these patterns where practical.
Concise Arrow Functions (Preferred)
Preference: When a function body is a single expression, prefer concise arrow syntax without {} braces.
const handleClick = () => {
doSomething();
};
const getFullName = (user) => {
return `${user.firstName} ${user.lastName}`;
};
const handleClick = () => doSomething();
const getFullName = (user) => `${user.firstName} ${user.lastName}`;
When braces ARE needed:
const handleSubmit = () => {
validate();
submit();
};
const processAndReturn = (data) => {
console.log(data);
return transform(data);
};
Inline Single-Expression Handlers (Preferred)
Preference: For single-expression handlers, prefer inlining directly in the event prop rather than declaring a separate named function.
const handleOpenModal = () => setIsOpen(true);
const handleCloseModal = () => setIsOpen(false);
return (
<>
<Button onClick={handleOpenModal}>Open</Button>
<Modal onClose={handleCloseModal} />
</>
);
return (
<>
<Button onClick={() => setIsOpen(true)}>Open</Button>
<Modal onClose={() => setIsOpen(false)} />
</>
);
When to extract a named handler:
-
Multiple expressions - Handler does more than one thing
const handleSubmit = () => {
validateForm();
submitData();
};
-
Reused in multiple places - Same handler used by multiple elements
const handleClose = () => {
resetForm();
setIsOpen(false);
};
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleClose}>X</Button>
-
Complex logic - Benefits from a descriptive name for readability
const handleToggleWithAnalytics = () => {
trackEvent("feature_toggled");
setIsEnabled((prev) => !prev);
};
-
Memoization needed - Passed to memoized child component
const handleChange = useCallback((value) => {
setData(value);
}, []);
<MemoizedInput onChange={handleChange} />;
FORBIDDEN: eslint-disable Comments
NEVER use eslint-disable comments to suppress hook dependency warnings. These warnings exist for good reason - suppressing them hides bugs.
useEffect(() => {
pContext.setState({ data: newData });
}, [newData]);
Root Cause: Unstable References
The warning usually means a dependency changes reference on every render. Common causes:
- Context object recreated -
value={{ state, setState }} creates new object each render
- Array/object created inline -
options={[1, 2, 3]} is a new array each render
- Callback not memoized - Functions created in render without useCallback
Solution: Compare Before Update
Instead of suppressing the warning, add a comparison to break the update cycle:
useEffect(() => {
const prevIds = pContext.state.users
.map((u) => u.id)
.sort()
.join(",");
const nextIds = newUsers
.map((u) => u.id)
.sort()
.join(",");
if (prevIds !== nextIds) {
pContext.setState({ users: newUsers });
}
}, [newUsers, pContext]);
Solution: Functional Updates
For state that depends on previous value:
setOnlineUsers((prev) => {
if (prev.length !== next.length) return next;
const prevIds = prev
.map((u) => u.id)
.sort()
.join(",");
const nextIds = next
.map((u) => u.id)
.sort()
.join(",");
return prevIds === nextIds ? prev : next;
});
Exception Process
If eslint-disable is truly unavoidable:
- Stop and ask the user - Explain why you believe it's necessary
- Get explicit approval - User must confirm it's acceptable
- Document the reason - Add a comment explaining WHY (not just disabling)
useEffect(() => { ... }, [stableValue]);
Related Skills
- frontend-naming-conventions - Naming patterns for components, hooks, and files