React Event and Hooks

Summary: Learning about React Event Handling and Hooks.

Event Handling

In React, event handling is a key concept that allows us to capture and respond to user actions like clicks, form submissions, key presses, etc. React’s event system is very similar to handling events in vanilla JavaScript, but it has its own syntactical differences and best practices.

Events in JSX

  • In React, we pass a function as an event handler rather than a string (like in plain HTML).
  • Event names in React are written in camelCase instead of lowercase. For example:
    • onClick instead of onclick
    • onChange instead of onchange
  • We don’t need to call addEventListener. We simply define the event handler inline.

Passing Functions as Event Handlers

  • We can pass a function directly as an event handler, or we can pass an inline function.
  • The function can reference the event object or any other data passed to it.

Synthetic Events

  • React uses a cross-browser wrapper around the browser’s native event system called SyntheticEvent.
  • SyntheticEvent normalizes events to ensure they behave consistently across different browsers.
  • These synthetic events are faster and more efficient because React can pool and reuse them for performance reasons.

Binding Event Handlers

  • In React components, especially class components, we need to bind event handlers to the component’s context (this) using:
    • Arrow functions: Automatically bind the function to the component instance.
    • bind() method: Use this.handleClick.bind(this) to manually bind the function.

Prevent Default and Stop Propagation

  • Just like in regular HTML, we can prevent default actions or stop event propagation by calling event.preventDefault() and event.stopPropagation(), respectively.

Functional vs Class Components

  • In modern React (using Hooks), functional components are the most commonly used, and events are handled within them differently compared to class components.
import React, { useState } from 'react';

function EventHandlingExample() {
  // State to store the input value and button click count
  const [inputValue, setInputValue] = useState('');
  const [clickCount, setClickCount] = useState(0);

  // Handle input change event
  const handleInputChange = (event) => {
    // 'event' is the SyntheticEvent passed to this function
    setInputValue(event.target.value);
  };

  // Handle button click event
  const handleClick = () => {
    setClickCount(clickCount + 1);
  };

  // Prevent default form submission
  const handleFormSubmit = (event) => {
    event.preventDefault(); // Prevents page reload on form submission
    alert(`Form submitted with input: ${inputValue}`);
  };

  return (
    <div>
      {/* Input field with onChange event */}
      <input 
        type="text" 
        value={inputValue} 
        onChange={handleInputChange} 
        placeholder="Type something here" 
      />

      <p>Input Value: {inputValue}</p>

      {/* Button with onClick event */}
      <button onClick={handleClick}>
        Click me!
      </button>

      <p>Button Clicked {clickCount} times</p>

      {/* Form with onSubmit event */}
      <form onSubmit={handleFormSubmit}>
        <button type="submit">Submit Form</button>
      </form>
    </div>
  );
}

export default EventHandlingExample;
  • Input Change Event (onChange)
  • The handleInputChange function is triggered every time the value of the input changes. The event object contains useful information like event.target.value, which is the current value of the input field.
  • This value is then set into the inputValue state using setInputValue(), and this updated value is displayed below the input field.
  • Button Click Event (onClick):
  • The handleClick function is triggered when the button is clicked. This increases the clickCount by 1, and the updated count is displayed on the screen.
  • Form Submit Event (onSubmit):
  • The form submission’s default behavior (which would reload the page) is prevented by event.preventDefault().
  • It then alerts the current value of the inputValue state to demonstrate form handling.

Event Binding in Class Components

If we are using class components event handling is slightly different. We must explicitly bind this to event handlers.

import React, { Component } from 'react';

class EventHandlingClassExample extends Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: '',
      clickCount: 0,
    };

    // Bind the event handler to `this`
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  handleInputChange(event) {
    this.setState({ inputValue: event.target.value });
  }

  handleClick() {
    this.setState((prevState) => ({
      clickCount: prevState.clickCount + 1
    }));
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.inputValue}
          onChange={this.handleInputChange}
        />
        <p>Input Value: {this.state.inputValue}</p>
        <button onClick={this.handleClick}>
          Click me!
        </button>
        <p>Button Clicked {this.state.clickCount} times</p>
      </div>
    );
  }
}

export default EventHandlingClassExample;

Hooks

React Hooks are a powerful feature introduced in React 16.8 that allow us to use state and other React features in functional components, without needing to write class components. Hooks simplify component logic, making it easier to manage state, lifecycle, and side effects in functional components.

Why Hooks?

Before hooks, class components were required to manage component state and lifecycle methods, which could be cumbersome.

  • Reuse logic: Hooks allow us to extract and reuse stateful logic across multiple components.
  • Simplify components: Functional components with hooks tend to be simpler and cleaner compared to class components.
  • Reduce complexity: Hooks eliminate the need for complex class hierarchies and make code easier to maintain and test.
  1. useState: Manages local state in functional components.
  2. useEffect: Handles side effects like data fetching, subscriptions, and DOM manipulations.
  3. useContext: Provides access to React’s context API, making it easier to share state between components.
  4. useReducer: Manages more complex state logic that involves multiple sub-values or requires a centralized state management.
  5. useRef: Gives access to DOM elements or mutable variables that persist across renders.
  6. useCallback: Memoizes a function to prevent unnecessary re-creations on every render.
  7. useMemo: Memoizes the result of a computation, optimizing performance.
  8. useLayoutEffect: Like useEffect, but runs synchronously after all DOM mutations, useful for measuring DOM nodes.
  9. useImperativeHandle: Customizes the instance value exposed to parent components when using ref.

useState

The useState hook allows functional components to hold and manage local state. It returns two values: the current state and a function to update that state.

import React, { useState } from 'react';

function Counter() {
  // Declare a state variable 'count' with an initial value of 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
  • useState takes an initial state value (0 in this case) and returns a pair:
  • State variable: count (the current state value).
  • State updater function: setCount (a function to update the state).

useEffect

The useEffect hook allows us to perform side effects in functional components.

  • Fetching data from an API.
  • Directly interacting with the DOM (like setting a title).
  • Setting up a subscription.
import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    // Fetch data when the component mounts
    fetch('https://api.example.com/data')
      .then((response) => response.json())
      .then((data) => setData(data));

    // Optionally, return a cleanup function to run when the component unmounts
    return () => {
      console.log('Component unmounted, cleanup any side effects.');
    };
  }, []); // Empty dependency array means this effect runs once, after the first render

  return (
    <div>
      <p>Fetched Data: {JSON.stringify(data)}</p>
    </div>
  );
}
  • useEffect takes two arguments:
  • The effect function: The function that runs after the component renders (this is where we perform side effects like fetching data).
  • The dependency array (optional): Controls when the effect should re-run. If empty, the effect only runs after the first render (similar to componentDidMount in class components).

useContext

useContext provides an easy way to consume values from React’s Context API. Context allows for sharing state or functions globally across a component tree, without needing to pass props down manually at every level.

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

const ThemeContext = createContext('light');

function ThemeButton() {
  // Access the current value of ThemeContext
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
      {theme === 'dark' ? 'Dark Theme' : 'Light Theme'}
    </button>
  );
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemeButton />
    </ThemeContext.Provider>
  );
}
  • useContext takes a context object (created with createContext) and returns the current context value.
  • The component doesn’t need to pass props down manually; instead, it can directly access the context value.

useReducer

useReducer is similar to useState but is more suited for managing complex state logic, such as when state depends on previous values or when we have multiple state transitions.

import React, { useReducer } from 'react';

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:
      return state;
  }
}

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

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}
  • useReducer takes two arguments:
  • A reducer function that takes the current state and an action, and returns a new state.
  • The initial state value.
  • It returns the state and a dispatch function that we can use to trigger actions and update the state.

useRef

  • Access and manipulate DOM elements.
  • Hold mutable values that persist across renders without causing a re-render.
import React, { useRef } from 'react';

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

  const handleFocus = () => {
    // Focus the input field when the button is clicked
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus the input</button>
    </div>
  );
}
  • useRef creates a reference that can be attached to a DOM element via the ref attribute.
  • inputRef.current gives us access to the DOM node, allowing us to manipulate it (e.g., focusing the input).

useCallback

useCallback is used to memoize functions to prevent them from being re-created on every render unless their dependencies change. This is useful for optimizing performance in child components that rely on stable references for props.

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

function Child({ handleClick }) {
  console.log('Child re-rendered');
  return <button onClick={handleClick}>Click me!</button>;
}

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

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []); // Function won't be recreated unless the dependencies change

  return (
    <div>
      <Child handleClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>Re-render Parent</button>
    </div>
  );
}
  • useCallback ensures that the handleClick function remains stable across renders unless the dependency array changes.
  • Without useCallback, handleClick would be re-created on every render, causing unnecessary re-renders of the Child component.

useMemo

useMemo is used to memoize expensive computations, so they are only recomputed when necessary.

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

function ExpensiveCalculation({ num }) {
  const computeExpensiveValue = (n) => {
    console.log('Calculating...');
    return n * 2;
  };

  const memoizedValue = useMemo(() => computeExpensiveValue(num), [num]);

  return <div>Computed Value: {memoizedValue}</div>;
}

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

  return (
    <div>
      <ExpensiveCalculation num={count} />
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
«
»

Leave a Reply

Your email address will not be published. Required fields are marked *