-
Notifications
You must be signed in to change notification settings - Fork 20
Development Frontend Typescript Guide
TypeScript helps us build reliable education software. This guide gets you productive quickly.
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
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
};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}`);
}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;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;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");
};// 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[]>([]);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 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");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
};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
| 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 |
Problem: TypeScript doesn't know about a property
// Error
const user = { name: "Ahmed" };
console.log(user.email); // Error: Property 'email' does not existSolution: Define the type
interface User {
name: string;
email?: string;
}
const user: User = { name: "Ahmed" };Problem: Value might be undefined
// Error
const name: string = localStorage.getItem("name"); // Could return nullSolution: Handle null/undefined
const name: string = localStorage.getItem("name") || "";
// Or
const name: string | null = localStorage.getItem("name");Solution: Import React types
import React, { FC, ReactNode, MouseEvent } from "react";Solution: Install React types
npm install --save-dev @types/react @types/react-dom- TypeScript Handbook - Official documentation
- React TypeScript Cheatsheet - React-specific patterns
- TypeScript Playground - Try TypeScript in browser
- Check our converted components:
Input.tsxandButton.tsx - Ask questions in Discord #frontend channel
- Run
npm run type-checkto verify your types
- Try converting a simple component - Start with one that uses PropTypes
-
Run type checking -
npm run type-checkto see if it works - Ask for review - Post in Discord for feedback
- 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!