Web UI: TypeScript & JavaScript Patterns

TypeScript Basics

TypeScript is a superset of JavaScript that adds static type checking. It helps catch errors before runtime and makes code more maintainable.

Why TypeScript with React?

  • Type Safety: Catch errors during development, not in production
  • Better IDE Support: Autocomplete, refactoring, and navigation
  • Self-Documenting: Types serve as documentation
  • Refactoring Confidence: Types help ensure changes don’t break code

Basic TypeScript Types:

// Primitive types
const name: string = "Alice";
const age: number = 25;
const isActive: boolean = true;

// Arrays
const numbers: number[] = [1, 2, 3];
const names: Array<string> = ["Alice", "Bob"];

// Objects
interface User {
  name: string;
  age: number;
  email?: string; // Optional property
}

const user: User = {
  name: "Alice",
  age: 25
};

TypeScript with React Props:

// Inline types
function Greeting({ name, age }: { name: string; age: number }) {
  return <h1>Hello, {name}! You are {age} years old.</h1>;
}

// Interface (recommended for reusable types)
interface GreetingProps {
  name: string;
  age: number;
  email?: string;
}

function Greeting({ name, age, email }: GreetingProps) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Age: {age}</p>
      {email && <p>Email: {email}</p>}
    </div>
  );
}

Type Inference:

TypeScript can often infer types automatically:

const count = 0; // TypeScript infers: number
const name = "Alice"; // TypeScript infers: string
const [value, setValue] = useState(0); // TypeScript infers: [number, React.Dispatch<React.SetStateAction<number>>]

Common JavaScript Patterns in React

Destructuring

Destructuring lets you extract values from arrays or properties from objects into distinct variables.

Object Destructuring:

// Instead of:
const name = user.name;
const age = user.age;

// Use destructuring:
const { name, age } = user;

// With renaming:
const { name: userName, age: userAge } = user;

// With default values:
const { name, age = 0 } = user;

// In function parameters (very common in React):
function UserCard({ name, age, email }: { name: string; age: number; email?: string }) {
  return <div>{name}, {age}</div>;
}

Array Destructuring:

// useState returns an array, so we destructure it:
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [user, setUser] = useState(null);

// Destructuring arrays:
const [first, second, third] = [1, 2, 3];
const [first, ...rest] = [1, 2, 3, 4]; // first = 1, rest = [2, 3, 4]

Spread Operator

The spread operator (...) expands arrays or objects into individual elements.

Spreading Arrays:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// Adding to an array:
const newArray = [...arr1, 4]; // [1, 2, 3, 4]

Spreading Objects:

const user = { name: "Alice", age: 25 };
const updatedUser = { ...user, age: 26 }; // { name: "Alice", age: 26 }

// Merging objects:
const defaults = { theme: "light", lang: "en" };
const settings = { ...defaults, theme: "dark" }; // { theme: "dark", lang: "en" }

// In React, updating state immutably:
const [user, setUser] = useState({ name: "Alice", age: 25 });
setUser({ ...user, age: 26 }); // Update age while keeping other properties

Spreading Props:

const props = { name: "Alice", age: 25, email: "alice@example.com" };
<UserCard {...props} /> // Spreads all props to UserCard

Conditional Rendering

React components can conditionally render content based on state or props.

Ternary Operator:

function Greeting({ isLoggedIn }: { isLoggedIn: boolean }) {
  return (
    <div>
      {isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please log in</h1>}
    </div>
  );
}

// With multiple conditions:
function Status({ count }: { count: number }) {
  return (
    <div>
      {count === 0 ? (
        <p>No items</p>
      ) : count === 1 ? (
        <p>1 item</p>
      ) : (
        <p>{count} items</p>
      )}
    </div>
  );
}

Logical AND (&&) Operator:

function UserProfile({ user }: { user: User | null }) {
  return (
    <div>
      {user && <div>Welcome, {user.name}!</div>}
      {/* Only renders if user is truthy */}
    </div>
  );
}

// Multiple conditions:
function Dashboard({ user, isAdmin }: { user: User | null; isAdmin: boolean }) {
  return (
    <div>
      {user && <UserInfo user={user} />}
      {isAdmin && <AdminPanel />}
      {user && isAdmin && <AdminControls />}
    </div>
  );
}

Important Note: Be careful with && - if the left side is 0 or an empty string, React will render that value. Use ternary for numbers:

// ❌ Bad - renders "0" if count is 0
{count && <div>You have {count} items</div>}

// ✅ Good
{count > 0 && <div>You have {count} items</div>}
// or
{count ? <div>You have {count} items</div> : null}

Early Returns:

function UserProfile({ user }: { user: User | null }) {
  if (!user) {
    return <div>Loading...</div>;
  }
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Template Literals

Template literals use backticks and ${} for string interpolation:

const name = "Alice";
const greeting = `Hello, ${name}!`; // "Hello, Alice!"

// In JSX:
function Greeting({ firstName, lastName }: { firstName: string; lastName: string }) {
  return <h1>Hello, {`${firstName} ${lastName}`}!</h1>;
}

Arrow Functions

Arrow functions are commonly used in React for event handlers and callbacks:

// Regular function:
function handleClick() {
  console.log('Clicked');
}

// Arrow function:
const handleClick = () => {
  console.log('Clicked');
};

// In JSX:
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={handleClick}>Click me</button>

// With parameters:
<button onClick={(e) => handleClick(e)}>Click me</button>

Optional Chaining

Optional chaining (?.) safely accesses nested properties:

const user = { profile: { name: "Alice" } };

// Without optional chaining (error if profile is null):
const name = user.profile.name; // Error if profile is null/undefined

// With optional chaining:
const name = user.profile?.name; // Returns undefined if profile is null/undefined

// In React:
function UserCard({ user }: { user: User | null }) {
  return <div>{user?.profile?.name || "Unknown"}</div>;
}

Resources

Loading quiz...

UNC Asheville Department of Computer Science