Setting up a React project with Vite
The mental model: how React renders (2)
Difficulty: Easy Concepts: Functional components, JSX, embedding JavaScript expressions
Build a ProfileCard component that renders a small profile box for one person, using only hardcoded data inside the component itself.
Requirements:
npm create vite@latest profile-card -- --template react-tsProfileCard component in its own file inside src/components/name (string), birthYear (number), and hobbies (array of three strings)age from birthYear using new Date().getFullYear() - birthYear<h2> with the name<p> with the age<ul> with the three hobbies (you can hardcode the three <li>s for now — we'll do .map() properly in Exercise 4)<p> saying either "Adult" or "Minor" depending on age, using a ternary expressionApp.tsxHints:
ProfileCard, not profileCard{ } in JSX must be an expression (something with a value): if statements don't work, but ternaries doexport default your component, and import it in App.jsx with the correct pathDifficulty: Easy Concepts: HTML vs JSX, attribute differences, inline styles
Take the broken component below, paste it into a fresh project, and fix every error so it renders without warnings in the browser console.
Starting point — the broken JSX:
JSX
const BrokenCard = () => {
return (
<div class="card" style="padding: 10px; background: #f0f0f0;">
<h2>Welcome!</h2>
<p>This card is broken.</p>
<img src="logo.png">
<label for="name">Your name:</label>
<input type="text" id="name">
<button onclick="alert('hi')">Click me</button>
<!-- TODO: add more content -->
</div>
);
};
export default BrokenCard;
Requirements:
class attribute must be replaced with the correct JSX equivalentfor attribute on the <label> must be replaced with the correct JSX equivalent<img>, <input>) must use the JSX self-closing syntaxstyle attribute must be converted from a string to a JavaScript objectonclick handler must be converted to the JSX way of handling events, and the alert must still trigger when clickedHints:
class is a reserved word in JavaScript, which is why JSX uses something elsepadding: 10 becomes 10px automatically), but strings like "#f0f0f0" need quotesonClick={() => alert('hi')} — note the camelCase and the function reference inside { }{/* this is a comment */} and only work inside JSX, not at the top of a fileDifficulty: Medium Concepts: Component decomposition, props, destructuring, prop defaults
Take a single large App component and break it into smaller reusable components that receive their data through props.
Starting point — paste this into App.jsx:
JSX
const App = () => {
return (
<div>
<header>
<h1>My Travel Blog</h1>
<nav>
<a href="/">Home</a>
<a href="/posts">Posts</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<article>
<h2>3 days in Lisbon</h2>
<p>Posted on 12 March 2026</p>
<p>Lisbon was sunny and full of pastel de nata...</p>
</article>
<article>
<h2>A weekend in Berlin</h2>
<p>Posted on 5 February 2026</p>
<p>Berlin in winter is cold but full of life...</p>
</article>
</main>
<footer>
<p>© 2026 My Travel Blog</p>
</footer>
</div>
);
};
export default App;
Requirements:
Header, Article, and Footer, each in its own file inside src/components/App.jsx must only import and compose the components — no <header>, <article>, or <footer> JSX should remain inside App.jsxArticle component must accept three props: title, date, and contentconst Article = ({ title, date, content }) => ...App.jsx, render two <Article /> instances, passing the original data through props instead of hardcoding it inside the component<Article ... /> line — no other code should need to changetags (an array of strings) with a default value of [], displayed under the article as comma-separated text using .join(", "). If the array is empty, render nothing for the tagsHints:
title="3 days in Lisbon". Anything else (numbers, arrays, booleans, objects) must use braces: tags={["food", "sun"]}({ title, date, content, tags = [] })&&: {tags.length > 0 && <small>{tags.join(", ")}</small>} — and watch out for the number-zero gotcha if you ever change the conditionprops.title = "new", that would be an anti-pattern: props are read-onlyDifficulty: Medium
Concepts: Rendering lists with .map(), unique keys, conditional rendering, empty states
Build a small product catalogue that renders a list of items from an array, with conditional badges and a graceful empty state.
Requirements:
ProductCard component that accepts four props: title (string), price (number), inStock (boolean), and tags (array of strings, default [])€${price.toFixed(2)}), the in-stock status, and the tags as a comma-separated list🔥 Hot badge that only appears when price > 100, using the && pattern⚠️ Out of stock line that only appears when inStock is falseProductList component that accepts one prop: products (an array of product objects)products is empty, the component must return early with <p>No products available.</p>.map() to render one <ProductCard /> per item, spreading the product object into the props (<ProductCard {...product} />) and giving each a unique key={product.id}App.jsx, define an array of at least 5 products with id, title, price, inStock, and tags, and render <ProductList products={products} />Hints:
.map() returns a new array — React knows how to render an array of JSX elements, just wrap the call in { }product.id) as the key, never Math.random() and ideally not the array indexkey prop goes on the outermost element returned inside the .map() callback — in this case, on <ProductCard /> itself, not on something inside itproducts={[]} from App.jsx and confirm the fallback message appears&& returns the left side if it's falsy: {products.length && <List />} will render the literal text 0 when the list is empty. Always compare explicitly: {products.length > 0 && <List />}The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

Built with ❤️ by the HackYourFuture community · Thank you, contributors
Found a mistake or have a suggestion? Let us know in the feedback form.