TypeScript with React

(Updated on )

TypeScript with React

TypeScript and React make a powerful combination for building robust web applications. In this guide, we’ll explore how to use TypeScript effectively with React.

Setting Up TypeScript with React

Installation

npx create-react-app my-app --template typescript

Basic Configuration

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  }
}

Typing React Components

Functional Components

interface ButtonProps {
  text: string;
  onClick: () => void;
  disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({ text, onClick, disabled }) => {
  return (
    <button onClick={onClick} disabled={disabled}>
      {text}
    </button>
  );
};

Class Components

interface CounterProps {
  initialCount: number;
}

interface CounterState {
  count: number;
}

class Counter extends React.Component<CounterProps, CounterState> {
  state: CounterState = {
    count: this.props.initialCount
  };

  increment = () => {
    this.setState(state => ({
      count: state.count + 1
    }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

Hooks with TypeScript

useState

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

useEffect

useEffect(() => {
  const fetchData = async () => {
    const response = await fetch('/api/data');
    const data: ApiResponse = await response.json();
    setData(data);
  };
  fetchData();
}, []);

useReducer

interface State {
  count: number;
}

type Action = 
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' };

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      return state;
  }
};

Context with TypeScript

interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = React.createContext<ThemeContextType | undefined>(undefined);

const ThemeProvider: React.FC = ({ children }) => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

Custom Hooks

interface UseWindowSize {
  width: number;
  height: number;
}

function useWindowSize(): UseWindowSize {
  const [size, setSize] = useState<UseWindowSize>({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

Best Practices

  1. Use proper type definitions for props and state
  2. Leverage TypeScript’s type inference
  3. Use interfaces for complex data structures
  4. Implement proper error handling
  5. Use type guards for runtime type checking

Next Steps

Happy coding!