DEV Community

Ebube111
Ebube111

Posted on

Unlocking the Full Potential of React with Essential and Overlooked Hooks

React hooks have revolutionized the way developers build scalable and efficient applications. Among the most commonly used hooks are useState, useEffect, and useRef. These essential tools allow developers to optimize their code and unlock the full potential of React. However, some hooks are often overlooked, yet they could significantly reduce the amount of code needed to perform specific tasks. This is where hooks like startTransition and useTransition come into play, offering developers an easy way to manage data fetching and loading states and deliver a seamless user experience.

The startTransition Hook
The first thing to know about this hook is that it doesn't return anything. Secondly, it's useful when you want to make a state change that may take some time to complete, but you don't want to block the user interface while it's happening. By using startTransition , you can tell React to prioritize rendering updates that the user can see, while still keeping the fallback UI in place to prevent the user from seeing a broken or incomplete UI. Examples include:

1. When Animating

import React, { useState, useRef, startTransition } from 'react';

function AnimatedObject() {
  const [isAnimating, setIsAnimating] = useState(false);
  const ref = useRef(null);

  const handleClick = () => {
    startTransition(() => {
      setIsAnimating(true);
    });
    setTimeout(() => {
      setIsAnimating(false);
    }, 1000); // the animation takes 1 second to complete

    // do some other animation logic here using ref.current
  };

  return (
    <div ref={ref} className={isAnimating ? 'animated' : ''} onClick={handleClick}>
      Animated Object
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

2. When Fetching data

import React, { useState, useEffect, startTransition } from 'react';

function DataComponent() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    startTransition(() => {
      setIsLoading(true);
    });
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(data => {
        setData(data);
        setIsLoading(false);
      });
  }, []);

  return (
    <div>
      {isLoading ? (
        <p>Loading...</p>
      ) : (
        <p>{data.title}</p>
      )}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Notice Something yet?
If you look closely, you will notice that we could just use the normal loading state with useState which is a common practice in React Applications. startTransition Offers some additional benefits.

First, using startTransition can help reduce unnecessary re-renders of your component. When you use a loading state to indicate that your component is still fetching data, React will still attempt to re-render the component every time the state updates. This can lead to unnecessary re-renders, which can impact performance and slow down your application.

In contrast, startTransition tells React to delay the rendering of your component until the end of the current event loop. This allows React to batch updates together and avoid unnecessary re-renders. It can also improve the performance of your application by reducing the amount of work that needs to be done during each render.

Second, startTransition can help improve the perceived performance of your application. By delaying the rendering of your component until after the current event loop, you can avoid displaying a partially rendered component with missing data. This can give the impression that your application is more responsive and can help improve the overall user experience.

Overall, while using a loading state can help indicate that your component is still fetching data, startTransition offers additional benefits by reducing unnecessary re-renders and improving the perceived performance of your application.

The useTransition Hook
The useTransition hook is different from startTransition hook but a little bit similar in functionality. By using this hook you can create a smooth and responsive animations that make your application feel more polished and professional.

This hook takes in no parameter and is destructured to return two values isPending and startTransition .

For example, imagine you have a list of items in your application, and you want to allow the user to reorder the items by dragging and dropping them. When the user drops an item in a new position, you need to update the state of the component to reflect the new order. If you update the state immediately, the component will re-render and the user may perceive a moment of lag or jank while the component updates. By using useTransition, you can delay the state update until after the drag-and-drop animation has been completed, which can make the component feel more responsive and reduce the perception of lag or jank.

import { useState, useRef, useTransition } from 'react';

function TableRow({ id, text, moveRow }) {
  const ref = useRef(null);
  const [isDragging, setIsDragging] = useState(false);
  const [startY, setStartY] = useState(0);
  const [endY, setEndY] = useState(0);

  const handleMouseDown = (e) => {
    setIsDragging(true);
    setStartY(e.clientY);
  };

  const handleMouseMove = (e) => {
    if (isDragging) {
      const offset = e.clientY - startY;
      ref.current.style.transform = `translateY(${offset}px)`;
    }
  };

  const handleMouseUp = () => {
    setIsDragging(false);
    setEndY(ref.current.getBoundingClientRect().y);
    moveRow(id, startY, endY);
    ref.current.style.transform = '';
  };

  const [startTransition, isPending] = useTransition({
    timeoutMs: 500,
  });

  const style = {
    transition: isPending ? 'none' : 'transform 0.5s ease',
  };

  return (
    <tr
      ref={ref}
      onMouseDown={handleMouseDown}
      onMouseMove={handleMouseMove}
      onMouseUp={handleMouseUp}
      style={style}
    >
      <td>{id}</td>
      <td>{text}</td>
    </tr>
  );
}

function Table() {
  const [rows, setRows] = useState([
    { id: 1, text: 'Row 1' },
    { id: 2, text: 'Row 2' },
    { id: 3, text: 'Row 3' },
  ]);

  const moveRow = (id, startY, endY) => {
    const index = rows.findIndex((row) => row.id === id);
    const nextIndex = endY < startY ? index - 1 : index + 1;
    if (nextIndex < 0 || nextIndex >= rows.length) {
      return;
    }
    const newRows = [...rows];
    const [removedRow] = newRows.splice(index, 1);
    newRows.splice(nextIndex, 0, removedRow);
    setRows(newRows);
  };

  return (
    <table>
      <tbody>
        {rows.map((row) => (
          <TableRow key={row.id} {...row} moveRow={moveRow} />
        ))}
      </tbody>
    </table>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we define a TableRow component that renders a table row and handles dragging and dropping by using the useRef hook to get a reference to the row element, the useState hook to track whether the row is currently being dragged, and the useTransition hook to smooth the transition between the row's original position and its new position after it has been dropped.

The TableRow component also takes a moveRow prop that is called when the row has been dropped and calculates the new position of the row in the table based on its starting and ending Y coordinates.

The useTransition hook is used to smooth the transition between the row's original position and its new position after it has been dropped. We pass an options object to useTransition that specifies a timeout of 500ms for the transition. We also use the isPending value returned by useTransition to conditionally set the transition property on the row's style object.

Overall, useTransition can be a powerful tool for improving the performance and user experience of React applications by adding smooth, responsive transitions and reducing jank.

Conclusion
In conclusion, optimizing applications is a critical concern for developers, especially as the application scales and becomes more complex. To avoid lags and the need for frequent rework, developers can make use of often-overlooked hooks like useTransition and startTransition when managing data fetching and loading states. By implementing these techniques, developers can provide users with a seamless experience while preventing unnecessary rerendering and delays in the application.

Top comments (0)