React: Introduction to Hooks for Beginners

lukewanghanxiang

Luke

Posted on August 29, 2024

React: Introduction to Hooks for Beginners

What are React Hooks?

React Hooks are special functions that let you use state and other React features in functional components. Introduced in React 16.8, Hooks have fundamentally changed the way developers build components.

Hooks allow you to "hook into" React’s state and lifecycle features directly from functional components, which were previously only possible in class components.

Historical Background

  • Class Components Before Hooks: Before Hooks, React developers had to rely on class components to manage state and lifecycle methods. This often led to complex, unmanageable code, especially as components grew larger.

  • Why Hooks Were Introduced: Hooks were introduced to solve the common problems associated with class components, such as the difficulties in reusing logic, managing side effects, and dealing with complex lifecycle methods.

Why use hooks?

The Problems Hooks Solve

  • State Management in Functional Components: Previously, functional components were stateless. Hooks enabled them to manage state, making it easier to write simpler, more reusable code.

  • Reusing Logic: Class components made it difficult to reuse stateful logic across multiple components. With Hooks, you can easily extract and share logic through custom hooks.

  • Simplifying Complex Components: Hooks help reduce the complexity of components by eliminating the need for lifecycle methods that are spread out across different phases of a component’s life.

  • Cleaner and More Readable Code: With Hooks, there’s no need to deal with this keyword, binding issues, or long class hierarchies, making the code more readable and easier to maintain.

Commonly Used Hooks Brief Intro

useState

  • Concept: useState is the most basic Hook, allowing you to add state to functional components. It returns an array with the current state and a function to update it.

  • How It Works: When you call useState, React initializes state for the component and preserves it between re-renders.

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Use Cases: useState is used whenever you need to keep track of a value that can change over time, like form inputs, counters, toggles, etc.

useEffect

  • Concept: useEffect allows you to perform side effects in function components. It combines the functionality of componentDidMount, componentDidUpdate, and componentWillUnmount.

  • How It Works: useEffect runs after every render by default but can be controlled with a dependency array to run only when certain values change.

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]); // Only re-run the effect if count changes

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Use Cases: useEffect is perfect for fetching data, manually updating the DOM, setting up subscriptions, or cleaning up resources when a component unmounts.

useContext

  • Concept: useContext allows you to access context values directly in your functional components without needing to use the Consumer component.

  • How It Works: When you call useContext with a context object, React returns the current value of that context.

const ThemeContext = React.createContext('light');

function ThemeButton() {
  const theme = useContext(ThemeContext);

  return <button className={theme}>I am styled by theme context!</button>;
}

Enter fullscreen mode Exit fullscreen mode

Use Cases: useContext is used when you need to pass data deeply through the component tree without having to prop-drill, such as theme settings, authenticated user information, etc.

useReducer

  • Concept: useReducer is similar to useState, but more powerful in managing complex state logic. It is often used as an alternative to useState when dealing with complex state transitions.

  • How It Works: useReducer takes a reducer function and an initial state, returning the current state and a dispatch function.

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Use Cases: useReducer is ideal for managing complex state logic, such as when the state object has multiple sub-values or when the next state depends on the previous one.

useRef

  • Concept: useRef returns a mutable ref object whose .current property is initialized to the passed argument. It’s primarily used to access DOM elements directly.

  • How It Works: useRef doesn’t cause a re-render when the .current property changes, unlike useState.

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };

  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Use Cases: useRef is useful for accessing and interacting with DOM elements directly, as well as holding mutable values that don’t cause re-renders, like timers or IDs.

Limitations and Notice Points of Hooks Usage

Hooks Can Only Be Called in Function Components or Custom Hooks

  • Limitation: Hooks cannot be used in regular JavaScript functions. They can only be called within React's function components or custom Hook functions.

  • Reason: React relies on the order of Hook calls to correctly manage component state and side effects. If Hooks are used in regular functions, React won't be able to track these calls properly, leading to errors or unstable behavior.

// Correct
function MyComponent() {
  const [count, setCount] = useState(0);
  // Using useState is allowed
  return <div>{count}</div>;
}

// Incorrect
function myFunction() {
  const [count, setCount] = useState(0); // Error: Hooks cannot be used in regular functions
}

Enter fullscreen mode Exit fullscreen mode

Hooks Can Only Be Called at the Top Level

  • Limitation: Do not call Hooks inside loops, conditionals, or nested functions. Hooks must be called at the top level of a component or custom Hook.

  • Reason: React relies on the order of Hook calls to manage state correctly. If Hooks are called inside conditionals or loops, the order of calls might change between renders, leading to incorrect state updates.

// Correct
function MyComponent() {
  const [count, setCount] = useState(0);

  if (count > 0) {
    // You can use state or call other functions inside conditionals
  }

  return <div>{count}</div>;
}

// Incorrect
function MyComponent() {
  if (someCondition) {
    const [count, setCount] = useState(0); // Error: Hooks cannot be called inside conditionals
  }

  return <div>{count}</div>;
}

Enter fullscreen mode Exit fullscreen mode

Naming Convention for Custom Hooks

  • Limitation: The name of a custom Hook must start with "use," such as useMyCustomHook.

  • Reason: The "use" prefix lets React know that the function is a custom Hook, allowing it to handle the Hook calls properly. Violating this naming convention might cause the custom Hook not to be recognized correctly.

Be Aware of the Asynchronous Nature of State Updates

  • Limitation: State updates are asynchronous, so do not rely on the immediately updated value after a state change.

  • Reason: React batches state updates to improve performance, which may cause delays in state updates. Depending on the immediately updated value may result in outdated data.

function MyComponent() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
    console.log(count); // May log the old value because setCount is asynchronous
  }

  return <button onClick={handleClick}>Click me</button>;
}

Enter fullscreen mode Exit fullscreen mode

When using React Hooks, it's essential to adhere to these usage limitations and best practices to ensure your components behave consistently, predictably, and are easy to maintain. Hooks are designed to simplify the development of function components, but improper usage can introduce unexpected issues. Understanding these limitations and avoiding common mistakes will help you use Hooks more effectively and write robust React applications.

💖 💪 🙅 🚩
lukewanghanxiang
Luke

Posted on August 29, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Request - Custom Hooks
webdev Request - Custom Hooks

November 9, 2024

React lifecycles - Custom Hooks
webdev React lifecycles - Custom Hooks

November 8, 2024

Event - Custom Hooks
webdev Event - Custom Hooks

November 7, 2024