Before You Begin

  • Node.js 18.x or later (LTS recommended)
  • npm 9.x or later (comes with Node.js)
  • A code editor (VS Code, WebStorm, or similar)
  • Basic HTML/CSS knowledge (you know what <div> and class mean)
  • Familiarity with JavaScript fundamentals: variables, functions, arrays, objects, arrow functions

No prior React experience needed. We start from absolute zero.

1. The Problem with Most React Tutorials

Most React tutorials throw you into JSX, components, and state management all at once. You copy-paste a counter app, then a todo list, and you still have no idea how to build something you'd actually use. Sound familiar?

We're going to fix that. You'll build a real, interactive dashboard for browsing music genres. By the end, you'll have a working app that displays genre cards, lets you filter them, and shows a detail view when you click one. This isn't a toy—it's the skeleton of every content dashboard on the web.

2. Scaffold Your Project Without CRA

Create React App is bloated and slow. Instead, use Vite—the modern, fast build tool adopted by the React team and countless open-source projects on GitHub.

npm create vite@latest genre-dashboard -- --template react
cd genre-dashboard
npm install
npm run dev

Open http://localhost:5173 in your browser. You'll see the default Vite + React page. Clear src/App.css and replace src/App.jsx with this:

function App() {
  return <h1>Genre Dashboard</h1>;
}

export default App;

3. Your First Component: GenreCard

Components are the building blocks of any React interface. Think of them as custom HTML elements that you define with JavaScript functions. A common pattern seen in production React repositories on GitHub is to start with a single, focused component and build outward.

Create src/components/GenreCard.jsx:

function GenreCard({ name, color, emoji }) {
  return (
    <div style={{ backgroundColor: color, padding: '1rem', borderRadius: '8px', margin: '0.5rem' }}>
      <span style={{ fontSize: '2rem' }}>{emoji}</span>
      <h2>{name}</h2>
    </div>
  );
}

export default GenreCard;

Notice the curly braces in { name, color, emoji }. That's destructuring the props object—the mechanism for passing data from parent to child. This is how real components receive dynamic content.

Now use it in App.jsx:

import GenreCard from './components/GenreCard';

function App() {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap' }}>
      <GenreCard name="Jazz" color="#2E86AB" emoji="🎷" />
      <GenreCard name="Electronic" color="#A23B72" emoji="🎛️" />
      <GenreCard name="Rock" color="#F18F01" emoji="🎸" />
    </div>
  );
}

export default App;

Refresh the browser. You have three colored cards. This is your first React component in action.

4. Data-Driven Rendering with Arrays and Keys

Hardcoding components is unsustainable. Real apps fetch data from APIs or define data arrays. Extract your genre data into a separate file—a pattern recommended by the official React documentation for separating concerns.

Create src/data/genres.js:

export const genres = [
  { id: 'jazz', name: 'Jazz', color: '#2E86AB', emoji: '🎷', description: 'Smooth improvisations and complex harmonies.' },
  { id: 'electronic', name: 'Electronic', color: '#A23B72', emoji: '🎛️', description: 'Synthesizers, drum machines, and digital beats.' },
  { id: 'rock', name: 'Rock', color: '#F18F01', emoji: '🎸', description: 'Electric guitars, powerful drums, and raw vocals.' },
  { id: 'classical', name: 'Classical', color: '#C9A227', emoji: '🎻', description: 'Orchestral compositions spanning centuries.' },
];

Now update App.jsx to map over this array:

import GenreCard from './components/GenreCard';
import { genres } from './data/genres';

function App() {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap' }}>
      {genres.map((genre) => (
        <GenreCard
          key={genre.id}
          name={genre.name}
          color={genre.color}
          emoji={genre.emoji}
        />
      ))}
    </div>
  );
}

export default App;

The key prop is mandatory when rendering lists. Many developers on StackOverflow encounter the "Each child in a list should have a unique 'key' prop" warning—that's why we add key={genre.id}. It helps React track which items change, are added, or are removed.

You now have four cards rendered from a single data source. Add or remove items from the genres array, and the UI updates automatically.

5. State and Event Handling: Building a Filter

A dashboard without user interaction is just a brochure. Let's add a filter bar that shows only genres matching a search term. This introduces state, the most misunderstood concept in React.

State is data that changes over time. When state changes, React re-renders the component to reflect the new data.

Add a search input and a filteredGenres computed value in App.jsx:

import { useState } from 'react';
import GenreCard from './components/GenreCard';
import { genres } from './data/genres';

function App() {
  const [searchTerm, setSearchTerm] = useState('');

  const filteredGenres = genres.filter((genre) =>
    genre.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        placeholder="Filter genres..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        style={{ margin: '1rem', padding: '0.5rem', width: '300px' }}
      />
      <div style={{ display: 'flex', flexWrap: 'wrap' }}>
        {filteredGenres.map((genre) => (
          <GenreCard
            key={genre.id}
            name={genre.name}
            color={genre.color}
            emoji={genre.emoji}
          />
        ))}
      </div>
    </div>
  );
}

export default App;

Type "roc" in the input—only the Rock card appears. Clear it, and all cards return. The useState hook gives you searchTerm (the current value) and setSearchTerm (the function to update it). Every keystroke triggers a re-render, and filteredGenres recalculates automatically.

Engineering blogs from companies like Airbnb emphasize that derived state (like filteredGenres) should never be stored in state itself—compute it from existing state and props. That's exactly what we did.

Sponsored Deal

6. Conditional Rendering: Showing Details

Clicking a card should show more info about that genre. We need to track which genre is selected. Add selected state and a detail panel.

Update App.jsx:

import { useState } from 'react';
import GenreCard from './components/GenreCard';
import GenreDetail from './components/GenreDetail';
import { genres } from './data/genres';

function App() {
  const [searchTerm, setSearchTerm] = useState('');
  const [selectedGenre, setSelectedGenre] = useState(null);

  const filteredGenres = genres.filter((genre) =>
    genre.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        placeholder="Filter genres..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        style={{ margin: '1rem', padding: '0.5rem', width: '300px' }}
      />
      <div style={{ display: 'flex', flexWrap: 'wrap' }}>
        {filteredGenres.map((genre) => (
          <GenreCard
            key={genre.id}
            name={genre.name}
            color={genre.color}
            emoji={genre.emoji}
            onSelect={() => setSelectedGenre(genre)}
          />
        ))}
      </div>
      {selectedGenre && <GenreDetail genre={selectedGenre} onClose={() => setSelectedGenre(null)} />}
    </div>
  );
}

export default App;

Create src/components/GenreDetail.jsx:

function GenreDetail({ genre, onClose }) {
  return (
    <div style={{
      position: 'fixed',
      top: 0, left: 0, right: 0, bottom: 0,
      backgroundColor: 'rgba(0,0,0,0.5)',
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center'
    }}>
      <div style={{
        background: 'white',
        padding: '2rem',
        borderRadius: '8px',
        maxWidth: '400px'
      }}>
        <span style={{ fontSize: '3rem' }}>{genre.emoji}</span>
        <h2>{genre.name}</h2>
        <p>{genre.description}</p>
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  );
}

export default GenreDetail;

Update GenreCard to accept and call onSelect:

function GenreCard({ name, color, emoji, onSelect }) {
  return (
    <div
      onClick={onSelect}
      style={{ backgroundColor: color, padding: '1rem', borderRadius: '8px', margin: '0.5rem', cursor: 'pointer' }}
    >
      <span style={{ fontSize: '2rem' }}>{emoji}</span>
      <h2>{name}</h2>
    </div>
  );
}

export default GenreCard;

The selectedGenre && <GenreDetail ... /> syntax is a common pattern for conditional rendering. When selectedGenre is null, the expression is falsy, and nothing renders. When it's an object, the component appears. This is idiomatic React—no if statements needed in JSX.

7. Common Issues

Issue: Components not updating when state changes.

You likely mutated state directly instead of using the setter. Never do searchTerm = 'new value'—always call setSearchTerm('new value'). React relies on the setter to detect changes.

Issue: The "missing key prop" warning.

Always provide a unique, stable key when rendering lists with .map(). Using array index as a key is a bad practice—it breaks animations and can cause bugs when the list order changes. Use a unique ID from your data.

Issue: State updates feel delayed.

State updates are asynchronous. If you log state right after calling a setter, you'll see the old value. That's expected. Use useEffect (a hook for side effects) if you need to react to state changes.

Issue: Event handlers firing on parent elements.

If a click on a child element triggers the parent's handler, you may need event.stopPropagation() in the child handler. This prevents the event from bubbling up the DOM tree.

What You Built

You now have a working React dashboard that:

  • Renders components from data
  • Filters content with user input
  • Shows modals with conditional rendering
  • Uses state to track interactions

This is the foundation every React developer needs before tackling advanced state management like useReducer or external libraries. The same patterns power apps at Netflix, Airbnb, and Discord. You're ready for the next step.

If you want to extend this project, try adding a "favorites" feature using state, or fetch genre data from an API using useEffect. The official React documentation's "Thinking in React" guide is your next read.