11 KiB
Code Cleanup Guide
This guide documents the cleanup patterns and best practices applied to the codebase, starting with GroceryList.jsx. Use this as a reference for maintaining consistent, clean, and readable code across all files.
Table of Contents
- Spacing & Organization
- Comment Formatting
- Code Simplification
- React Performance Patterns
- Cleanup Checklist
Spacing & Organization
Two-Line Separation
Use 2 blank lines to separate logical groups and functions.
Before:
const handleAdd = async (itemName, quantity) => {
// function body
};
const handleBought = async (id) => {
// function body
};
After:
const handleAdd = async (itemName, quantity) => {
// function body
};
const handleBought = async (id) => {
// function body
};
Logical Grouping
Organize code into clear sections:
- State declarations
- Data loading functions
- Computed values (useMemo)
- Event handlers grouped by functionality
- Helper functions
- Render logic
Comment Formatting
Section Headers
Use the === Section Name === format for major sections, followed by 2 blank lines before the next code block.
Pattern:
// === State ===
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
// === Data Loading ===
const loadItems = async () => {
// implementation
};
// === Event Handlers ===
const handleClick = () => {
// implementation
};
Common Section Names
=== State ====== Data Loading ====== Computed Values ===or=== Sorted Items Computation ====== Event Handlers ===or specific groups like=== Item Addition Handlers ====== Helper Functions ====== Render ===
Code Simplification
1. Optional Chaining
Replace && null checks with optional chaining when accessing nested properties.
Before:
if (existingItem && existingItem.bought === false) {
// do something
}
After:
if (existingItem?.bought === false) {
// do something
}
When to use:
- Accessing properties on potentially undefined/null objects
- Checking nested properties:
user?.profile?.name - Method calls:
item?.toString?.()
When NOT to use:
- When you need to check if the object exists first (use explicit check)
- For boolean coercion:
if (item)is clearer thanif (item?.)
2. Ternary Operators
Use ternary operators for simple conditional assignments and returns.
Before:
let result;
if (condition) {
result = "yes";
} else {
result = "no";
}
After:
const result = condition ? "yes" : "no";
When to use:
- Simple conditional assignments
- Inline JSX conditionals
- Return statements with simple conditions
When NOT to use:
- Complex multi-line logic (use if/else for readability)
- Nested ternaries (hard to read)
3. Early Returns
Use early returns to reduce nesting.
Before:
const handleSuggest = async (text) => {
if (text.trim()) {
// long implementation
} else {
setSuggestions([]);
setButtonText("Add Item");
}
};
After:
const handleSuggest = async (text) => {
if (!text.trim()) {
setSuggestions([]);
setButtonText("Add Item");
return;
}
// main implementation without nesting
};
4. Destructuring
Use destructuring for cleaner variable access.
Before:
const username = user.username;
const email = user.email;
const role = user.role;
After:
const { username, email, role } = user;
5. Array Methods Over Loops
Prefer array methods (.map(), .filter(), .find()) over traditional loops.
Before:
const activeItems = [];
for (let i = 0; i < items.length; i++) {
if (!items[i].bought) {
activeItems.push(items[i]);
}
}
After:
const activeItems = items.filter(item => !item.bought);
React Performance Patterns
1. useCallback for Event Handlers
Wrap event handlers in useCallback to prevent unnecessary re-renders of child components.
const handleBought = useCallback(async (id, quantity) => {
await markBought(id);
setItems(prevItems => prevItems.filter(item => item.id !== id));
loadRecentlyBought();
}, []); // Add dependencies if needed
When to use:
- Handler functions passed as props to memoized child components
- Functions used as dependencies in other hooks
- Functions in frequently re-rendering components
2. useMemo for Expensive Computations
Use useMemo for computationally expensive operations or large transformations.
const sortedItems = useMemo(() => {
const sorted = [...items];
if (sortMode === "az") {
sorted.sort((a, b) => a.item_name.localeCompare(b.item_name));
}
return sorted;
}, [items, sortMode]);
When to use:
- Sorting/filtering large arrays
- Complex calculations
- Derived state that's expensive to compute
3. React.memo for Components
Wrap components with React.memo and provide custom comparison functions to prevent unnecessary re-renders.
const GroceryListItem = React.memo(
({ item, onClick, onLongPress }) => {
// component implementation
},
(prevProps, nextProps) => {
return (
prevProps.id === nextProps.id &&
prevProps.item_name === nextProps.item_name &&
prevProps.quantity === nextProps.quantity &&
prevProps.item_image === nextProps.item_image
);
}
);
When to use:
- List item components that render frequently
- Components with stable props
- Pure components (output depends only on props)
4. In-Place State Updates
Update specific items in state instead of reloading entire datasets.
Before:
const handleUpdate = async (id, newData) => {
await updateItem(id, newData);
loadItems(); // Reloads entire list from server
};
After:
const handleUpdate = useCallback(async (id, newData) => {
const response = await updateItem(id, newData);
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, ...response.data } : item
)
);
}, []);
Benefits:
- Faster updates (no server round-trip for entire list)
- Preserves scroll position
- Better user experience (no full re-render)
Cleanup Checklist
Use this checklist when cleaning up a file:
Structure & Organization
- Group related state variables together
- Use 2-line spacing between logical sections
- Add section comments using
=== Format === - Order sections logically (state → data loading → computed → handlers → helpers → render)
Code Simplification
- Replace
&&null checks with optional chaining where appropriate - Convert simple if/else to ternary operators
- Use early returns to reduce nesting
- Apply destructuring for cleaner variable access
- Use array methods instead of loops
React Performance
- Wrap stable event handlers in
useCallback - Use
useMemofor expensive computations - Consider
React.memofor list items or frequently re-rendering components - Update state in-place instead of reloading from server
Consistency
- Check naming conventions (camelCase for functions/variables)
- Ensure consistent spacing and indentation
- Remove unused imports and variables
- Remove console.logs (except intentional debugging aids)
Testing After Cleanup
- Verify no functionality broke
- Check that performance improved (using React DevTools Profiler)
- Test all interactive features
- Verify mobile/responsive behavior still works
Example: Before & After
Before Cleanup
import { useState, useEffect } from "react";
export default function MyComponent() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const loadItems = async () => {
setLoading(true);
const res = await getItems();
setItems(res.data);
setLoading(false);
};
useEffect(() => {
loadItems();
}, []);
const handleUpdate = async (id, data) => {
await updateItem(id, data);
loadItems();
};
const handleDelete = async (id) => {
await deleteItem(id);
loadItems();
};
if (loading) return <p>Loading...</p>;
return (
<div>
{items.map(item => (
<Item key={item.id} item={item} onUpdate={handleUpdate} onDelete={handleDelete} />
))}
</div>
);
}
After Cleanup
import { useCallback, useEffect, useMemo, useState } from "react";
export default function MyComponent() {
// === State ===
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
// === Data Loading ===
const loadItems = async () => {
setLoading(true);
const res = await getItems();
setItems(res.data);
setLoading(false);
};
useEffect(() => {
loadItems();
}, []);
// === Event Handlers ===
const handleUpdate = useCallback(async (id, data) => {
const response = await updateItem(id, data);
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, ...response.data } : item
)
);
}, []);
const handleDelete = useCallback(async (id) => {
await deleteItem(id);
setItems(prevItems => prevItems.filter(item => item.id !== id));
}, []);
// === Render ===
if (loading) return <p>Loading...</p>;
return (
<div>
{items.map(item => (
<Item
key={item.id}
item={item}
onUpdate={handleUpdate}
onDelete={handleDelete}
/>
))}
</div>
);
}
Key improvements:
- Added section comments for clarity
- Proper 2-line spacing between sections
- Wrapped handlers in
useCallbackfor performance - In-place state updates instead of reloading entire list
- Better import organization
Additional Resources
Notes
- Don't over-optimize: Not every component needs
useCallback/useMemo. Apply these patterns when you have measurable performance issues or when working with large lists. - Readability first: If a simplification makes code harder to understand, skip it. Code should be optimized for human reading first.
- Test thoroughly: Always test after cleanup to ensure no functionality broke.
- Incremental cleanup: Don't try to clean up everything at once. Focus on one file at a time.
Last Updated: Based on GroceryList.jsx cleanup (January 2026)