TypeScript with React: Industry Standard UI

TypeScript with React: Industry Standard UI

TypeScript and React are a match made in heaven. React handles the UI components, while TypeScript ensures the data flowing between those components is correct. This combination virtually eliminates "prop-drilling" errors and "missing property" crashes.


1. Typing Functional Components

In modern React, we use Functional Components. You can type them using React.FC (Functional Component) or by typing the props directly.

interface UserProfileProps {
    name: string;
    age: number;
    isAdmin?: boolean; // Optional
}

// Option A: Direct typing (Recommended for simplicity)
export const UserProfile = ({ name, age, isAdmin }: UserProfileProps) => {
    return <div>{name} - {age}</div>;
};

// Option B: React.FC (Includes 'children' by default in older versions)
const Card: React.FC<{ title: string }> = ({ title, children }) => (
    <div className="card">
        <h3>{title}</h3>
        {children}
    </div>
);

2. Typing Hooks

useState

TypeScript is usually good at inferring the type from the initial value. If the initial value is null or an object, you should provide a generic.

const [count, setCount] = useState(0); // Inferred as number
const [user, setUser] = useState<User | null>(null); // Generic type

useRef

Crucial for DOM elements or persisting values.

const inputRef = useRef<HTMLInputElement>(null);

const focusInput = () => {
    inputRef.current?.focus();
};

3. Handling Events

Typing events correctly gives you full IntelliSense for event properties (like e.target.value).

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
};

const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
};

4. Complex State with useReducer

For complex state logic, useReducer combined with Discriminated Unions is the ultimate pattern.

type Action = 
    | { type: "increment"; amount: number }
    | { type: "decrement" };

interface State { count: number; }

function reducer(state: State, action: Action): State {
    switch (action.type) {
        case "increment": return { count: state.count + action.amount };
        case "decrement": return { count: state.count - 1 };
        default: return state;
    }
}

5. Typing Children and Layouts

If you create layout components, you need to type the children prop.

  • React.ReactNode: Most flexible. Includes strings, numbers, elements, or arrays.
  • React.ReactElement: Only includes a single JSX element.
interface LayoutProps {
    children: React.ReactNode;
}

const Layout = ({ children }: LayoutProps) => (
    <main className="container">{children}</main>
);

6. Summary

  • Type your Props to define component interfaces.
  • Use Generics with Hooks when inference isn't enough.
  • React.ChangeEvent and other built-in types make event handling easy.
  • Discriminated Unions are perfect for reducers.