React Ts Mastering

By Zain Ali

"Master advanced React with TypeScript by learning essential tools and best practices."

October 2, 2024

React Ts Mastering

Mastering Advanced React with TypeScript: Tools, Packages, and Best Practices

React with TypeScript is a powerful combination for building scalable and type-safe applications. This guide covers advanced concepts, essential tools, recommended packages, best practices, and tips to help you write clean, reusable, and efficient code. Whether you're managing state, organizing folders, or creating a clear structure, these strategies will elevate your skills.


1. Setting Up the Project Structure

To keep your code organized and maintainable, start with a well-structured folder setup:

src/
|-- assets/           // Static files (images, fonts, etc.)
|-- components/       // Reusable UI components
|-- hooks/            // Custom React hooks
|-- layouts/          // Page layouts
|-- pages/            // Page components
|-- services/         // API calls or external service integrations
|-- store/            // State management (e.g., Redux, Zustand)
|-- types/            // TypeScript types and interfaces
|-- utils/            // Utility functions
|-- App.tsx           // Main App component
|-- index.tsx         // Entry point
  • Naming Conventions: Use PascalCase for component files (MyComponent.tsx), camelCase for hooks (useCustomHook.ts), and snake_case for utility functions (format_date.ts).
  • Location of Folders: Place each folder directly under src/ to keep everything modular and easy to locate.

2. Essential Tools and Packages for React with TypeScript

For an advanced React + TypeScript project, consider these essential packages:

  • State Management:

    • Redux Toolkit: A powerful, widely-used state management library that simplifies Redux setup.
    • Zustand: Lightweight, simpler state management for smaller projects or components.
    • React Query: A great library for managing server-state caching, asynchronous data fetching, and caching.
  • Styling:

    • Styled Components or Emotion: CSS-in-JS libraries with TypeScript support, making it easy to manage scoped, dynamic styles.
  • Form Handling:

    • React Hook Form: Efficient form management with TypeScript support, built for validation and ease of use.
  • Routing:

    • React Router: TypeScript-friendly routing library to manage navigation and routes in a React app.
  • Utility Libraries:

    • Axios: TypeScript-friendly HTTP client for handling API requests.
    • date-fns or Moment.js: For date manipulation and formatting.

3. Best Practices for Writing Reusable and Simple Code

a. Type-Driven Development

Use TypeScript's strong typing to make your code safe and predictable. Define types and interfaces to ensure consistency across components.

// types/User.ts
export interface User {
  id: number;
  name: string;
  email: string;
}
// components/UserCard.tsx
import { User } from '../types/User';
 
interface UserCardProps {
  user: User;
}
 
const UserCard: React.FC<UserCardProps> = ({ user }) => (
  <div>
    <h2>{user.name}</h2>
    <p>{user.email}</p>
  </div>
);
b. Component Reusability

Avoid redundant code by creating reusable components. Use props and composition to pass down data and keep components modular.

  • Reusable Button Component:

    // components/ui/Button.tsx
    import React from 'react';
     
    interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
      label: string;
    }
     
    const Button: React.FC<ButtonProps> = ({ label, ...props }) => (
      <button {...props}>{label}</button>
    );
     
    export default Button;
  • Tip: Keep your components "dumb" by making them as stateless as possible and managing complex logic elsewhere, such as in hooks or parent components.

c. Custom Hooks

Custom hooks allow you to encapsulate and reuse complex logic across components.

// hooks/useFetchUser.ts
import { useState, useEffect } from 'react';
import { User } from '../types/User';
import axios from 'axios';
 
const useFetchUser = (id: number) => {
  const [user, setUser] = useState<User | null>(null);
 
  useEffect(() => {
    axios.get<User>(`/api/users/${id}`).then((response) => setUser(response.data));
  }, [id]);
 
  return user;
};
 
export default useFetchUser;

4. State Management and Caching

a. Redux Toolkit with TypeScript

Redux Toolkit offers a simplified setup for Redux. Here’s a quick example:

// store/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { User } from '../types/User';
 
interface UserState {
  currentUser: User | null;
}
 
const initialState: UserState = { currentUser: null };
 
const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUser(state, action: PayloadAction<User>) {
      state.currentUser = action.payload;
    },
  },
});
 
export const { setUser } = userSlice.actions;
export default userSlice.reducer;
b. React Query for Caching

React Query is ideal for caching server-side state and handling asynchronous data.

// components/UserProfile.tsx
import { useQuery } from 'react-query';
import axios from 'axios';
import { User } from '../types/User';
 
const fetchUser = async (id: number) => {
  const { data } = await axios.get<User>(`/api/users/${id}`);
  return data;
};
 
const UserProfile: React.FC<{ userId: number }> = ({ userId }) => {
  const { data: user } = useQuery(['user', userId], () => fetchUser(userId));
 
  if (!user) return <div>Loading...</div>;
 
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
};

5. File and Folder Naming Conventions

  • Components: Use PascalCase (e.g., UserCard.tsx).
  • Types and Interfaces: Use PascalCase (e.g., User.ts in the types/ folder).
  • Utilities and Hooks: Use camelCase (e.g., useFetchUser.ts in the hooks/ folder).
Example of a Well-Organized Component Folder
components/
|-- ui/
|   |-- Button.tsx
|   |-- Modal.tsx
|-- cards/
|   |-- UserCard.tsx
|-- profile/
|   |-- UserProfile.tsx

6. Performance Optimization Tips

  • Lazy Loading Components: Use React’s React.lazy() to load components on demand, reducing initial load time.

    const UserProfile = React.lazy(() => import('./UserProfile'));
  • Memoization: Use React.memo and useCallback to avoid unnecessary re-renders.

    import React, { memo } from 'react';
     
    const ExpensiveComponent = memo(({ value }: { value: number }) => {
      console.log('Rendered');
      return <div>{value}</div>;
    });
  • Dependency Array: Always pass dependency arrays in useEffect and useCallback hooks to control re-renders precisely.


7. Best Practices for Code Simplicity and Reusability

  • Use Type Aliases and Enums: Define custom types and enums to make code more readable and type-safe.

    type Status = 'loading' | 'success' | 'error';
     
    enum Roles {
      ADMIN = 'ADMIN',
      USER = 'USER',
    }
  • Avoid "Prop Drilling": For deeply nested components, consider using Context API or a state management solution instead of passing props down multiple levels.

  • DRY Principle: “Don’t Repeat Yourself” — consolidate reusable logic into utility functions or custom hooks.


Conclusion

By applying these advanced practices in React and TypeScript, you’ll create applications that are highly maintainable, performant, and scalable. A strong foundation with tools like Redux Toolkit, React Query, CSS-in-JS, and a solid project structure can make a significant impact on your development process. Remember, every project is unique, so adapt these guidelines to fit your needs and continue refining your practices as you grow.

Happy coding!