Cohorts Week 3: React Counter App
First, create a fork and clone the project from Github:
git clone https://github.com/CSES-Open-Source/cohorts-week-3-react.gitBefore starting, make sure you have Node.js v18+ installed. You can check by running:
node -vIf you see something like v20.11.1, you’re ready to continue.
If not, follow the steps below based on your operating system.
-
Install Node with Homebrew (recommended)
brew install node
-
Verify installation
node -v npm -v
You should now see version numbers for both Node and npm.
-
(Alternative) If you don’t use Homebrew, you can also download Node directly:
- Go to https://nodejs.org
- Choose LTS → macOS Installer (
.pkg) - Follow the on-screen setup wizard
-
Install Node using the official installer
- Visit https://nodejs.org
- Download the LTS version (
.msifile) - Run the installer
- Keep the default options checked — especially “Add to PATH”
-
Verify installation Open a new Command Prompt and run:
node -v npm -v
You should see version numbers appear.
-
(Optional for advanced users) You can also install via Chocolatey:
choco install nodejs-lts
Navigate to the folder you created earlier with your name. Then run the following command in your terminal:
npm create vite@latest counter -- --template react-ts
This will create a new Vite project with React and TypeScript in a folder named counter.
Move into the folder and install dependencies:
cd counter
npm install
You should now see a structure like this:
counter/
├─ index.html
├─ package.json
├─ tsconfig.json
├─ vite.config.ts
└─ src/
├─ App.css
├─ App.tsx
├─ index.css
└─ main.tsx
You can start the development server to verify everything is working:
npm run dev
This might be overwhelming at first, but don't worry! We'll guide you through replacing the default files so that it's more manageable.
We’ll now copy over your prepared files from root/template/ into the new Vite project.
Make sure you’re still inside root/your-name/counter/, then run:
mkdir -p src
cp -r ../../template/* src/
This will copy the necessary files into your src/ directory.
In Counter.tsx, ensure that useState is imported from React at the top of the file:
import { useState } from "react";useState is a React Hook that allows you to add state management to your functional components.
Our goal is to create a Counter component that displays a count and has buttons to increment, decrement, and reset the count. It will take in two props: name (a string) and initial (a number).
This is what it will look like
<Counter name="example" initial={0} />In React, a prop (short for "property") is a way to pass data from a parent component to a child component. Props are read-only, meaning that a child component cannot modify the props it receives from its parent.
In Counter.tsx, define the props for the Counter component by creating an interface named CounterProps. This interface should include two properties: name of type string and initial of type number. Then, update the Counter function to accept these props.
interface CounterProps {
name: string;
initial: number;
}Update the Counter function to accept props of type CounterProps:
function Counter({ name, initial }: CounterProps) {
}This allows you to access the name and initial props directly within the component. Make sure the variable names match those defined in the CounterProps interface.
Inside the Counter component, use the useState hook to create a state variable named count and a function named setCount to update it. Initialize count with the value of the initial prop.
const [count, setCount] = useState(initial);This sets up a state variable count that starts with the value passed in through the initial prop.
For the <h2> tag inside the return statement of the Counter component, replace the placeholder text with the name prop to display the name of the counter.
<h2>{name}</h2>This will dynamically show the name of the counter based on the prop passed to the component.
For the <p> tag inside the return statement of the Counter component, replace the placeholder text with the count state variable to display the current count. Also, add an id attribute to the <p> tag with the value count-${name}.
<p id={`count-${name}`}>{count}</p>This will dynamically show the current count and assign a unique ID based on the counter's name.
Add onClick event handlers to the buttons inside the return statement of the Counter component to update the count state using the setCount function.
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(count + 1)}>+</button>This will allow the buttons to decrement, reset, and increment the count when clicked.
In App.tsx, ensure that you import the useState and useEffect hooks from React at the top of the file:
import { useEffect, useState } from "react";This allows you to use the React hooks within the App component.
In App.tsx, ensure that you import the Counter component from the ./ components/Counter file at the top of the file:
import Counter from "./components/Counter";This allows you to use the Counter component within the App component.
In the App component, add a Counter component and pass the required props:
function App() {
return (
<div>
<Counter name="Counter 1" initial={0} />
</div>
);
}This will render the Counter component with the specified name and initial props.
In the App component, add a second Counter component with a different name and initial value:
<Counter "INSERT THE PROPER PROPERTIES HERE" />Inside the Counter component, create a new function named changeCount that takes a parameter newCount of type number. This function should update both the count state and call the updateCounter function to update localStorage.
const changeCount = (newCount: number) => {
setCount(newCount);
updateCounter(name, newCount); // update localStorage directly
};updateCounter is a helper function that is given to you in helpers/counter.ts that updates the counter value so that it persists even after a page refresh.
Modify the onClick event handlers of the buttons inside the return statement of the Counter component to use the changeCountfunction instead ofsetCount`:
<button onClick={() => changeCount(count - 1)}>-</button>
<button onClick={() => changeCount(0)}>Reset</button>
<button onClick={() => changeCount(count + 1)}>+</button>This will ensure that both the state and storage are updated when the buttons are clicked so that the changes persist across page refreshes.
In App.tsx, create an interface named CounterData that defines the structure for counter objects. This interface should include two properties: name of type string and initial of type number.
interface CounterData {
name: string;
initial: number;
}This interface will be used to type the counter data loaded from localStorage.
In the App component, use the useState hook to create a state variable named counters that holds an array of CounterData objects. Initialize it with an empty array.
const [counters, setCounters] = useState<CounterData[]>([]);This sets up a state variable to manage the list of counters in the application.
In the App component, use the useEffect hook to load the counters from localStorage when the component mounts. Inside the useEffect, call the loadCounters function and update the counters state with the loaded data.
useEffect(() => {
const loadedCounters = loadCounters();
setCounters(loadedCounters);
}, []);useEffect is a React Hook that allows you to perform specific actions in components. By default, it runs after the first render and after every update. The empty array ([]) ensures it only runs once when the component mounts.
This will ensure that the counters are loaded from localStorage when the app starts.
In the App component, replace the existing Counter components with a dynamic rendering of Counter components based on the counters state. Use the map function to iterate over the counters array and render a Counter for each item.
{counters.map((counter) => (
<Counter key={counter.name} name={counter.name} initial={counter.initial} />
))}list.map() is a JavaScript array method that creates a new array by calling a provided function on every element in the calling array. In this case, it is used to generate a list of Counter components based on the data in the counters state.
This will render a Counter component for each counter object in the counters array.
In the App component, uncomment the button that adds a new counter. Implement the onClick event handler to call the addCounter function with a unique name and an initial value of 0. After adding the new counter, update the counters state to include the newly added counter.
To upload your work to your GitHub repository, follow these steps:
In your terminal, navigate to the root directory of your project (the your-name folder)
and run the following command to stage all your changes:
```bash
git add .
Next, commit your changes with a descriptive message:
git commit -m "Add new counter functionality"Finally, push your changes to your GitHub repository:
git push origin mainThis will upload your committed changes to the main branch of your remote repository on GitHub.
You have successfully completed the React Counter App project! 🎉