React in 2020: Getting Started the Right Way

By Odilon10 min read

React in 2020: Getting Started the Right Way#

TL;DR

  • Use Create React App with the TypeScript template — it handles Webpack and Babel so you can focus on code
  • Functional components are the right default from day one; class components are legacy
  • Invest 30 minutes in your folder structure before you write a single feature component

Every week I see beginners starting React with outdated tutorials. They learn class components because the tutorial is from 2017. They put everything in one file. They fight with Webpack configuration before they have written a single component.

This is the setup guide I wish someone had handed me. We are going to scaffold a real project, understand JSX properly, and build our first few components using patterns that will still make sense six months from now.

Setting Up with Create React App#

Create React App (CRA) is the official scaffolding tool maintained by the React team. In 2020 it is still the right default choice for new projects. It gives you:

  • Webpack 4 with hot module replacement
  • Babel configured for modern JavaScript and JSX
  • Jest with jsdom for testing
  • A sensible development server
  • A production build command that produces optimized bundles
bash
npx create-react-app my-dashboard --template typescript
cd my-dashboard
npm start

I always use the TypeScript template. Even if you are not yet comfortable with TypeScript, starting with it prevents a painful migration later. Adding TypeScript to an existing JavaScript React codebase is three weeks of misery that is entirely avoidable.

The generated project structure looks like this:

my-dashboard/
  src/
    App.tsx
    App.test.tsx
    index.tsx
    react-app-env.d.ts
  public/
    index.html
  package.json
  tsconfig.json

Project Structure That Scales#

The default CRA structure puts everything flat in src/. That works for tutorials. For anything you plan to maintain for more than a month, I organize by feature:

src/
  components/       # Shared, reusable UI components
    Button/
      Button.tsx
      Button.test.tsx
      index.ts
  features/         # Feature-specific components and logic
    Dashboard/
      DashboardPage.tsx
      RecentActivityList.tsx
      StatsCard.tsx
    UserProfile/
      UserProfilePage.tsx
      AvatarUploader.tsx
  hooks/            # Custom hooks
  utils/            # Pure utility functions
  types/            # Shared TypeScript interfaces
  App.tsx
  index.tsx

Components get their own directory when they need more than one file (styles, tests, subcomponents). Simple single-file components can live directly in their feature folder. The key thing is to decide on a structure before you write feature number one. Moving fifty files later is painful in a way that is hard to appreciate until you have done it.

Understanding JSX#

JSX is syntactic sugar over React.createElement(). When you write:

jsx
const element = <h1 className="title">Hello, world</h1>;

Babel compiles it to:

javascript
const element = React.createElement('h1', { className: 'title' }, 'Hello, world');

This compilation step is why import React from 'react' is required at the top of every file that uses JSX — the transform needs React in scope to call React.createElement. (This will change in React 17, but for now, just include the import.)

A few JSX rules that trip up beginners:

Use className not class: JSX attributes map to DOM properties, and the DOM property for CSS classes is className.

Expressions go in curly braces: Anything that needs to be evaluated as JavaScript goes inside {}.

Return a single root element: A component must return one root node. Use fragments when you do not want an extra DOM wrapper.

tsx
// a notification banner
function NotificationBanner({
  message,
  type,
  onDismiss
}: {
  message: string;
  type: 'info' | 'warning' | 'error';
  onDismiss: () => void;
}) {
  const iconMap = {
    info: 'ℹ️',
    warning: '⚠️',
    error: '❌',
  };

  return (
    <div className={`notification notification--${type}`}>
      <span className="notification__icon">{iconMap[type]}</span>
      <p className="notification__message">{message}</p>
      <button
        className="notification__dismiss"
        onClick={onDismiss}
        aria-label="Dismiss notification"
      >
        ×
      </button>
    </div>
  );
}

Functional Components and Props#

In 2020, functional components are the right default. Class components are not going away — the React team has committed to supporting them — but all new development should use functional components and hooks.

A functional component is a function that accepts props and returns JSX. Props are just an object. With TypeScript, we define an interface for that object, and this is where things start to feel really clean compared to what I was used to:

tsx
interface UserAvatarProps {
  displayName: string;
  avatarUrl: string | null;
  size?: 'sm' | 'md' | 'lg';
  showName?: boolean;
}

function UserAvatar({
  displayName,
  avatarUrl,
  size = 'md',
  showName = true
}: UserAvatarProps) {
  const sizeClasses = {
    sm: 'w-8 h-8 text-xs',
    md: 'w-12 h-12 text-sm',
    lg: 'w-16 h-16 text-base',
  };

  const initials = displayName
    .split(' ')
    .map(part => part[0])
    .slice(0, 2)
    .join('')
    .toUpperCase();

  return (
    <div className="user-avatar-wrapper">
      <div className={`user-avatar ${sizeClasses[size]}`}>
        {avatarUrl ? (
          <img src={avatarUrl} alt={displayName} />
        ) : (
          <span aria-hidden="true">{initials}</span>
        )}
      </div>
      {showName && <span className="user-avatar-name">{displayName}</span>}
    </div>
  );
}

Notice a few things:

  • Props are destructured directly in the function signature — much cleaner than this.props.whatever
  • Default values are set with destructuring defaults, not a separate defaultProps object
  • The TypeScript interface makes the component's contract explicit

Coming from dynamically typed templating systems where you could pass anything and get silent failures, this explicitness is a breath of fresh air.

Rendering Lists#

Rendering arrays of data is one of the first things you will do in any real app. React handles this with .map() and one important requirement: the key prop.

tsx
interface Transaction {
  id: string;
  description: string;
  amount: number;
  date: string;
  type: 'debit' | 'credit';
}

function TransactionList({ transactions }: { transactions: Transaction[] }) {
  if (transactions.length === 0) {
    return <p className="empty-state">No transactions found.</p>;
  }

  return (
    <ul className="transaction-list">
      {transactions.map(transaction => (
        <li key={transaction.id} className={`transaction transaction--${transaction.type}`}>
          <span className="transaction__description">{transaction.description}</span>
          <span className="transaction__date">{transaction.date}</span>
          <span className="transaction__amount">
            {transaction.type === 'debit' ? '-' : '+'}
            ${Math.abs(transaction.amount).toFixed(2)}
          </span>
        </li>
      ))}
    </ul>
  );
}

The key prop must be a stable, unique identifier — use your database record's ID, not the array index. React uses keys to reconcile which list items changed, were added, or were removed between renders. Using the array index as a key produces subtle bugs when items are reordered or removed. A sorted list showing the wrong data in the wrong rows is a classic symptom of this mistake.

Conditional Rendering#

tsx
function DataCard({ isLoading, error, data }: {
  isLoading: boolean;
  error: string | null;
  data: { title: string; value: number } | null;
}) {
  // Guard clauses keep the happy path clean
  if (isLoading) {
    return <div className="card card--skeleton" aria-busy="true" />;
  }

  if (error) {
    return (
      <div className="card card--error" role="alert">
        <p>{error}</p>
      </div>
    );
  }

  if (!data) {
    return null;
  }

  return (
    <div className="card">
      <h3 className="card__title">{data.title}</h3>
      <p className="card__value">{data.value.toLocaleString()}</p>
    </div>
  );
}

Use early returns for loading and error states rather than deeply nested ternary expressions. The happy path should read cleanly from top to bottom. Think of it like a guard clause pattern — handle the edge cases first, then focus on the main logic.

Pitfalls That Will Bite You Early#

Using array index as a list key. It will work until you add sorting or deletion, then you will get mysterious UI bugs. Always use a stable ID.

Prop drilling without thinking. Passing a prop through three layers of components to get to the one that needs it is a sign you should reconsider your component tree. We will cover context in a later post.

Mutating props. Props are read-only. React does not enforce this at runtime in most cases, but mutating props produces undefined behavior. If you need to derive new values from props, compute them in the render function.

Giant components. If your component is over 150 lines, it is probably doing too many things. Extract logical sections into sub-components — even if those sub-components only live in the same file. I once inherited a 900-line component that rendered an entire dashboard — header, sidebar, three charts, and a data table. Took two days to untangle. Thirty minutes of upfront structure would have prevented it.

That covers the foundation — tooling, JSX, components, and the patterns that will not embarrass you in a code review. Next time I want to talk about the thing that made me finally stop reaching for class components: hooks, and the mental shift they require.

React in 2020: Getting Started the Right Way | Blog