Learning JS frameworks with me(part 5): React.js(2/3)- Advanced React Component Patterns & State Management

In Part 1, we covered React fundamentals. Now let's explore more advanced concepts that will elevate your React applications to professional standards. Modern React development has evolved significantly, with hooks replacing class components and custom patterns emerging to solve complex state management challenges. Component Lifecycle and useEffect Patterns In modern React, the class component lifecycle methods have been consolidated into the useEffect hook. This powerful hook lets you perform side effects in function components. Basic useEffect Pattern useEffect(() => { // Code to run after render console.log('Component rendered'); // Optional cleanup function return () => { console.log('Component will unmount or dependencies changed'); }; }, [/* dependencies */]); Common useEffect Patterns Run once on mount (equivalent to componentDidMount): useEffect(() => { fetchData(); }, []); // Empty dependency array Run when specific props/state change: useEffect(() => { console.log('userId changed'); fetchUserData(userId); }, [userId]); // Only re-run when userId changes Cleanup pattern (equivalent to componentWillUnmount): useEffect(() => { const subscription = subscribeToData(); return () => { subscription.unsubscribe(); }; }, []); Best Practices Avoid the exhaustive-deps ESLint warning by including all dependencies your effect uses Split effects by concern rather than lifecycle Use cleanup functions to prevent memory leaks Consider using useLayoutEffect for DOM measurements that need to be synchronized Context API for State Sharing React Context provides a way to share values between components without having to explicitly pass props through every level of the component tree. Creating and Using Context // Create context const ThemeContext = createContext('light'); // Provider component function App() { const [theme, setTheme] = useState('light'); return ( ); } // Consumer component using useContext hook function ThemedButton() { const { theme, setTheme } = useContext(ThemeContext); return ( setTheme(theme === 'light' ? 'dark' : 'light')} > Toggle Theme ); } Context Best Practices Create separate contexts for different domains of your application Keep context values focused on specific concerns Consider performance implications – all components using a context re-render when the context value changes Use memoization with useMemo to prevent unnecessary re-renders React Router for Navigation React Router is the standard library for routing in React applications, allowing you to build single-page applications with navigation. Basic Setup import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; function App() { return ( Home About Dashboard ); } Advanced Routing Patterns Nested Routes: Private Routes: function PrivateRoute({ children }) { const { isAuthenticated } = useAuth(); return isAuthenticated ? children : ; } // Usage useParams for Dynamic Routes: function ProductDetail() { const { id } = useParams(); const [product, setProduct] = useState(null); useEffect(() => { fetchProduct(id).then(setProduct); }, [id]); if (!product) return Loading...; return {product.name}; } Form Handling and Validation Forms are a core part of web applications, and React offers several approaches to handle them effectively. Controlled Components function SignupForm() { const [formData, setFormData] = useState({ email: '', password: '', }); const [errors, setErrors] = useState({}); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const validate = () => { const newErrors = {}; if (!formData.email) { newErrors.email = 'Email is required'; } else if (!/\S+@\S+\.\S+/.test(formData.email)) { newErrors.email = 'Email is invalid'; } if (!formData.password) { newErrors.password = 'Password is required'; } else if (formData.password.length { e.preventDefault(); if (validate()) { // Submit form data console.log('Form submitted:', formData); } }; return ( Email {errors.email && {errors.email}} Password {errors.password && {errors.password}} Sign Up ); } Form Libraries For complex forms, libraries like Formik and React Hook Form can simplify development: Using React Hook Form: import { useForm } from 'react-hook-form'; function SignupForm() { const { register,

Apr 6, 2025 - 06:56
 0
Learning JS frameworks with me(part 5): React.js(2/3)- Advanced React Component Patterns & State Management

In Part 1, we covered React fundamentals. Now let's explore more advanced concepts that will elevate your React applications to professional standards. Modern React development has evolved significantly, with hooks replacing class components and custom patterns emerging to solve complex state management challenges.

Component Lifecycle and useEffect Patterns

In modern React, the class component lifecycle methods have been consolidated into the useEffect hook. This powerful hook lets you perform side effects in function components.

Basic useEffect Pattern

useEffect(() => {
  // Code to run after render
  console.log('Component rendered');

  // Optional cleanup function
  return () => {
    console.log('Component will unmount or dependencies changed');
  };
}, [/* dependencies */]);

Common useEffect Patterns

  1. Run once on mount (equivalent to componentDidMount):
useEffect(() => {
  fetchData();
}, []); // Empty dependency array
  1. Run when specific props/state change:
useEffect(() => {
  console.log('userId changed');
  fetchUserData(userId);
}, [userId]); // Only re-run when userId changes
  1. Cleanup pattern (equivalent to componentWillUnmount):
useEffect(() => {
  const subscription = subscribeToData();

  return () => {
    subscription.unsubscribe();
  };
}, []);

Best Practices

  • Avoid the exhaustive-deps ESLint warning by including all dependencies your effect uses
  • Split effects by concern rather than lifecycle
  • Use cleanup functions to prevent memory leaks
  • Consider using useLayoutEffect for DOM measurements that need to be synchronized

Context API for State Sharing

React Context provides a way to share values between components without having to explicitly pass props through every level of the component tree.

Creating and Using Context

// Create context
const ThemeContext = createContext('light');

// Provider component
function App() {
  const [theme, setTheme] = useState('light');

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

// Consumer component using useContext hook
function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button 
      style={{ background: theme === 'dark' ? '#333' : '#fff' }}
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
    >
      Toggle Theme
    button>
  );
}

Context Best Practices

  • Create separate contexts for different domains of your application
  • Keep context values focused on specific concerns
  • Consider performance implications – all components using a context re-render when the context value changes
  • Use memoization with useMemo to prevent unnecessary re-renders

React Router for Navigation

React Router is the standard library for routing in React applications, allowing you to build single-page applications with navigation.

Basic Setup

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">HomeLink>
        <Link to="/about">AboutLink>
        <Link to="/dashboard">DashboardLink>
      nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/dashboard" element={<Dashboard />} />
      Routes>
    BrowserRouter>
  );
}

Advanced Routing Patterns

  1. Nested Routes:
<Routes>
  <Route path="/products" element={<Products />}>
    <Route path=":id" element={<ProductDetail />} />
    <Route path="new" element={<NewProduct />} />
  Route>
Routes>
  1. Private Routes:
function PrivateRoute({ children }) {
  const { isAuthenticated } = useAuth();

  return isAuthenticated ? children : <Navigate to="/login" />;
}

// Usage
<Route 
  path="/dashboard" 
  element={<PrivateRoute><Dashboard />PrivateRoute>} 
/>
  1. useParams for Dynamic Routes:
function ProductDetail() {
  const { id } = useParams();
  const [product, setProduct] = useState(null);

  useEffect(() => {
    fetchProduct(id).then(setProduct);
  }, [id]);

  if (!product) return <div>Loading...div>;

  return <div>{product.name}div>;
}

Form Handling and Validation

Forms are a core part of web applications, and React offers several approaches to handle them effectively.

Controlled Components

function SignupForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
  });
  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  const validate = () => {
    const newErrors = {};

    if (!formData.email) {
      newErrors.email = 'Email is required';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email is invalid';
    }

    if (!formData.password) {
      newErrors.password = 'Password is required';
    } else if (formData.password.length < 8) {
      newErrors.password = 'Password must be at least 8 characters';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    if (validate()) {
      // Submit form data
      console.log('Form submitted:', formData);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Emaillabel>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
        {errors.email && <span>{errors.email}span>}
      div>

      <div>
        <label>Passwordlabel>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
        {errors.password && <span>{errors.password}span>}
      div>

      <button type="submit">Sign Upbutton>
    form>
  );
}

Form Libraries

For complex forms, libraries like Formik and React Hook Form can simplify development:

Using React Hook Form:

import { useForm } from 'react-hook-form';

function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log('Form submitted:', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Emaillabel>
        <input
          {...register('email', { 
            required: 'Email is required',
            pattern: {
              value: /\S+@\S+\.\S+/,
              message: 'Email is invalid'
            }
          })}
        />
        {errors.email && <span>{errors.email.message}span>}
      div>

      <div>
        <label>Passwordlabel>
        <input
          type="password"
          {...register('password', {
            required: 'Password is required',
            minLength: {
              value: 8,
              message: 'Password must be at least 8 characters'
            }
          })}
        />
        {errors.password && <span>{errors.password.message}span>}
      div>

      <button type="submit">Sign Upbutton>
    form>
  );
}

Custom Hooks

Custom hooks allow you to extract component logic into reusable functions, promoting code reuse and separation of concerns.

Creating a Custom Hook

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    setLoading(true);
    fetch(url, { signal: controller.signal })
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        if (error.name !== 'AbortError') {
          setError(error);
          setLoading(false);
        }
      });

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>Loading...div>;
  if (error) return <div>Error: {error.message}div>;

  return (
    <div>
      <h2>{data.name}h2>
      <p>Email: {data.email}p>
    div>
  );
}

Common Custom Hook Patterns

  1. useLocalStorage - Persist state to localStorage:
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}
  1. useMediaQuery - Respond to media queries:
function useMediaQuery(query) {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const media = window.matchMedia(query);
    setMatches(media.matches);

    const listener = (event) => setMatches(event.matches);
    media.addEventListener('change', listener);

    return () => media.removeEventListener('change', listener);
  }, [query]);

  return matches;
}

// Usage
function ResponsiveComponent() {
  const isMobile = useMediaQuery('(max-width: 768px)');

  return (
    <div>
      {isMobile ? <MobileView /> : <DesktopView />}
    div>
  );
}

Modern React Development in 2025

The React ecosystem has continued to evolve, with several key trends dominating the landscape:

Server Components and React Server Components (RSC)

React Server Components allow rendering components on the server, with zero JavaScript sent to the client for server-only components. This provides:

  • Improved performance with reduced client-side JavaScript
  • Better SEO as content is rendered server-side
  • Access to backend resources directly from components
// A server component
import { db } from '../db'; // Direct backend access

// This component never sends JS to the client
async function ProductList() {
  const products = await db.query('SELECT * FROM products');

  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    div>
  );
}

Meta-Frameworks

Most React applications now use meta-frameworks that build on top of React:

  1. Next.js - The most popular React framework offering:

    • Server components
    • File-based routing
    • API routes
    • Server-side rendering and static generation
    • Built-in image optimization
  2. Remix - Focused on web fundamentals:

    • Nested routing
    • Server-rendered UI
    • Progressive enhancement
    • Error boundaries at route level

State Management Evolution

While Redux was once the standard, state management has diversified:

  1. React Query / TanStack Query - For server state management:

    • Caching
    • Background refetching
    • Mutation handling
    • Pagination and infinite scrolling
  2. Zustand - A lightweight alternative to Redux:

    • Simple API with hooks
    • No boilerplate
    • Middleware support
  3. Jotai/Recoil - Atomic state management:

    • Granular updates
    • Derived state
    • Shared state without context providers

Styling Approaches

Several approaches have gained prominence:

  1. Tailwind CSS - Utility-first CSS:

    • Component-level styling
    • No naming required
    • Consistency through constraints
  2. CSS Modules - Scoped CSS with no runtime cost:

    • Local scope by default
    • Composition
    • TypeScript support
  3. CSS-in-JS - Libraries like styled-components and emotion:

    • Dynamic styling
    • Theming
    • Component-based styling

TypeScript Integration

TypeScript has become the standard for React development:

type UserProps = {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
  onDelete?: (id: string) => void;
};

function User({ id, name, email, role, onDelete }: UserProps) {
  return (
    <div>
      <h3>{name}h3>
      <p>{email}p>
      <span>Role: {role}span>
      {onDelete && (
        <button onClick={() => onDelete(id)}>Deletebutton>
      )}
    div>
  );
}

Testing Best Practices

Modern React testing focuses on user behavior:

  1. React Testing Library - Testing from the user's perspective:

    • Queries that mirror how users find elements
    • Event firing
    • Accessibility checks
  2. Vitest - Fast testing framework:

    • Compatible with Jest API
    • Faster execution
    • Better HMR support

Performance Optimization

  1. Suspense and Concurrent Rendering:
   <Suspense fallback={<LoadingSpinner />}>
     <ProductDetails id={productId} />
   Suspense>
  1. Memoization with useMemo and useCallback:
   const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
   const memoizedCallback = useCallback(() => {
     doSomething(a, b);
   }, [a, b]);
  1. Virtualization for long lists:
   import { useVirtualizer } from '@tanstack/react-virtual';

   function VirtualList({ items }) {
     const rowVirtualizer = useVirtualizer({
       count: items.length,
       getScrollElement: () => parentRef.current,
       estimateSize: () => 35,
     });

     return (
       <div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
         <div
           style={{
             height: `${rowVirtualizer.getTotalSize()}px`,
             width: '100%',
             position: 'relative',
           }}
         >
           {rowVirtualizer.getVirtualItems().map((virtualRow) => (
             <div
               key={virtualRow.index}
               style={{
                 position: 'absolute',
                 top: 0,
                 left: 0,
                 width: '100%',
                 height: `${virtualRow.size}px`,
                 transform: `translateY(${virtualRow.start}px)`,
               }}
             >
               {items[virtualRow.index]}
             div>
           ))}
         div>
       div>
     );
   }

Conclusion

React continues to evolve with a focus on performance, developer experience, and component composition. By mastering these advanced patterns and staying current with ecosystem trends, you'll be well-equipped to build modern, maintainable React applications that scale with your needs.

The fundamental React philosophy remains: compose your UI from small, focused components and manage state predictably. However, the tools and techniques to achieve this have become more sophisticated, allowing for increasingly powerful and performant web applications.