Hands-on : Build a Grocery List App

Goal: Build a Grocery Wish List App

The React app with Vite for managing a grocery list is set up! Follow these steps:

  1. Project Initialization: Use the Vite template for React and install Bootstrap for styling.

  2. Component Structure: The app is modular, with separate components for each feature.

  3. Implementation Details: Each component handles specific functionality, ensuring maintainable code.

  4. Integration: All components come together in AppGrocery.jsx, the parent component, using state management.

Run npm run dev to see your grocery list application in action!

Step 1. Initialize the project

Run the following commands in your terminal to create a Vite React project and install Bootstrap for styling.

// Step 1: 
// Run the following commands in your terminal
npm create vite@latest react-grocery-app --template react
cd grocery-list-app
npm install
npm install bootstrap bootstrap-icons

Step 2. Structure the src folder

Create the necessary folder structure for organizing the application components.

// Create the following structure:
// src/
//   AppGrocery.jsx
//   components/
//     HeaderNav.jsx
//     GroceryInputForm.jsx
//     GroceryList.jsx
//     Footer.jsx

Step 3. Implement HeaderNav.jsx

  • This component displays the application header with a title.

  • Create HeaderNav.jsx file in src/components folder

// Step 3: Implement HeaderNav.jsx
import React from 'react';

const HeaderNav = () => {
    return (
        <header className="bg-primary text-white text-center py-3">
            <h1>Grocery Wish List</h1>
        </header>
    );
};

export default HeaderNav;

Step 4. Implement GroceryInputForm.jsx

  • This component contains an input field and a button to add new grocery items.

  • Create GroceryInputForm.jsx file in src/components folder

// Step 4: Implement GroceryInputForm.jsx
import React, { useState } from 'react';

const GroceryInputForm = ({ onAddItem }) => {
    const [itemName, setItemName] = useState(""); // State to keep track of the input value.

    const handleAddItem = () => {
        if (itemName.trim()) { // Ensures the input is not empty.
            onAddItem(itemName); // Calls the parent function to add the item.
            setItemName(""); // Resets the input field.
        }
    };

    return (
        <div className="my-3 d-flex justify-content-center">
            <input
                type="text"
                className="form-control w-50"
                placeholder="Enter item name"
                value={itemName} // Binds the input value to the state.
                onChange={(e) => setItemName(e.target.value)} // Updates state on input change.
            />
            <button className="btn btn-success ms-2" onClick={handleAddItem}>
                Add Item // Triggers adding a new item.
            </button>
        </div>
    );
};

export default GroceryInputForm;

Step 5. Implement GroceryList.jsx

  • This component displays a list of grocery items with toggle and delete functionalities.

  • Create GroceryList.jsx file in src/components folder

  • use icon style for delete button. For this, the delete button has been updated to use a Bootstrap icon (bi bi-trash) for a more polished and modern look. You need to install bootstrap-icons at terminal if not installed.

npm install bootstrap-icons
// Step 5: Implement GroceryList.jsx
import React from 'react';

const GroceryList = ({ items, onToggleBought, onDeleteItem }) => {
    return (
        <ul className="list-group">
            {items.map((item, index) => (
                <li
                    key={index} // Provides a unique key for each list item.
                    className="list-group-item d-flex justify-content-between align-items-center"
                >
                    <span
                        style={{ textDecoration: item.bought ? "line-through" : "none" }} // Strikes through if bought.
                        onClick={() => onToggleBought(index)} // Toggles the bought state when clicked.
                        className="cursor-pointer"
                    >
                        {item.name}
                    </span>
                    <div>
                        <button
                            className="btn btn-danger btn-sm"
                            onClick={() => onDeleteItem(index)} // Removes the item when clicked.
                        >
                         <i className="bi bi-trash"></i>  // use "bi bi-trash" instead of "Delete" text 
                        </button>
                    </div>
                </li>
            ))}
        </ul>
    );
};

export default GroceryList;

Step 6. Implement Footer.jsx

  • This component displays a summary of total items and the number bought.

  • Create Footer.jsx file in src/components folder

// Step 6: Implement Footer.jsx
import React from 'react';

const Footer = ({ totalItems, totalBoughtItems }) => {
    return (
        <footer className="text-center py-3">
            <p>{totalBoughtItems} out of {totalItems} items bought</p> // Displays summary statistics.
        </footer>
    );
};

export default Footer;

Step 7. Implement the parent component AppGrocery.jsx

  • This is the main component that integrates all child components and manages the application state.

  • Create AppGrocery.jsx in src folder

  • import Ass.css style

// Step 7: Implement AppGrocery.jsx
import React, { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import HeaderNav from './components/HeaderNav';
import GroceryInputForm from './components/GroceryInputForm';
import GroceryList from './components/GroceryList';
import Footer from './components/Footer';

const AppGrocery = () => {
    const [items, setItems] = useState([]); // Manages the list of grocery items.

    const handleAddItem = (name) => {
        setItems([...items, { id: Date.now(), name:name, bought: false }]); // Adds a new item with a default bought state.
    };

    const handleToggleBought = (index) => {
        setItems(
            items.map((item, i) =>
                i === index ? { ...item, bought: !item.bought } : item // Toggles the bought state of the item.
            )
        );
    };

    const handleDeleteItem = (index) => {
        setItems(items.filter((_, i) => i !== index)); // Removes the item from the list.
    };

    return (
        <div>
            <HeaderNav /> // Renders the header.
            <div className="container my-4">
                <GroceryInputForm onAddItem={handleAddItem} /> // Passes the add item handler.
                <GroceryList
                    items={items} // Passes the current items to the list.
                    onToggleBought={handleToggleBought} // Passes the toggle handler.
                    onDeleteItem={handleDeleteItem} // Passes the delete handler.
                />
            </div>
            <Footer
                totalItems={items.length} // Total number of items.
                totalBoughtItems={items.filter((item) => item.bought).length} // Total bought items.
            />
        </div>
    );
};

export default AppGrocery;

Step 8. Update main.jsx to render AppGrocery

  • This step ensures the main application component is rendered to the DOM.

// Step 8: Update main.jsx to render AppGrocery
// Replace the contents of src/main.jsx with:
import React from 'react';
import ReactDOM from 'react-dom/client';
import AppGrocery from './AppGrocery';

ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <AppGrocery /> // Mounts the main component.
    </React.StrictMode>
);

//Step 9. Run the application

Use the following command to start the development server and view the app in the browser.

Run npm run dev to see your grocery list application in action!

npm run dev

Summing Up

  • Forms

    • Set up

    • Handling onSubmit

    • Multi-input field form

  • Controlled Element

  • Built a Grocery List Web App

    • Lifted state up to the parent component

Last updated