React with TypeScript


React with TypeScript Interview with follow-up questions

1. What is TypeScript and why would you use it in a React application?

TypeScript is a statically typed superset of JavaScript that compiles to plain JS. It adds optional static types, interfaces, generics, enums, and richer tooling on top of everything JavaScript already does.

Why use it in a React app:

  • Catch bugs at compile time – typos, wrong prop shapes, null/undefined access, and bad API responses are caught before runtime.
  • Typed props and state – components document and enforce exactly what data they accept, preventing whole classes of integration errors.
  • Superb editor experience – autocomplete, inline docs, safe refactors, and "go to definition" across the codebase.
  • Self-documenting code – types act as living documentation that can't drift from the implementation.
  • Safer at scale – the larger the team/codebase, the more value typing adds.

React has first-class TypeScript support (typed hooks, JSX, event types), and the ecosystem ships types for most libraries. Modern React tooling (Vite, Next.js, React Router v7) scaffolds TypeScript by default, and .tsx is the norm for new projects.

↑ Back to top

Follow-up 1

How does TypeScript enhance code quality in a React application?

TypeScript enhances code quality in a React application by providing static type checking. This allows developers to catch errors and bugs at compile-time rather than runtime. TypeScript also provides better code navigation and autocompletion, making it easier to understand and maintain the codebase. Additionally, TypeScript supports modern JavaScript features and provides advanced type inference, which helps in writing cleaner and more concise code.

Follow-up 2

Can you explain how to define component props using TypeScript?

In TypeScript, component props can be defined using interfaces. An interface is a way to define the shape of an object. To define component props, you can create an interface that describes the expected props and their types. For example:

interface MyComponentProps {
  name: string;
  age: number;
}

const MyComponent: React.FC = ({ name, age }) => {
  return <div>{`My name is ${name} and I am ${age} years old.`}</div>;
};

In the above example, the MyComponentProps interface defines two props: name of type string and age of type number. The MyComponent component then uses this interface to specify the props it expects.

Follow-up 3

What are some challenges you might face when using TypeScript with React?

When using TypeScript with React, some challenges you might face include:

  1. Learning curve: TypeScript has a learning curve, especially if you are new to static typing and TypeScript concepts.
  2. Integration with existing JavaScript code: If you have an existing React codebase written in JavaScript, migrating it to TypeScript can be challenging and time-consuming.
  3. Type definitions for third-party libraries: Some third-party libraries may not have complete or up-to-date TypeScript type definitions, which can make it harder to use them in a TypeScript project.
  4. Strict type checking: TypeScript's strict type checking can sometimes be too restrictive and require additional effort to satisfy the type system.
  5. Build time: TypeScript adds an extra build step to transpile TypeScript code to JavaScript, which can increase the build time of your React application.

2. How do you define a component's props using TypeScript?

Define an interface or type for the props and annotate the component's parameter. For function components you typically type the destructured props object directly:

interface ButtonProps {
  label: string;
  variant?: "primary" | "secondary"; // optional + union type
  disabled?: boolean;
  onClick: (e: React.MouseEvent) =&gt; void;
  children?: React.ReactNode;          // for nested content
}

function Button({ label, variant = "primary", onClick, children }: ButtonProps) {
  return {label}{children};
}

Notes:

  • Use ? for optional props; use union types ("primary" | "secondary") to constrain values.
  • For children, type them as React.ReactNode (or use React.PropsWithChildren to add children automatically).
  • Prefer annotating the props object over the older React.FC pattern — React.FC is fine but has fallen out of favor (it used to force an implicit children and complicates generics).
  • In React 19, ref is a normal prop, so you can include it in your props type without forwardRef.
↑ Back to top

Follow-up 1

What are the benefits of defining props with TypeScript?

Defining props with TypeScript provides several benefits:

  1. Type safety: TypeScript allows you to specify the types of props, which helps catch potential errors at compile-time and provides better code completion and documentation.
  2. Improved developer experience: With TypeScript, you can leverage the editor's autocompletion and type checking features to write components with confidence.
  3. Easier refactoring: When you define props using TypeScript, renaming or changing the type of a prop will automatically update all the places where it is used, making refactoring easier and less error-prone.

Follow-up 2

Can you give an example of a functional component with props defined using TypeScript?

Sure! Here's an example of a functional component with props defined using TypeScript:

import React from 'react';

interface GreetingProps {
  name: string;
}

const Greeting: React.FC = ({ name }) =&gt; {
  return <div>Hello, {name}!</div>;
};

export default Greeting;

In this example, the Greeting component expects a prop called name of type string. The prop is then used within the component to render a greeting message.

Follow-up 3

How would you handle optional props with TypeScript?

To handle optional props with TypeScript, you can use the ? modifier after the prop name in the interface definition. This indicates that the prop is optional. For example:

interface MyComponentProps {
  name: string;
  age?: number;
}

In this example, the age prop is optional, meaning it can be omitted when using the component. Within the component, you should handle the case where the optional prop is not provided, either by providing a default value or checking for its existence before using it.

3. How can you use TypeScript with React Hooks?

React's built-in hooks are fully typed; you add types where inference isn't enough.

  • useState – usually inferred; provide a generic for unions or initially-null values:

    const [count, setCount] = useState(0);            // number, inferred
    const [user, setUser] = useState(null);
    
  • useRef – type the element or value:

    const inputRef = useRef(null);
    
  • useReducer – type the state and a discriminated-union action:

    type Action = { type: "inc" } | { type: "set"; value: number };
    const [state, dispatch] = useReducer(reducer, { count: 0 } as State);
    
  • useContext – the value type comes from createContext(...).

  • Custom hooks – annotate parameters and return values; return a const tuple (as const) or object so call sites get precise types.

TypeScript then checks setter arguments, dispatch payloads, and ref usage, catching mistakes as you write. The @types/react package supplies all the hook and event types.

↑ Back to top

Follow-up 1

Can you give an example of a custom hook using TypeScript?

Sure! Here's an example of a custom hook called useCounter using TypeScript:

import { useState } from 'react';

interface CounterHookProps {
  initialCount: number;
}

const useCounter = ({ initialCount }: CounterHookProps) =&gt; {
  const [count, setCount] = useState(initialCount);

  const increment = () =&gt; {
    setCount(prevCount =&gt; prevCount + 1);
  };

  const decrement = () =&gt; {
    setCount(prevCount =&gt; prevCount - 1);
  };

  return {
    count,
    increment,
    decrement
  };
};

export default useCounter;

In this example, the useCounter hook takes an initialCount as a parameter and returns the count, increment, and decrement functions. The TypeScript interface CounterHookProps is used to define the type for the initialCount prop.

Follow-up 2

What are the benefits of using TypeScript with React Hooks?

Using TypeScript with React Hooks provides several benefits:

  1. Type Safety: TypeScript helps catch type errors during development, reducing the chances of runtime errors.
  2. Code Predictability: TypeScript provides better code predictability by enforcing strict typing rules, making it easier to understand and maintain the codebase.
  3. Improved Developer Experience: TypeScript provides better tooling support, including autocompletion, type inference, and documentation generation, which improves the overall developer experience.
  4. Enhanced Collaboration: TypeScript's static typing makes it easier for multiple developers to collaborate on a project, as it provides clear interfaces and contracts for components and hooks.

Follow-up 3

How does TypeScript help with predictability in your React application?

TypeScript helps with predictability in a React application by enforcing strict typing rules. By defining types for props, state, and other variables, TypeScript ensures that the code follows a specific structure and catches type errors during development. This helps in understanding and maintaining the codebase, as it provides clear interfaces and contracts for components and hooks. Additionally, TypeScript's static typing improves code predictability by making it easier to reason about the flow of data and the expected types of variables throughout the application.

4. What is the role of TypeScript in handling state in a React application?

TypeScript makes state handling safer and self-documenting by attaching precise types to every piece of state and the functions that update it:

  • Typed state shapeuseState(null) or a State interface for useReducer ensures only valid values are ever assigned, and forces you to handle the null/loading cases.
  • Typed updates – setters and reducer actions (often a discriminated union) are checked, so you can't dispatch a malformed action or set the wrong type.
  • Derived state & selectors – return types flow through useMemo, context, and selectors, so consumers know exactly what they get.
  • Refactor safety – change the state shape in one place and the compiler flags every component that must adapt.
  • Store integration – with Redux Toolkit/Zustand, the store, actions, and selectors are end-to-end typed (RootState, typed useAppSelector).

The net effect: many state-related bugs (undefined access, wrong shapes, impossible states) are caught at compile time, and editors give accurate autocomplete for state and its updaters.

↑ Back to top

Follow-up 1

How would you define the state of a component using TypeScript?

To define the state of a component using TypeScript, you can create an interface or a type that represents the shape of the state object. For example:

interface MyComponentState {
  count: number;
  name: string;
}

Then, you can use this interface or type as the type annotation for the state property in the component class:

class MyComponent extends React.Component&lt;{}, MyComponentState&gt; {
  state: MyComponentState = {
    count: 0,
    name: ''
  };
  // ...
}

In this example, the MyComponent class has a state object with two properties: count of type number and name of type string.

Follow-up 2

Can you give an example of a class component with state defined using TypeScript?

Sure! Here's an example of a class component with state defined using TypeScript:

import React from 'react';

interface CounterState {
  count: number;
}

class Counter extends React.Component&lt;{}, CounterState&gt; {
  state: CounterState = {
    count: 0
  };

  render() {
    const { count } = this.state;
    return (
      <div>
        <p>Count: {count}</p>
         this.setState({ count: count + 1 })}&gt;Increment
      </div>
    );
  }
}

In this example, the Counter class component has a state object with a single property count of type number. The render method displays the current count and a button to increment the count when clicked.

Follow-up 3

How does TypeScript help in managing complex state in a React application?

TypeScript helps in managing complex state in a React application by providing type safety and better tooling support. With TypeScript, you can define complex state objects with nested properties and ensure that the correct types are used throughout your codebase. This helps in catching potential errors at compile-time and provides better code completion and documentation. Additionally, TypeScript allows you to define interfaces or types for the state, making it easier to understand and maintain the structure of the state object.

5. How can TypeScript be used with Redux in a React application?

With Redux Toolkit (the recommended approach), Redux is essentially typed for you with minimal effort:

  1. Install @reduxjs/toolkit and react-redux (RTK ships its own types).
  2. Type slicescreateSlice infers action types from your reducers; annotate the slice's initialState: ts interface CounterState { value: number } const slice = createSlice({ name: "counter", initialState: { value: 0 } as CounterState, reducers: { incrementBy: (s, a: PayloadAction) =&gt; { s.value += a.payload; } }, });
  3. Derive store types from the store itself: ts export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch;
  4. Create typed hooks so components don't re-annotate: ts export const useAppDispatch = () =&gt; useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector;

Now useAppSelector((s) =&gt; s.counter.value) is fully typed and dispatch(incrementBy(5)) is checked. PayloadAction types action payloads, and RTK Query generates typed hooks for API calls. This gives end-to-end type safety with almost no manual action-type boilerplate.

↑ Back to top

Follow-up 1

Can you explain how to define Redux actions and reducers using TypeScript?

To define Redux actions and reducers using TypeScript, you can follow these steps:

  1. Define your action types as string constants. This helps avoid typos and provides better autocompletion in IDEs.
// actions.ts
export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
  1. Create action interfaces or types that define the shape of your actions. This includes the type property and any additional payload.
// actions.ts
export interface AddTodoAction {
  type: typeof ADD_TODO;
  payload: string;
}

export interface RemoveTodoAction {
  type: typeof REMOVE_TODO;
  payload: number;
}
  1. Define your action creators. These are functions that return the action objects.
// actions.ts
export const addTodo = (text: string): AddTodoAction =&gt; ({
  type: ADD_TODO,
  payload: text
});

export const removeTodo = (id: number): RemoveTodoAction =&gt; ({
  type: REMOVE_TODO,
  payload: id
});
  1. Create your reducers using TypeScript. Define the initial state and handle different action types.
// reducers.ts
import { ADD_TODO, REMOVE_TODO } from './actions';

export interface TodoState {
  todos: string[];
}

const initialState: TodoState = {
  todos: []
};

export const todoReducer = (state = initialState, action: AddTodoAction | RemoveTodoAction): TodoState =&gt; {
  switch (action.type) {
    case ADD_TODO:
      return {
        todos: [...state.todos, action.payload]
      };
    case REMOVE_TODO:
      return {
        todos: state.todos.filter((_, index) =&gt; index !== action.payload)
      };
    default:
      return state;
  }
};

By using TypeScript, you can ensure type safety and catch errors during development. This leads to more maintainable and reliable Redux code.

Follow-up 2

What are the benefits of using TypeScript with Redux?

Using TypeScript with Redux provides several benefits:

  1. Static type checking: TypeScript enforces type safety, catching errors at compile-time rather than runtime. This helps prevent common mistakes and improves code reliability.
  2. Improved code maintainability: TypeScript allows you to define interfaces and types for your Redux state, actions, and reducers. This provides better documentation and makes it easier to understand and modify the codebase.
  3. Enhanced developer experience: TypeScript provides better code completion, navigation, and refactoring tools in modern IDEs. This improves productivity and reduces development time.
  4. Better collaboration: TypeScript's static types make it easier for multiple developers to work on the same codebase. It helps ensure consistent coding practices and reduces the likelihood of introducing bugs.

Overall, using TypeScript with Redux leads to more robust, maintainable, and scalable applications.

Follow-up 3

How does TypeScript help in maintaining a large Redux store?

TypeScript helps in maintaining a large Redux store in the following ways:

  1. Type safety: TypeScript enforces type checking, ensuring that the correct types are used throughout the codebase. This helps catch errors early and prevents unexpected behavior.
  2. Code navigation and documentation: TypeScript allows you to define interfaces and types for your Redux state, actions, and reducers. This provides better code navigation and documentation, making it easier to understand and modify the codebase.
  3. Refactoring support: TypeScript provides powerful refactoring tools in modern IDEs. This makes it easier to rename variables, extract functions, and perform other code transformations without introducing bugs.
  4. Improved collaboration: TypeScript's static types make it easier for multiple developers to work on the same codebase. It helps ensure consistent coding practices and reduces the likelihood of introducing bugs.

By leveraging TypeScript's features, maintaining a large Redux store becomes more manageable and less error-prone.

Live mock interview

Mock interview: React with TypeScript

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.