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.
// Acceptable (existing pattern)
const handleClick = () => {
doSomething();
};
const getFullName = (user) => {
return `${user.firstName} ${user.lastName}`;
};
// Preferred for NEW code: Concise form for single expressions
const handleClick = () => doSomething();
const getFullName = (user) => `${user.firstName} ${user.lastName}`;
When braces ARE needed:
// Multiple statements require braces
const handleSubmit = () => {
validate();
submit();
};
// Side effect followed by return
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.
// Acceptable (existing pattern)
const handleOpenModal = () => setIsOpen(true);
const handleCloseModal = () => setIsOpen(false);
return (
<>
<Button onClick={handleOpenModal}>Open</Button>
<Modal onClose={handleCloseModal} />
</>
);
// Preferred for NEW code: Inline directly in the event prop
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
tsxconst handleSubmit = () => { validateForm(); submitData(); }; - •
Reused in multiple places - Same handler used by multiple elements
tsxconst handleClose = () => { resetForm(); setIsOpen(false); }; <Button onClick={handleClose}>Cancel</Button> <Button onClick={handleClose}>X</Button> - •
Complex logic - Benefits from a descriptive name for readability
tsxconst handleToggleWithAnalytics = () => { trackEvent("feature_toggled"); setIsEnabled((prev) => !prev); }; - •
Memoization needed - Passed to memoized child component
tsxconst 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.
// ❌ FORBIDDEN - Suppressing eslint is a code smell
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
pContext.setState({ data: newData });
}, [newData]);
// ❌ Also forbidden
/* eslint-disable react-hooks/exhaustive-deps */
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:
// ✅ CORRECT - Keep all dependencies, compare before updating
useEffect(() => {
// Compare to check if update is actually needed
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]); // All deps included, eslint happy
Solution: Functional Updates
For state that depends on previous value:
// ✅ CORRECT - Use functional update to avoid needing state in deps
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)
// ⚠️ EXCEPTION - User approved on 2026-01-17
// Reason: Third-party library returns unstable ref, no workaround available
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => { ... }, [stableValue]);
Related Skills
- •frontend-naming-conventions - Naming patterns for components, hooks, and files