React: From Components to Production · Components and JSX
TypeScript in React for JavaScript Developers
Read and write the course's TSX examples if you know JavaScript: what the types add, how to type props, state, events and refs, and how to adopt TypeScript gradually.
This course writes examples in TypeScript (.tsx files) because that is how most new React projects start and what React's own documentation recommends for larger apps. If you know JavaScript, you already know almost everything in a TSX file: TSX is JSX plus type annotations. Delete the types and you have valid JavaScript. In the first module every example has a JavaScript tab so you can compare the two; after that, read the types as documentation of what a component expects.
import { useRef, useState, type ChangeEvent } from "react";
type SearchBoxProps = {
placeholder?: string; // optional prop
onSearch: (query: string) => void;
};
export function SearchBox({ placeholder = "Search", onSearch }: SearchBoxProps) {
const [query, setQuery] = useState(""); // inferred as string
const [results, setResults] = useState<string[]>([]); // empty array needs a type
const inputRef = useRef<HTMLInputElement>(null); // DOM ref
function handleChange(e: ChangeEvent<HTMLInputElement>) {
setQuery(e.target.value);
}
return (
<form onSubmit={(e) => { e.preventDefault(); onSearch(query); }}>
<input ref={inputRef} value={query} onChange={handleChange} placeholder={placeholder} />
<button type="submit">Go</button>
<p>{results.length} results</p>
</form>
);
}
import { useRef, useState } from "react";
export function SearchBox({ placeholder = "Search", onSearch }) {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const inputRef = useRef(null);
function handleChange(e) {
setQuery(e.target.value);
}
return (
<form onSubmit={(e) => { e.preventDefault(); onSearch(query); }}>
<input ref={inputRef} value={query} onChange={handleChange} placeholder={placeholder} />
<button type="submit">Go</button>
<p>{results.length} results</p>
</form>
);
}
What the types add
- Props types (
type SearchBoxProps = {...}) describe what a component accepts. The editor autocompletes them and flags a missing or misspelled prop before you run the app. - State is usually inferred from the initial value. Add a type when the initial value doesn't say enough:
useState<string[]>([])oruseState<User | null>(null). - Events have types such as
ChangeEvent<HTMLInputElement>orFormEvent<HTMLFormElement>; inline handlers (onChange={(e) => ...}) infer them for you. - Refs to DOM nodes are
useRef<HTMLInputElement>(null). !(as indocument.getElementById("root")!) tells TypeScript "this is not null".ascasts. Use both sparingly.
What you can ignore at first
Generic components, utility types (Partial, Omit, ComponentProps<"button">) and strict-mode compiler settings can wait. You can follow every lesson by treating the types as comments that the editor checks.
Choosing JavaScript or TypeScript
- New project:
npm create vite@latest my-app -- --template react-ts(TypeScript) or--template react(JavaScript). Frameworks such as Next.js ask during setup. - Existing JavaScript app: adopt gradually. Add a
tsconfig.jsonwithallowJs, rename files to.tsxone at a time, and optionally type-check plain JS withcheckJsand JSDoc comments. Nothing forces an all-at-once rewrite. - Plain JavaScript is fine for prototypes, small widgets and teams not ready to adopt TypeScript. TypeScript pays off as the codebase and team grow: safer refactors, self-documenting props and fewer runtime surprises.
Common pitfalls
- Fighting the compiler with
anyeverywhere: you lose the benefits and keep the cost. Preferunknownplus a check, or a proper type. - Thinking types are checked at runtime: they are erased at build time. Data from APIs still needs validation (for example with Zod).
- Typing everything explicitly: let inference work for state, return values and inline handlers.
Key points
- TSX = JSX + types; remove the types and it is JavaScript
- Type props; let state and handlers infer where possible
- Adopt TypeScript gradually with
allowJs/checkJs - Types disappear at runtime; validate external data
Further reading: react.dev: Using TypeScript, TypeScript handbook: Migrating from JavaScript
Practice questions
Is a .tsx file valid JavaScript if you remove the type annotations?
Answer
Yes. TSX is JSX plus type annotations. TypeScript erases types at build time, so a component without its annotations is ordinary JSX that runs the same way.
When do you need to give useState an explicit type?
Answer
When the initial value doesn't describe all the values the state will hold, for example an empty array (useState<string[]>([])) or a value that starts as null (useState<User | null>(null)). Otherwise TypeScript infers the type from the initial value.
How would you move an existing JavaScript React app to TypeScript without a big rewrite?
Answer
Add a tsconfig.json with allowJs so .js/.jsx and .ts/.tsx files compile together, convert files one at a time starting with leaf components and shared utilities, optionally use checkJs with JSDoc types for files not yet converted, and tighten compiler options (strict) as coverage grows.
Do TypeScript types protect you from bad data returned by an API?
Answer
No. Types are erased at runtime, so a response shaped differently from your type is not detected. Validate external data at the boundary (for example with Zod) and derive the TypeScript type from the schema.
Open in the app to mark lessons complete, take notes and track your progress.