/GroceryList

Grocery List app made with React, Vite and Yarn.

Primary LanguageJavaScript

Grocery List App

Visit the Live Site!

HTML5 CSS3 React JavaScript Vite Yarn

This is a test app for me; I'm coding from memory with minimal checks of my notes. My aim is to gain a greater understanding of state and practice using memory. I notice that I didn't put the notes in one central place, so I'll be documenting the process.

MVP

This is Grocery List App can:

  • Add new items to the list either by pressing Enter or clicking the plus button
  • Display current items with the ability to check them off and delete
  • A count of remaining items in the list
  • Search ability

Table of Contents

The Process

top

I'm working in VS Code on a Windows 11 computer, so all shortcuts I use are for those. I'm also using the ES7+ React/Redux/React-Native snippets extension.

Add the Dependencies

Set Up the Project

To follow along, ensure you have:

  • the latest Node version installed. You can check in the terminal with
node -v

  1. Create a new React project from the command line using the Vite tool by typing:
yarn create vite
  1. Enter your project name at the prompt after the quick installation.

    Installation with project name prompt

  2. Press Enter to have the package automatically named.

  3. Use the arrow keys to choose a framework.

    React project selected

  4. Pick JavaScript or TypeScript (I went with JavaScript)

    JavaScript chosen

  5. Check out the cool file structure!

    file structure

    I later moved README and screenshots folder up one level.

    And the message that the scaffolding is complete.

    complete message

  6. Change directories. I got my first learning moment here as I had used two words, so I had to enclose it in commas.

    correct way to cd with whitespace

  7. Install dependencies with:

yarn

Super-fast success is proven with the node_modules folder being added to our file structure and this command line message:

node modules in the file structure and cli messages

Spin Up the Servers

top

  1. Start the development server on your computer with
yarn run dev

Ctrl + click the blue link to verify everything's working.

Hyperlink

  1. Check it out on your mobile - Yes, you heard that right! Either stop the current server with Ctrl + C, or just open up a new terminal.

opening a new terminal

cd back to your directory, and type:

yarn dev run --host

You will get

server options

Type the network address (the one with all the numbers), then you can check out your changes on desktop and/or mobile!

Rid Ourselves of the Boilerplate

top

Now it's time to get rid of the defaults!

  1. It's all coming from our /src folder, as you can see by listing all the files:
ls src/
  1. Remove all the files except main.jsx with
rm src/App.css
  1. Now we can actually work within our files! In main.jsx, remove the reference to index.css on line 4.

delete the css import on line 4

Use Dracula UI

top

Dracula UI possibilities

I had come across Dracula UI in my reading and really wanted to try it out! It describes itself as a:

Dark-first collection of UI patterns and components.

First we need to install it to our project.

  1. In your terminal, type yarn add dracula-ui.

  2. I like to verify things, so you can check package.json to see it listed as a dependency on line 11.

dracula_ui version 1.0.3 showing as a dependency

Let's take advantage of the in-editor docs and autocomplete function in VS Code, by installing an extension to bring in Design System Packages.

  1. Ctrl + Shift + X to open Extensions and search for Adobe XD extension.

Adobe XD extension showing 45, 909 downloads

  1. Click on the extension icon in the sidebar, probably in the bottom right corner.

XD icon in status bar

  1. Click on Load package.

extension start screen to load DSPs

  1. Choose node_modules/dracula-ui/dsp by navigating through all the files.

Windows load window showing Grocery List to node_modules to dracula_ui to dsp

  1. Choose the Code Snippets for CSS and React, click Next and you can see the docs!

select CSS and React code snippets

On to the Code!

top

  1. In the src folder, add a new file called (capital!) App.jsx.

  2. Create a exported function component called App.

  3. Add the import statements at the top.

  4. Check it's working by adding a main with h1.

initial App code

  1. Bring in the Dracula UI components and styling! I'm not sure if I was doing this optimally, but... I set up my reset in index.css with styles on the body. I centered my list with CSS Grid and set the background colour.
body {
  display: grid;
  place-items: center;
  height: 100%;
  background-color: black;
}
  1. Import the Box component from Dracula UI, add it to our App JSX and style it up with colour, margin, padding, rounded and width properties special to the Design System.

import box from dracula-ui, add it to the JSX with style properties

Add a Component: Header

top

  1. Add a components folder with Header.jsx.

  2. Import Heading from Dracula UI on line 2.

  3. Add the inline styles.

import Heading and add the styling as

  1. Import Header in App.jsx, then add component.

import statement and component in the JSX

The result so far...

Grocery List title

ItemList

top

Set Up A Default List

top

I got ahead of myself here and jumped straight to AddItem, but it does make more sense to start with a static list.

  1. Create an ItemList component. Import it in App.jsx and add the component in the returned JSX:
return (
  <Box>
    <Header />
    <ItemList />
  </Box>
);
  1. Set up a default list of grocery items to get the list started. This goes within the ItemList function and before returning the JSX. Include the following import statement:
import { useState } from "react";

Set the list as a const with destructuring of items and setItems equal to useState() with an array of objects.

destructured items, setItems for array of objects in useState with id, checked and item properties

Add to ItemList.jsx, even though it may be moved later on to App.jsx.


Use Font Awesome Icons

top

  1. Use react-icons from NPMJS to import only the icons used in this project. In your terminal, type yarn add react-icons --save-prod Then verify it in package.json.

react-icons version 4.6.0 in our dependencies

  1. Optional: Go to Font Awesome to search for plus. Make sure to click the Free tab.
  2. Add the import statement to the top of the component where it will be used. Like import { Fa< name of icon >} from "react-icons/fa";
  3. Add to the JSX just like a component - <FaCheck>.

  1. Include two more import statements for our Dracula UI components and Font Awesome icon:
import { List, Checkbox } from "dracula-ui";
import { FaTrashAlt } from "react-icons/fa";
  1. Have the ItemList function return a <List> component from Dracula UI.

  2. Get ready to have lots of fun creating the <li>s! Use .map() on items created on line 8. This will go over each item, creating the JSX.

{items.map((item) => (...))}
  1. Create an <li> with a couple important attributes to enclose each line.
<li className="item" key={item.id}>
  ...
</li>
  1. Use the Dracula UI checkbox component passing all the properties (pictured below).

  2. Use the label as the actual item to display. Don't worry, I'll go over all that's going on there later!

  3. Add the Font Awesome trash can icon on line 55.

List component with map function creating each list item including a checkbox, label, and trash icon

Style with Dracula UI or CSS

top

  1. Style it! Much of this could be done through Dracula UI including: py, pb, size on List and li, and color on Checkbox.

  2. Some were easier to control with regular CSS, like font-size and hover/focus effects. The icons can be targetted by the svg selector.

  3. The most complex thing was getting the item text to change colour when the li was hovered on. I had to grab the class name using DevTools.

li.item:hover .drac-text,
li.item:focus .drac-text {
  color: #ff80bf;
}

Handle Checks

top

  1. Set up the handleCheck function to take in the id. Create a listItems variable to map over each item: checking if it is the clicked id, if so create a new array so we don't directly change the state and then flip that item's checked value. Afterwards set the items to the updated state.

handleCheck function mapping over items in listItems to check id, create a new array, then change checked value

  1. Set up conditional styling to add a line through the item to further indicate that it's checked. To the label tag, add a style attribution with double curly brackets, { } so we can use a ternary operator to only apply the rule when the item has been checked.

style attribute checking if item checked and applying a line through text decoration or nothing

  1. Improve UX by adding a double click event on the item to trigger handleCheck like on line 51 above.

  2. This is an intermediate step, to be changed later. To have the app store the user changes, add this line to the function:

localStorage.setItem("shoppinglist", JSON.stringify(listItems));

This saves the state to local storage.

Handle Delete

top

  1. Add an onClick event to the trash can icon and use an anonymous function so we can pass in the item's id.

on click event added to delete button calling the handleDelete function with id passed in

  1. Write the function which will create an array of only the items not matching that id, set the state, and save to localStorage.

handle delete function creating a new list items variable filtering out the clicked item, setting state and saving to local storage

Add Empty List Message

top

  1. Add <> and </> tags as the first item, then tab everything over. This is needed because we will use a ternary operator on <List> and React must have one element to render.

  2. Tab over and begin the ternary with {items.length ? (, then cut the )} - to be added afterwards. In essence, this is saying if there are items, then proceed with all the existing code.

  3. Tab all the code over once, so no indentation errors will be thrown.

  4. After the closing </List> tag and one indentation in, continue the ternary like on line 77.

  5. Add the Dracula UI Text component to the import statement on line 2.

  6. Add a <Text> tag with a style expression and our message.

  7. Paste the )} to close out the ternary operator.

  8. One indent in add the closing fragment, </>.

  9. Close the return statement and function.

end of the ternary operation, JSX, and function

Note: I had quite the difficult time finding exactly which bracket (rounded, curly) was wrong. I've included a copy of the code up to this state to compare. I suggest using DiffChecker to compare. It's so easy to make a single, critical mistake here! 😭

Pass props Down From App to ItemList Components

top

This tells our user how many items are on their list.

  1. In ItemList.jsx, highlight the entire list variable, use Ctrl + X to cut it.

  2. In App.jsx, press Ctrl + C to copy it in the App function, right above the return statement.

destructured items variable moved from ItemList to App

  1. Pass items and setItems down as props to the ItemList component.

items and set items passed as props down to Item List component

  1. Repeat for handleCheck and handleDelete functions: cut and copy, and pass as props.

  2. Back in ItemList.jsx, destructure the props to gain access. Pay attention to any that are grayedout, like setItems. A quick scan shows that it isn't used in this file (only used in the functions). So you can delete it from this file.

destructured props in Item List, with set Items greyedout

  1. Remove setItems={setItems} on line 48 in App.jsx.

  2. Add import { useState } from 'react'; to App.jsx and remove from ItemList.jsx.

Footer Component

top

This will show the user how many items are on their Grocery List.

  1. Create Footer.jsx using the Paragraphy component from Dracula UI, or Text will work too.

Footer component file using Paragraph from Dracula UI

  1. Import the component into App.jsx.

import statement for Footer in App file

  1. Add component to the JSX.

  2. Pass the prop down to Footer. We can name it whatever we want, so we'll call it length.

passing the length of the items list to Footer

  1. Add color and align Dracula UI attributes to style.

  2. Within the Paragraph, use the expression {length} to get the passed number of list items.

  3. Use a ternary expression after List to display Item or Item(s) based on the number.

styled Paragraph using props to show number of items and ternary expression to construct grammatically correct sentence

Use Abstraction to Clean-up Code

top

ItemList.jsx is a bit unwieldy as we had a ternary expression to display our Empty List. message. But it started on line 10 and went all the way until line 49. We can make it a lot more readable!

  1. Create a new component called ListLi.jsx.

  2. Look at the parent to copy the props and drill them down.

  3. Cut and copy the <List> from ItemList to ListLi.

  4. Update the import statements for the new component.

new List Li component file with import statements, prop drilling, and JSX

  1. Import the component into ItemList and add to the JSX with props.

cleaned up Item List component

AddItem Component

top

  1. Create the component with imports of Dracula UI and Input component. Add Input tag to JSX with the style attributes.

AddItem component with import of Dracula UI and Input component. Input with attributes included in the JSX

  1. Add the import line and component to App.jsx.

import and component added to App.jsx

  1. In AddItem.jsx and add import { FaPlus } from 'react-icons/fa' to the top of the file.

  2. Because we have more than one item in the JSX, add the <form className="inputBlock"></form> tags to enclose <Input> and the add button we will have.

  3. Add a <label> with sr-only class, so it's accessible but visually hidden.

  4. Add the Input component, imported from Dracula UI, with autoFocus, id, type, placeholder and required attributes.

  5. Add FaPlus (with import statement on line 3) with attributes to add it's role, tabIndex and aria-label.

import icon as FaPlus, add containing tags, add icon as component

  1. Over in index.css, put flex on our inputBlock class.

centering input and button with flexbox

  1. Import and add the component to App.jsx, then set up the state with const [newItem, setNewItem] = useState("");.

  2. Pass the props to the component: newItem, setNewItem, and the handleSubmit function we will create.

  3. Add destructured props to AddItem.jsx on line 5: export default function AddItem({ newItem, setNewItem, handleSubmit }).

  4. Set the Input tag to be the one source of truth for this controlled input with value={newItem}.

  5. Use onChange={(e) => setNewItem(e.target.value)} to change the state as the user types.

  6. Add onSubmit={handleSubmit} on the <form> element itself.

  7. Back in the handleSubmit function in App.jsx, we first want to prevent the page reloading, then check if there is indeed an item to add, call the addItem function and set the state back to empty.

handle Submit function preventing a page reload, checking for input, calling add Item function, then clearing the state

  1. Create the addItem function.

  2. Add the item id with const id = items.length ? items[items.length - 1] + 1 : 1;. This checks if there are items, returns the index of the item length minus 1, then increments the id, or adds an id of 1.

  3. Create a new item object with const nextNewItem = { id, checked: false, item };.

  4. Create a new array to update the state of the list, using the spread operator and adding the newly entered item: const listItems = [...items, nextNewItem];.

  5. Now we use the same code to set the new state and store it, like we already have in handleCheck and handleDelete. So we can make it DRYer with a new function to call each time.

new function to set items and save to local storage

I ran into some issues with line 39 with adding an id, so I switched to using uuidv4. Add to dependencies with the CLI command yarn add uuidv4, import in App.jsx with import { v4 as uuidv4 } from 'uuid';, then call it in the addItem function with const id = uuidv4(). This is actually a more secure method!

  1. Finish up the handleSubmit function.

add add Item function call to handle submit

  1. Add onClick={handleSubmit} to the <FaPlus> tag to allow user to use both Enter key and button to submit.

Set the State

top

  1. In App.jsx, import useState on line 6.

  2. Handle the state to immediately react to changes our user makes in the input. Define the what's used in state, newItem and setNewItem, using the array destructuring from useState.

import useState from react and destructuring useState

I just noticed that I had Header as my export function in AddItem.jsx, so interesting that I was able to use it in App.jsx as <AddItem /> - the correct name!

change function name to AddItem

  1. Pass the props to AddItem.jsx, remembering to place them in curly brackets.

pass newItem and setNewItem into the function

Load State from localStorage

top

To-date the initial grocery list items are being added from our default list, so let's fix that!

  1. Delete the array within useState on line 12 of App.jsx.

  2. Replace with JSON.parse(localStorage.getItem("shoppinglist")).

replace the default array with local Storage

Add Search Functionality

top

  1. Add a new component SearchItem.jsx.

  2. Have it return a <form> with a className and onSubmit.

  3. As this form won't have a button, we will immediately prevent page reload in the onSubmit.

  4. Add a label, like in AddItem.

  5. Import Dracula UI and the Input component.

  6. Add the Input tag with attributes, especially role="searchbox".

Search Item component file with imports, form, label and input

  1. Put the import statement under AddItem import in App.jsx.

  2. Set the state on line 15: const [search, setSearch] = useState("");.

  3. Add the component and props in the JSX.

Search Item component with search and set Search props

  1. Use the wonders of Dracula UI to style it with a just 6 more attributes on the Input tag in SearchItem.jsx.

style attributes

  1. Now link it to the state. Add the destructured props to SearchItem.

  2. Add the reference to the props to Input.

value and on Change

  1. Filter the items in App.jsx on line 65 by changing the expression: items={items.filter(item => ((item.item).toLowerCase()).includes(search.toLowerCase()))}.

Add a Hook to Shift Focus

top

Currently, when the user uses the Plus Button to add an item, the focus stays there.

focus on plus button after use

  1. Import useRef in AddItem.jsx on line 4.

  2. Add it as a variable on line 7.

  3. Add a ref attribute to Input on line 20.

  4. Send the focus back...

useEffect

top

  1. Add useEffect to import statement on line 9 of App.jsx with a comma after useState.

  2. Set up the function with a dependency, so it only changes when something in it's dependency changes rather than when anything changes.

useEffect(() => {}, []);
  1. Cut and copy the JSON.parse... line from line 14's useState.

  2. Within useEffect setItems() to this copied line. This will set the items state at load time.

  3. Initialize useState to an empty array.

importing use Effect, initializing items use State to an empty array, use Effect function calling shopping list from local Storage when the page loads