Building Your First React App in VSCode — With a Little Help From Copilot

Updated: Sep 2
If you've been following this series, you already have VSCode set up with GitHub Copilot in agent mode. This post puts that setup to work on something concrete: a small React app, built on a Mac, from an empty directory to a working dev server in about 15 minutes. The goal isn't to teach React from scratch—there are much better resources for that. Instead, it's to show the end-to-end workflow of building a modern React app with AI assistance sitting right next to the editor. If you've been reading and not building, this is the "now do the thing" post.
One Quick Note on Tooling
The React team officially deprecated create-react-app in February 2025. If you're following an old tutorial that starts with `npx create-react-app`, close that tab. The current defaults are Vite for standalone apps (what we'll use here) or Next.js for full-stack apps with routing and server components.
Vite starts a dev server in under two seconds. It has hot module replacement that feels instant and doesn't bury you in configuration. For a first React app, it's the right tool.
What You Need
Node.js 20 or later. Check with `node --version`. If you don't have it, run `brew install node`.
VSCode with GitHub Copilot enabled. Agent mode is a nice-to-have, but the setup works without it.
About 15 minutes and a directory to work in.
Step 1: Scaffold the Project
Open Terminal (or VSCode's integrated terminal with ⌃`) and run:
Vite's CLI will walk you through a few prompts. Pick:
Framework: React
Variant: JavaScript (or TypeScript if you're comfortable with it—the rest of this post uses JavaScript)
Then:
The dev server starts on http://localhost:5173. Open it in a browser. You should see the Vite + React welcome page with a counter button. That means everything works.
Open the project in VSCode. If code isn't on your PATH, open VSCode manually and drag the folder in. Either way, keep the dev server running in the terminal. It watches for changes and hot-reloads the browser as you save.
Step 2: Understand What You're Looking At
Vite's React template gives you a minimal project structure. The files that matter are:
index.html — the HTML entry point. Vite loads your React code into the `<div id="root">` here.
src/main.jsx — the JavaScript entry point. This mounts your root component to that div.
src/App.jsx — the root component. This is the file you'll spend most of your time in.
src/App.css and src/index.css — styles.
package.json — dependencies and scripts (dev, build, preview).
Ignore `node_modules/`—that's just your installed packages; you never edit it directly. Ignore `public/` for now—it's for static assets like favicons.
Step 3: Replace App.jsx with a Todo List
Open `src/App.jsx` and replace its contents with this:
```javascript
import React, { useState } from 'react';
import './App.css';
function App() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input) {
setTodos([...todos, input]);
setInput('');
}
};
const toggleTodo = (index) => {
const newTodos = [...todos];
newTodos[index] = newTodos[index].done ? newTodos[index].text : { text: newTodos[index], done: true };
setTodos(newTodos);
};
const deleteTodo = (index) => {
const newTodos = todos.filter((_, i) => i !== index);
setTodos(newTodos);
};
return (
<div>
<h1>Todo List</h1>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo, index) => (
<li key={index} onClick={() => toggleTodo(index)}>
{todo.done ? <s>{todo.text}</s> : todo.text}
<button onClick={() => deleteTodo(index)}>×</button>
</li>
))}
</ul>
</div>
);
}
export default App;
```
Save the file. The browser should refresh automatically and show your todo app—a header, input box, add button, and an empty list. Type something, press Enter, and it appears. Click a todo to toggle it done; click the × to remove it.
What's happening in this file, in plain English? The `useState` hook gives you two pieces of state: the list of todos and the current input text. Three functions (`addTodo`, `toggleTodo`, `deleteTodo`) update that state. The return block is JSX—HTML-like syntax that describes what the component should render. When state changes, React re-renders automatically.
Step 4: Make It Look Like Something
The default Vite styling is fine for a demo but ugly for anything else. Replace `src/App.css` with:
```css
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
h1 {
color: #333;
}
input {
padding: 10px;
margin-right: 10px;
}
button {
padding: 10px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
ul {
list-style-type: none;
padding: 0;
}
li {
background: white;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
```
Save the file. The app refreshes. Now it looks like something you wouldn't be embarrassed to show someone.
Step 5: Let Copilot Extend It
This is where the VSCode + Copilot setup from earlier in the series pays off. The app works, but it has an obvious flaw: refresh the page, and your todos are gone. Let's fix that with Copilot agent mode instead of writing the code ourselves.
Open the Copilot Chat pane (⌃⌘I), switch the mode dropdown to Agent, and prompt:
In src/App.jsx, make the todos persist to localStorage so they survive a page refresh. Load them on mount and save them whenever the list changes. Don't change the UI or any other behavior.
Copilot will read the file, propose an edit, and show you a diff. It'll add a `useEffect` to save changes and modify the initial state to read from `localStorage`. Review the diff, accept it, and refresh the browser. Add a todo, refresh again. It's still there.
Try a couple more small extensions to get a feel for the loop:
Add a counter above the list that shows how many todos are left to do (not done).
Add a "Clear completed" button that removes all done todos at once. Only show it when there are completed todos to clear.
The agent-mode pattern is straightforward: describe what you want, let it make the change, and read the diff before accepting. You learn React faster by reading AI-generated code and asking, "Why did it do it this way?" than you would by writing every line yourself. The learning happens in the diffs.
Step 6: Build It for Production
When you're ready to deploy (or just want to see what the production build looks like), stop the dev server (⌃C) and run:
```bash
npm run build
npm run serve
```
The first command bundles your app into `dist/`—minified, tree-shaken, and ready to deploy. The second serves that bundle locally so you can verify it works. The contents of `dist/` are static files—you can drop them on any web host (Netlify, Vercel, Cloudflare Pages, S3, GitHub Pages, or a server you own), and they'll work.
Things I'd Extend Next
A todo list is a toy. The things that turn it into something you'd actually use are mostly obvious extensions, and each one is a good Copilot prompt rather than a rabbit hole:
Categories or tags for todos, with filtering
Due dates with visual urgency indicators
Keyboard shortcuts for common actions
Drag-to-reorder with @dnd-kit
A dark mode toggle that respects prefers-color-scheme
Sync to a backend so it works across devices
The last one is where you graduate from Vite to Next.js or React Router. Anything with real data fetching or routing benefits from a framework rather than a bare Vite app. But for local-only apps and small tools, Vite is the right choice. There's no reason to add complexity you don't need.
A Few VSCode Extensions Worth Installing
If you're going to keep writing React, these earn their keep quickly:
ES7+ React/Redux/React-Native snippets — Type `rfc` and get a full functional component scaffold.
Prettier — Auto-formats your code on save, so you stop arguing with yourself about indentation.
Error Lens — Shows errors and warnings inline next to the offending line instead of requiring you to hover.
Path Intellisense — Autocomplete for import paths.
Don't install everything at once. Add one, use it for a few days, then add the next. Extension bloat is a real drag on VSCode performance, and it's easy to end up with twelve things you forgot you installed.
Conclusion
Building a React app with Vite and GitHub Copilot is an exciting journey. It allows you to leverage AI to enhance your coding experience. Whether you're a seasoned developer or just starting, this workflow can significantly boost your productivity. So, what are you waiting for? Dive in and start building your next project today!
Comments