From 0c16d22c1edc0eb09f32b4e5cd9e5ddacf5481d5 Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 22 Jan 2026 01:06:48 -0800 Subject: [PATCH] cleaned up grocerylist page, created reference document for cleaning up files, and consolidated all readme docs within docs folder --- .../api-documentation.md | 0 .../classification-implementation.md | 0 docs/code-cleanup-guide.md | 473 ++++++++++++++++++ .../component-structure.md | 0 frontend/README.md => docs/frontend-readme.md | 0 .../image-storage-implementation.md | 0 SETUP_CHECKLIST.md => docs/setup-checklist.md | 0 frontend/src/pages/GroceryList.jsx | 64 ++- 8 files changed, 521 insertions(+), 16 deletions(-) rename API_DOCUMENTATION.md => docs/api-documentation.md (100%) rename CLASSIFICATION_IMPLEMENTATION.md => docs/classification-implementation.md (100%) create mode 100644 docs/code-cleanup-guide.md rename frontend/COMPONENT_STRUCTURE.md => docs/component-structure.md (100%) rename frontend/README.md => docs/frontend-readme.md (100%) rename IMAGE_STORAGE_IMPLEMENTATION.md => docs/image-storage-implementation.md (100%) rename SETUP_CHECKLIST.md => docs/setup-checklist.md (100%) diff --git a/API_DOCUMENTATION.md b/docs/api-documentation.md similarity index 100% rename from API_DOCUMENTATION.md rename to docs/api-documentation.md diff --git a/CLASSIFICATION_IMPLEMENTATION.md b/docs/classification-implementation.md similarity index 100% rename from CLASSIFICATION_IMPLEMENTATION.md rename to docs/classification-implementation.md diff --git a/docs/code-cleanup-guide.md b/docs/code-cleanup-guide.md new file mode 100644 index 0000000..e480516 --- /dev/null +++ b/docs/code-cleanup-guide.md @@ -0,0 +1,473 @@ +# 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 +1. [Spacing & Organization](#spacing--organization) +2. [Comment Formatting](#comment-formatting) +3. [Code Simplification](#code-simplification) +4. [React Performance Patterns](#react-performance-patterns) +5. [Cleanup Checklist](#cleanup-checklist) + +--- + +## Spacing & Organization + +### Two-Line Separation +Use **2 blank lines** to separate logical groups and functions. + +**Before:** +```javascript +const handleAdd = async (itemName, quantity) => { + // function body +}; +const handleBought = async (id) => { + // function body +}; +``` + +**After:** +```javascript +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:** +```javascript +// === 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:** +```javascript +if (existingItem && existingItem.bought === false) { + // do something +} +``` + +**After:** +```javascript +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 than `if (item?.)` + +--- + +### 2. Ternary Operators +Use ternary operators for simple conditional assignments and returns. + +**Before:** +```javascript +let result; +if (condition) { + result = "yes"; +} else { + result = "no"; +} +``` + +**After:** +```javascript +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:** +```javascript +const handleSuggest = async (text) => { + if (text.trim()) { + // long implementation + } else { + setSuggestions([]); + setButtonText("Add Item"); + } +}; +``` + +**After:** +```javascript +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:** +```javascript +const username = user.username; +const email = user.email; +const role = user.role; +``` + +**After:** +```javascript +const { username, email, role } = user; +``` + +--- + +### 5. Array Methods Over Loops +Prefer array methods (`.map()`, `.filter()`, `.find()`) over traditional loops. + +**Before:** +```javascript +const activeItems = []; +for (let i = 0; i < items.length; i++) { + if (!items[i].bought) { + activeItems.push(items[i]); + } +} +``` + +**After:** +```javascript +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. + +```javascript +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. + +```javascript +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. + +```javascript +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:** +```javascript +const handleUpdate = async (id, newData) => { + await updateItem(id, newData); + loadItems(); // Reloads entire list from server +}; +``` + +**After:** +```javascript +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 `useMemo` for expensive computations +- [ ] Consider `React.memo` for 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 +```javascript +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

Loading...

; + return ( +
+ {items.map(item => ( + + ))} +
+ ); +} +``` + +### After Cleanup +```javascript +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

Loading...

; + + return ( +
+ {items.map(item => ( + + ))} +
+ ); +} +``` + +**Key improvements:** +1. Added section comments for clarity +2. Proper 2-line spacing between sections +3. Wrapped handlers in `useCallback` for performance +4. In-place state updates instead of reloading entire list +5. Better import organization + +--- + +## Additional Resources + +- [React Performance Optimization](https://react.dev/learn/render-and-commit) +- [useCallback Hook](https://react.dev/reference/react/useCallback) +- [useMemo Hook](https://react.dev/reference/react/useMemo) +- [React.memo](https://react.dev/reference/react/memo) + +--- + +## 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) diff --git a/frontend/COMPONENT_STRUCTURE.md b/docs/component-structure.md similarity index 100% rename from frontend/COMPONENT_STRUCTURE.md rename to docs/component-structure.md diff --git a/frontend/README.md b/docs/frontend-readme.md similarity index 100% rename from frontend/README.md rename to docs/frontend-readme.md diff --git a/IMAGE_STORAGE_IMPLEMENTATION.md b/docs/image-storage-implementation.md similarity index 100% rename from IMAGE_STORAGE_IMPLEMENTATION.md rename to docs/image-storage-implementation.md diff --git a/SETUP_CHECKLIST.md b/docs/setup-checklist.md similarity index 100% rename from SETUP_CHECKLIST.md rename to docs/setup-checklist.md diff --git a/frontend/src/pages/GroceryList.jsx b/frontend/src/pages/GroceryList.jsx index e86ae13..7b4dd0e 100644 --- a/frontend/src/pages/GroceryList.jsx +++ b/frontend/src/pages/GroceryList.jsx @@ -1,5 +1,15 @@ import { useCallback, useContext, useEffect, useMemo, useState } from "react"; -import { addItem, getClassification, getItemByName, getList, getRecentlyBought, getSuggestions, markBought, updateItemImage, updateItemWithClassification } from "../api/list"; +import { + addItem, + getClassification, + getItemByName, + getList, + getRecentlyBought, + getSuggestions, + markBought, + updateItemImage, + updateItemWithClassification +} from "../api/list"; import FloatingActionButton from "../components/common/FloatingActionButton"; import SortDropdown from "../components/common/SortDropdown"; import AddItemForm from "../components/forms/AddItemForm"; @@ -13,9 +23,11 @@ import { AuthContext } from "../context/AuthContext"; import "../styles/pages/GroceryList.css"; import { findSimilarItems } from "../utils/stringSimilarity"; + export default function GroceryList() { const { role } = useContext(AuthContext); + // === State === // const [items, setItems] = useState([]); const [recentlyBoughtItems, setRecentlyBoughtItems] = useState([]); const [recentlyBoughtDisplayCount, setRecentlyBoughtDisplayCount] = useState(10); @@ -31,6 +43,8 @@ export default function GroceryList() { const [showEditModal, setShowEditModal] = useState(false); const [editingItem, setEditingItem] = useState(null); + + // === Data Loading === const loadItems = async () => { setLoading(true); const res = await getList(); @@ -39,6 +53,7 @@ export default function GroceryList() { setLoading(false); }; + const loadRecentlyBought = async () => { try { const res = await getRecentlyBought(); @@ -49,11 +64,14 @@ export default function GroceryList() { } }; + useEffect(() => { loadItems(); loadRecentlyBought(); }, []); + + // === Sorted Items Computation === const sortedItems = useMemo(() => { const sorted = [...items]; @@ -95,6 +113,8 @@ export default function GroceryList() { return sorted; }, [items, sortMode]); + + // === Suggestion Handler === const handleSuggest = async (text) => { if (!text.trim()) { setSuggestions([]); @@ -106,26 +126,21 @@ export default function GroceryList() { const lowerText = text.toLowerCase().trim(); const exactMatch = allItems.find(item => item.item_name.toLowerCase() === lowerText); - if (exactMatch) { - setButtonText("Add"); - } else { - setButtonText("Create + Add"); - } + setButtonText(exactMatch ? "Add" : "Create + Add"); try { - let suggestions = await getSuggestions(text); - suggestions = suggestions.data.map(s => s.item_name); - setSuggestions(suggestions); + const suggestions = await getSuggestions(text); + setSuggestions(suggestions.data.map(s => s.item_name)); } catch { setSuggestions([]); } }; + + // === Item Addition Handlers === const handleAdd = useCallback(async (itemName, quantity) => { if (!itemName.trim()) return; - const lowerItemName = itemName.toLowerCase().trim(); - let existingItem = null; try { const response = await getItemByName(itemName); @@ -153,6 +168,7 @@ export default function GroceryList() { }); }, [recentlyBoughtItems]); + const processItemAddition = useCallback(async (itemName, quantity) => { let existingItem = null; try { @@ -162,7 +178,7 @@ export default function GroceryList() { existingItem = null; } - if (existingItem && existingItem.bought === false) { + if (existingItem?.bought === false) { const currentQuantity = existingItem.quantity; const newQuantity = currentQuantity + quantity; const yes = window.confirm( @@ -196,11 +212,14 @@ export default function GroceryList() { } }, []); + + // === Similar Item Modal Handlers === const handleSimilarCancel = useCallback(() => { setShowSimilarModal(false); setSimilarItemSuggestion(null); }, []); + const handleSimilarNo = useCallback(async () => { if (!similarItemSuggestion) return; setShowSimilarModal(false); @@ -208,6 +227,7 @@ export default function GroceryList() { setSimilarItemSuggestion(null); }, [similarItemSuggestion, processItemAddition]); + const handleSimilarYes = useCallback(async () => { if (!similarItemSuggestion) return; setShowSimilarModal(false); @@ -215,6 +235,8 @@ export default function GroceryList() { setSimilarItemSuggestion(null); }, [similarItemSuggestion, processItemAddition]); + + // === Add Details Modal Handlers === const handleAddDetailsConfirm = useCallback(async (imageFile, classification) => { if (!pendingItem) return; @@ -243,6 +265,7 @@ export default function GroceryList() { } }, [pendingItem]); + const handleAddDetailsSkip = useCallback(async () => { if (!pendingItem) return; @@ -261,7 +284,8 @@ export default function GroceryList() { console.error("Failed to add item:", error); alert("Failed to add item. Please try again."); } - }, []); + }, [pendingItem]); + const handleAddDetailsCancel = useCallback(() => { setShowAddDetailsModal(false); @@ -271,14 +295,14 @@ export default function GroceryList() { }, []); - + // === Item Action Handlers === const handleBought = useCallback(async (id, quantity) => { await markBought(id); - setItems(prevItems => prevItems.filter(item => item.id !== id)); loadRecentlyBought(); }, []); + const handleImageAdded = useCallback(async (id, itemName, quantity, imageFile) => { try { const response = await updateItemImage(id, itemName, quantity, imageFile); @@ -300,6 +324,7 @@ export default function GroceryList() { } }, []); + const handleLongPress = useCallback(async (item) => { if (![ROLES.ADMIN, ROLES.EDITOR].includes(role)) return; @@ -317,6 +342,8 @@ export default function GroceryList() { } }, [role]); + + // === Edit Modal Handlers === const handleEditSave = useCallback(async (id, itemName, quantity, classification) => { try { const response = await updateItemWithClassification(id, itemName, quantity, classification); @@ -337,15 +364,18 @@ export default function GroceryList() { ); } catch (error) { console.error("Failed to update item:", error); - throw error; // Re-throw to let modal handle it + throw error; } }, []); + const handleEditCancel = useCallback(() => { setShowEditModal(false); setEditingItem(null); }, []); + + // === Helper Functions === const groupItemsByZone = (items) => { const groups = {}; items.forEach(item => { @@ -358,8 +388,10 @@ export default function GroceryList() { return groups; }; + if (loading) return

Loading...

; + return (