Skip to content

Instantly share code, notes, and snippets.

@29Kumait
Created April 26, 2024 20:58
Show Gist options
  • Save 29Kumait/65e9aa6a5ce1b94d4c4ac2421aeeeb7d to your computer and use it in GitHub Desktop.
Save 29Kumait/65e9aa6a5ce1b94d4c4ac2421aeeeb7d to your computer and use it in GitHub Desktop.
TypeScript examples in React

.

1. Basic Types and Interfaces

Basic component that displays a greeting message. This will illustrate the use of basic types and interfaces for component props.

import React from 'react';

interface GreetingProps {
  name: string;
  isLoggedIn: boolean;
}

const Greeting: React.FC<GreetingProps> = ({ name, isLoggedIn }) => {
  return (
    <div>
      {isLoggedIn ? <h1>Hello, {name}!</h1> : <h1>Welcome, Guest!</h1>}
    </div>
  );
};

export default Greeting;

2. Using Generics with React Hooks

useState with generics to ensure type safety in state management:

import React, { useState } from 'react';

interface User {
  id: number;
  name: string;
}

const UserProfile: React.FC = () => {
  // Using generics to define the type of state
  const [user, setUser] = useState<User | null>(null);

  const login = () => {
    // Simulate a login
    setUser({ id: 1, name: 'John Doe' });
  };

  const logout = () => {
    setUser(null);
  };

  return (
    <div>
      {user ? (
        <div>
          <h1>Welcome back, {user.name}!</h1>
          <button onClick={logout}>Logout</button>
        </div>
      ) : (
        <button onClick={login}>Login</button>
      )}
    </div>
  );
};

export default UserProfile;

Function Components with TypeScript

Typing props in a function component using TypeScript. Button component that takes a few props:

import React from 'react';

interface ButtonProps {
  label: string;
  onClick: () => void; // Define the onClick as a function that returns nothing
  type?: 'button' | 'submit' | 'reset'; // Optional prop with default type
}

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

export default Button;

Using Hooks with TypeScript

TypeScript with React's useEffect and useState hooks to manage and display a list of items:

import React, { useState, useEffect } from 'react';

interface Item {
  id: number;
  name: string;
}

const ItemList: React.FC = () => {
  const [items, setItems] = useState<Item[]>([]); // An array of Item objects

  useEffect(() => {
    // Simulate fetching data
    const fetchedItems: Item[] = [
      { id: 1, name: 'Apple' },
      { id: 2, name: 'Banana' }
    ];
    setItems(fetchedItems);
  }, []); // Dependency array is empty, so this runs once on mount

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

export default ItemList;

Handling Events with TypeScript

Handling events like form submissions or input changes can benefit greatly from TypeScript's ability to define the types of event objects:

import React, { useState } from 'react';

const EmailSignup: React.FC = () => {
  const [email, setEmail] = useState('');

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setEmail(event.target.value);
  };

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    alert(`Signup for ${email}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Email:
        <input type="email" value={email} onChange={handleChange} />
      </label>
      <button type="submit">Sign Up</button>
    </form>
  );
};

export default EmailSignup;

In this example, React.ChangeEvent<HTMLInputElement> and React.FormEvent<HTMLFormElement> are used to type the event parameters, ensuring you have access to all appropriate properties and methods related to those events.

Using Context with TypeScript

Example: use the React Context API with TypeScript to manage and distribute application state.

import React, { createContext, useContext, useState, ReactNode } from 'react';

interface User {
  id: number;
  name: string;
}

interface UserContextType {
  user: User | null;
  setUser: React.Dispatch

React.SetStateAction<User | null>>;
}

const UserContext = createContext<UserContextType | undefined>(undefined);

const UserProvider: React.FC<{children: ReactNode}> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};

const useUser = () => {
  const context = useContext(UserContext);
  if (context === undefined) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
};

// Example of a component that uses the useUser hook
const UserProfile: React.FC = () => {
  const { user, setUser } = useUser();

  return (
    <div>
      {user ? (
        <h1>Welcome, {user.name}</h1>
      ) : (
        <button onClick={() => setUser({ id: 1, name: 'Jane Doe' })}>Login</button>
      )}
    </div>
  );
};

// Usage of UserProvider in the app
const App: React.FC = () => {
  return (
    <UserProvider>
      <UserProfile />
    </UserProvider>
  );
};

export default App;

Advanced Types: Utility Types

TypeScript's utility types in a React context to demonstrate their usefulness in state management and component prop manipulation.

import React from 'react';

interface Props {
  name: string;
  age: number;
  email: string;
}

// Using Partial to make all properties optional
const updateUser = (user: Partial<Props>) => {
  console.log('Updating user:', user);
};

const UserComponent: React.FC = () => {
  return (
    <button onClick={() => updateUser({ name: 'Alice' })}>
      Update Name
    </button>
  );
};

export default UserComponent;

Complex State Management Using Reducers and TypeScript

Define a reducer using TypeScript for more complex state management scenarios, such as handling a shopping cart.

import React, { useReducer } from 'react';

interface CartItem {
  id: number;
  name: string;
  quantity: number;
}

type CartState = CartItem[];

type CartAction = 
  | { type: 'add'; item: CartItem }
  | { type: 'remove'; id: number }
  | { type: 'increase'; id: number }
  | { type: 'decrease'; id: number };

const cartReducer = (state: CartState, action: CartAction): CartState => {
  switch (action.type) {
    case 'add':
      return [...state, action.item];
    case 'remove':
      return state.filter(item => item.id !== action.id);
    case 'increase':
      return state.map(item => item.id === action.id ? { ...item, quantity: item.quantity + 1 } : item);
    case 'decrease':
      return state.map(item => item.id === action.id ? { ...item, quantity: Math.max(item.quantity - 1, 0) } : item);
    default:
      return state;
  }
};

const CartComponent: React.FC = () => {
  const [cart, dispatch] = useReducer(cartReducer, []);

  return (
    <div>
      {cart.map(item => (
        <div key={item.id}>
          <div>{item.name} - Quantity: {item.quantity}</div>
          <button onClick={() => dispatch({ type: 'increase', id: item.id })}>+</button>
          <button onClick={() => dispatch({ type: 'decrease', id: item.id })}>-</button>
          <button onClick={() => dispatch({ type: 'remove', id: item.id })}>Remove</button>
        </div>
      ))}
    </div>
  );
};

export default CartComponent;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment