Skip to content

Development Frontend Typescript Guide

Mathew Storm edited this page Sep 26, 2025 · 1 revision

TypeScript in 30 Minutes - EduLite Guide

TypeScript helps us build reliable education software. This guide gets you productive quickly.

Why TypeScript for EduLite

When a student loses connectivity mid-assignment, or a teacher works on a 5-year-old phone, our code must work perfectly. TypeScript helps by:

  • Catching errors at build time - Not when students are trying to learn
  • Self-documenting code - Critical for our global volunteer team
  • Better IDE support - Autocomplete helps contributors work faster
  • API contract enforcement - Frontend and backend stay synchronized

Quick Start: Your First 5 Minutes

Basic Types

TypeScript adds types to JavaScript. Here's what you need most often:

// Primitives
let studentName: string = "Ahmed";
let grade: number = 85;
let isOnline: boolean = true;
let courseData: null = null;

// Arrays
let scores: number[] = [90, 85, 92];
let students: string[] = ["Sara", "John", "Fatima"];

// Objects - use interfaces
interface Student {
  id: number;
  name: string;
  email: string;
  isActive: boolean;
}

const student: Student = {
  id: 1,
  name: "Sara",
  email: "sara@example.com",
  isActive: true
};

Functions

Define what goes in and what comes out:

// Simple function
function calculateGrade(score: number, total: number): number {
  return (score / total) * 100;
}

// Arrow function
const formatName = (first: string, last: string): string => {
  return `${first} ${last}`;
};

// Optional parameters use ?
function greetStudent(name: string, title?: string): string {
  if (title) {
    return `Hello ${title} ${name}`;
  }
  return `Hello ${name}`;
}

// Void for no return value
function logActivity(activity: string): void {
  console.log(`Student activity: ${activity}`);
}

React Component Patterns (Next 10 Minutes)

Basic Component with Props

Here's how we converted our Button component:

import React, { ReactNode, MouseEventHandler } from "react";

// Define your props interface
interface ButtonProps {
  children: ReactNode;
  onClick?: MouseEventHandler<HTMLButtonElement>;
  type?: "primary" | "secondary" | "danger";
  size?: "sm" | "md" | "lg";
  disabled?: boolean;
  className?: string;
}

// Use the interface in your component
const Button: React.FC<ButtonProps> = ({
  children,
  onClick,
  type = "primary",
  size = "md",
  disabled = false,
  className = ""
}) => {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={className}
      // ... other props
    >
      {children}
    </button>
  );
};

export default Button;

Component with ForwardRef

For components that need refs (like our Input):

import React, { ChangeEvent, InputHTMLAttributes } from "react";

interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
  name: string;
  value: string;
  onChange: (e: ChangeEvent<HTMLInputElement>) => void;
  label?: string;
  error?: string;
}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ name, value, onChange, label, error, ...props }, ref) => {
    return (
      <div>
        {label && <label htmlFor={name}>{label}</label>}
        <input
          ref={ref}
          id={name}
          name={name}
          value={value}
          onChange={onChange}
          {...props}
        />
        {error && <span className="error">{error}</span>}
      </div>
    );
  }
);

Input.displayName = "Input";
export default Input;

Event Handlers

Common patterns for handling events:

// Form events
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
  e.preventDefault();
  // Handle form submission
};

// Input changes
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
  setValue(e.target.value);
};

// Click events
const handleClick = (e: MouseEvent<HTMLButtonElement>): void => {
  console.log("Button clicked");
};

State with TypeScript

// Simple state
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);

// Object state
interface FormData {
  username: string;
  email: string;
  password: string;
}

const [formData, setFormData] = useState<FormData>({
  username: "",
  email: "",
  password: ""
});

// Array state
interface Assignment {
  id: number;
  title: string;
  dueDate: string;
}

const [assignments, setAssignments] = useState<Assignment[]>([]);

Common Patterns Cheat Sheet (Next 10 Minutes)

API Response Types

Define what your backend returns:

// User data from backend
interface User {
  id: number;
  username: string;
  email: string;
  created_at: string;
  is_active: boolean;
}

// API response wrapper
interface ApiResponse<T> {
  data: T;
  status: number;
  message?: string;
}

// Usage
async function getUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

Custom Hooks

// Custom hook with TypeScript
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });

  const setValue = (value: T) => {
    setStoredValue(value);
    window.localStorage.setItem(key, JSON.stringify(value));
  };

  return [storedValue, setValue];
}

// Usage
const [theme, setTheme] = useLocalStorage<"light" | "dark">("theme", "light");

Utility Types

TypeScript provides helpful utility types:

// Partial - makes all properties optional
interface Student {
  name: string;
  email: string;
  grade: number;
}

type PartialStudent = Partial<Student>;
// Same as: { name?: string; email?: string; grade?: number; }

// Pick - select specific properties
type StudentName = Pick<Student, "name">;
// Same as: { name: string; }

// Omit - exclude specific properties
type StudentWithoutGrade = Omit<Student, "grade">;
// Same as: { name: string; email: string; }

// Record - create object type with specific keys
type Grades = Record<string, number>;
// Same as: { [key: string]: number; }
const grades: Grades = {
  "math": 90,
  "science": 85
};

Migration Guide (Next 5 Minutes)

Converting a Component from JSX to TSX

Step 1: Rename file from .jsx to .tsx

Step 2: Create interface from PropTypes:

// Before (PropTypes)
Component.propTypes = {
  title: PropTypes.string.isRequired,
  count: PropTypes.number,
  isActive: PropTypes.bool,
  items: PropTypes.arrayOf(PropTypes.string),
  onClick: PropTypes.func
};
// After (TypeScript)
interface ComponentProps {
  title: string;
  count?: number;
  isActive?: boolean;
  items?: string[];
  onClick?: () => void;
}

Step 3: Add types to component:

const Component: React.FC<ComponentProps> = ({ title, count = 0, isActive = false }) => {
  // Component logic
};

Step 4: Remove PropTypes import and validation

Step 5: Run npm run type-check to verify

Common Migration Patterns

PropTypes TypeScript
PropTypes.string string
PropTypes.number number
PropTypes.bool boolean
PropTypes.array any[] or specific like string[]
PropTypes.object object or specific interface
PropTypes.func () => void or specific signature
PropTypes.node ReactNode
PropTypes.element ReactElement
PropTypes.oneOf(['a', 'b']) 'a' | 'b'
PropTypes.arrayOf(PropTypes.string) string[]
.isRequired No ? in interface
Optional (no .isRequired) Add ? in interface

Troubleshooting Common Issues

Error: "Property does not exist on type"

Problem: TypeScript doesn't know about a property

// Error
const user = { name: "Ahmed" };
console.log(user.email); // Error: Property 'email' does not exist

Solution: Define the type

interface User {
  name: string;
  email?: string;
}
const user: User = { name: "Ahmed" };

Error: "Type 'undefined' is not assignable to type 'string'"

Problem: Value might be undefined

// Error
const name: string = localStorage.getItem("name"); // Could return null

Solution: Handle null/undefined

const name: string = localStorage.getItem("name") || "";
// Or
const name: string | null = localStorage.getItem("name");

Error: "Cannot find name 'React'"

Solution: Import React types

import React, { FC, ReactNode, MouseEvent } from "react";

Error: "JSX element implicitly has type 'any'"

Solution: Install React types

npm install --save-dev @types/react @types/react-dom

Resources for Learning More

Quick References

For EduLite Contributors

  • Check our converted components: Input.tsx and Button.tsx
  • Ask questions in Discord #frontend channel
  • Run npm run type-check to verify your types

Next Steps

  1. Try converting a simple component - Start with one that uses PropTypes
  2. Run type checking - npm run type-check to see if it works
  3. Ask for review - Post in Discord for feedback
  4. Help others - Share what you learn

Remember: TypeScript is a tool to help us build better software for students. If it's not helping, we're using it wrong.


This guide is maintained by the EduLite community. Found an error or have a better example? Please contribute!

Clone this wiki locally