React Class Components, Hooks, and State Management Deep Dive

React applications rely on two primary component paradigms: class-based and functional. Understanding how state, props, lifecycle, and side effects are managed across both is essential for building robust, maintainable UIs.

Class Component State Management

In class components, internal state is declared as an instance property—this.state. It must be initialized either in the constructor or using class field syntax:

import React, { Component } from 'react';

class Counter extends Component {
  // Preferred modern initialization
  state = {
    value: 0,
    label: 'React'
  };

  render() {
    const { value, label } = this.state;
    return (
      <div>
        <h3>Counter</h3>
        <p>Current: {value} — {label}</p>
      </div>
    );
  }
}

export default Counter;

Updating State Safely

Direct mutation of this.state does not trigger re-renders. Instead, use this.setState(), which schedules an update and merges the provided object into the current state:

increment = () => {
  this.setState(prevState => ({
    value: prevState.value + 1
  }));
};

This functional form ensures correctness when multiple updates occur in quick succession.

Handling Event Callback Context

Event handlers like onClick are invoked with this bound to the DOM element—not the component instance. To preserve context, three common patterns exist:

  • Arrow method definition (recommended for simplicity): handleClick = () => { ... }
  • Inline arrow wrapper: <button onClick={() => this.handleClick()}>
  • Binding in render (less efficient): <button onClick={this.handleClick.bind(this)}>

Arrow methods avoid runtime binding overhead and are ideal for most cases.

Props: Unidirectional Data Flow

Props are read-only inputs passed from parent to child components. They enable composition without side effects:

// Parent
function App() {
  const [score, setScore] = useState(15);
  return <ScoreDisplay score={score} title="Game Score" />;
}

// Child
function ScoreDisplay({ score, title }) {
  return (
    <div>
      <h4>{title}</h4>
      <p>Points: {score}</p>
    </div>
  );
}

To pass data upward, parents supply callback functions via props. The child invokes them with arguments, enabling controlled state propagation.

Type Safety with PropTypes

For runtime validation, define prop expectations using PropTypes:

import PropTypes from 'prop-types';

ScoreDisplay.propTypes = {
  score: PropTypes.number.isRequired,
  title: PropTypes.string
};

ScoreDisplay.defaultProps = {
  title: 'Default Score'
};

Functional Components and Hooks

Modern React favors functional components paired with built-in hooks for managing state, effects, and references.

useState Hook

Replaces class state with a declarative, composable API:

import { useState } from 'react';

function ToggleButton() {
  const [isActive, setIsActive] = useState(false);

  const toggle = () => setIsActive(prev => !prev);

  return (
    <button onClick={toggle} className={isActive ? 'active' : ''}>
      {isActive ? 'Deactivate' : 'Activate'}
    </button>
  );
}

useEffect Hook

Unifies lifecycle logic—replacing componentDidMount, componentDidUpdate, and componentWillUnmount:

import { useEffect, useState } from 'react';

function DataFetcher({ userId }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isMounted = true;

    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(result => {
        if (isMounted) setData(result);
      });

    return () => {
      isMounted = false; // Cleanup to prevent state updates on unmounted components
    };
  }, [userId]); // Re-run only when userId changes

  return <div>{data?.name || 'Loading...' }</div>;
}

useRef and Imperative Interactions

useRef creates a mutable object that persists across renders. It’s commonly used to access DOM nodes or store mutable values without triggering re-renders:

import { useRef, useEffect } from 'react';

function FocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} placeholder="Auto-focused" />;
}

Component Communication Patterns

Beyond props, React supports several inter-component communication strategies:

  • Context API: For deeply nested data sharing without prop drilling.
  • Custom Events / PubSub: Decoupled publish-subscribe via third-party libraries (e.g., pubsub-js).
  • State Lifting: Centralizing shared state in the nearest common ancestor.

Optimization Techniques

Performance-critical apps benefit from memoization primitives:

  • React.memo: Prevents unnecessary re-renders of functional components when props remain unchanged.
  • useCallback: Memoizes function definitions to avoid recreation on every render—crucial for stable dependencies in useEffect.
  • useMemo: Caches expensive computations until dependencies change.

Routing with react-router-dom

Client-side navigation is handled by react-router-dom. Key constructs include:

  • <BrowserRouter> for HTML5 history mode.
  • <Routes> and <Route> for path-to-component mapping.
  • <NavLink> for styled, active-aware navigation links.
  • useNavigate for imperative navigation with conditional logic.

State Management with Redux Toolkit

For complex global state, Redux Toolkit simplifies boilerplate through createSlice and configureStore:

import { createSlice, configureStore } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload);
    }
  }
});

const store = configureStore({
  reducer: {
    cart: cartSlice.reducer
  }
});

Combine with react-redux’s useSelector and useDispatch for seamless integration in components.

Thẻ: react react-hooks redux-toolkit react-router-dom context-api

Đăng vào ngày 7 tháng 7 lúc 17:51