Setting up a React project with Vite
The mental model: how React renders (2)
Focus: Components, JSX, props, lists, conditional rendering
Your portfolio already exists. You built the HTML structure in week 1 and styled it in week 2. This week you rebuild it in React — not from scratch, but as a deliberate translation: the same content, the same sections, the same visual result, now expressed as a component tree.
By the end of this assignment you'll have a React application running on localhost that looks identical to your week 2 portfolio, with every section broken into its own component, your projects rendered dynamically from an array, and the dark mode toggle wired back up in React.
Your week 2 portfolio should have:
<body> elementstyles.css handling layout and responsive designYou'll carry all of this forward. The CSS you wrote in week 2 moves over almost unchanged.
npm create vite@latest portfolio-react -- --template react-ts
cd portfolio-react
npm install
npm run dev
Copy your styles.css from week 2 into src/styles.css and import it in main.tsx:
import './styles.css';
Delete the contents of src/App.tsx and src/App.css so you start from a clean slate.
Create the following structure inside src/:
components/Header.tsx — name, tagline, profile image, dark mode togglecomponents/About.tsx — bio paragraphs and interests listcomponents/Projects.tsx — project grid, renders a ProjectCard for each projectcomponents/ProjectCard.tsx — a single project cardcomponents/Contact.tsx — contact formcomponents/Footer.tsx — copyright lineApp.tsx — imports and composes all componentsCompose them in App.tsx:
import Header from './components/Header';
import About from './components/About';
import Projects from './components/Projects';
import Contact from './components/Contact';
import Footer from './components/Footer';
const projects = [
// defined in step 3
];
export default function App() {
return (
<>
<Header />
<main>
<About />
<Projects projects={projects} />
<Contact />
</main>
<Footer />
</>
);
}
Component requirements:
src/components/ and uses export defaultApp.tsx only imports and composes — no raw HTML sections inside itIn week 1 and 2 your projects were hardcoded HTML. Now they live in a typed array in App.tsx:
interface Project {
id: number;
title: string;
description: string;
techStack: string[];
url?: string;
}
const projects: Project[] = [
{
id: 1,
title: 'Portfolio Page',
description: 'The page you\\'re looking at — built with HTML and CSS in weeks 1 and 2.',
techStack: ['HTML', 'CSS'],
url: '',
},
// add at least 2 more — real or placeholder projects
];
Requirements:
App.tsx and pass it to <Projects /> as a propProjects accepts a projects prop typed with the Project interfaceProjectCard accepts at minimum title, description, and techStack as typed props.map()interface ProjectsProps {
projects: Project[];
}
export default function Projects({ projects }: ProjectsProps) {
if (projects.length === 0) {
return <p>No projects yet.</p>;
}
return (
<section>
<h2>Projects</h2>
<ul>
{projects.map((project) => (
<ProjectCard key={project.id} {...project} />
))}
</ul>
</section>
);
}
Requirements:
.map() to render one <ProjectCard /> per project<ProjectCard /> has a unique stable key — use project.id, never the array indexprojects={[]}techStack as a comma-separated list inside ProjectCard using .join(', ')Your week 1 toggle used document.body.classList.toggle('dark-mode') with a plain event listener. In React, event handlers go directly on JSX elements:
export default function Header() {
function handleThemeToggle() {
document.body.classList.toggle('dark-mode');
}
return (
<header>
{/* your name, tagline, image */}
<button onClick={handleThemeToggle}>Toggle dark mode</button>
</header>
);
}
Your week 2 .dark-mode CSS still applies — nothing changes there. The toggle just moves from script.js into the component.
<aside> 💡
This approach works fine for now. Next week you'll learn useState, which is the proper React way to track whether dark mode is on or off and keep the button label in sync.
</aside>
Requirements:
url is provided and non-empty, render it as a link: <a href={url}>View project</a>url is absent, render nothing for that link — use &&techStack.length > 0 — use &&, and watch the number-zero gotchanpm run dev # confirm everything renders without console errors or warnings
npm run build # confirm the production build succeeds
The browser console must show no red errors and no yellow warnings — especially no missing key warnings.
Then push to your existing portfolio GitHub repository in a new branch called react:
node_modules/ must be in .gitignoreREADME.md to mention the React rebuild and the run commandHeader is a good first target — it has no props and no list rendering.index.html, copy a section, paste it into the component, then fix the JSX differences: class → className, self-close <img />, wrap in a single root element.class for className on each element. You may need to tweak a selector or two.