DEV Community

Mina
Mina

Posted on

 

Custom Hooks to the Rescue

Hooks are great! I cannot say enough about them. If you are not in love with hooks yet, you will be. Hope it is not too presumptuous to say so.


Why do we love hooks and how are they different from regular components? When I had an ah-ha moment, it was not when I encountered all those existing hooks like useEffect, useState, useRef etc, but rather when I made my own custom hooks.

Hooks are stateful, so heavily rely on the closure mechanism. Each one of the hooks has a piece of memory attached to it, which can be retained. Here is a simple comparison between a stateful and stateless component.

function StatelessComponent({ data }) {
  return <>{data}</>;
};

function StatefulComponent() {
  const [data, setData] = React.useState()
  return <>{data}</>;
}

Enter fullscreen mode Exit fullscreen mode

A stateless component is just a pure function without any side-effects so what it returns always depends on the arguments that you pass in. In contrast, a hook is also a function but, it has stateful logic in it and it keeps the track of data.

Here is a simple custom hook for a pagination.

function usePagination({
   initialPage,
   prevPage,
   nextPage,
   latestPage,
   oldestPage,
}: Props) {

  const [current, setCurrent] = useState<number> 
  (initialPage);

//Storing the current page in the localStorage whenever it changes
  useEffect(() => {
    localStorage.setItem("page", current?.toString());
  }, [current]);

//Removing the current page on clean-up.
  useEffect(() => {
    return () => {
      localStorage.removeItem("page");
    };
  }, [latestPage]);

  const latest = () => {
    setCurrent(latestPage);
  };

  const previous = () => {
    setCurrent(Math.max(0, current - prevPage));
  };

  const next = () => {
    setCurrent(Math.min(latestPage, current + nextPage));
  };

  const oldest = () => {
    setCurrent(oldestPage);
  };

  return {
    current,
    getLatest: () => latest(),
    getPrev: () => previous(),
    getNext: () => next(),
    getOldest: () => oldest(),
  };
}
export default usePagination;

Enter fullscreen mode Exit fullscreen mode

Hooks makes your code DRY and keeps your data separate from one another and each defined hook caches data which you can persist and populate. You can just assign it and start using it right away.

function Pagination() {
  const navigation = {
    initial: 0,
    prevSteps: 10,
    nextSteps: 10,
    latestPage: 273,
    oldestPage: 273 % 10,
  };

  const pagination = usePagination(navigation);

  useEffect(() => {
    setPage(pagination?.current?.toString());
  }, [navigation]);


return (
    <div>
      <Button onClick={pagination.getLatest}>
        Latest
      </Button>
      <Button  onClick={pagination.getPrev}>
        Previous
      </Button>
      <Button onClick={pagination.getNext}>
        Next
      </Button>
      <Button onClick{pagination.getOldest}>
        Oldest
      </Button>
    </div>
  );

}
Enter fullscreen mode Exit fullscreen mode

This is it! You can make a component and give the navigation object as a prop and pass it into usePagination and so on. Well, you get the point! No longer need to worry about passing the state value to the parent component to keep track of the current page.

If you are like me, once you have the taste of hooks, you will be desperate to find places to replace hooks with.

Here is another useful custom hook to use for a toggle functionality.

function useToggle(initialState: boolean): [boolean, () => void] {
  const [isOpen, setIsOpen] = useState(initialState);

  const toggleSwitch = () => {
    setIsOpen((prev) => !prev);
  };
  return [isOpen, toggleSwitch];
}

Enter fullscreen mode Exit fullscreen mode

And you can call it like so.

const [toggle, setToggle] = useToggle(false);
Enter fullscreen mode Exit fullscreen mode

To detect the size of a specific element? Easy.

function useWidthDetect(refWidth: React.RefObject<HTMLDivElement>) {
  const [size, setSize] = useState<number | undefined>(0);

  const handleSize = () => {
    const { current } = refWidth;
    const tableWidth = current?.offsetWidth;
    setSize(tableWidth);
  };

  useLayoutEffect(() => {
    handleSize();
    window?.addEventListener("resize", handleSize);

    return () => {
      window?.removeEventListener("resize", handleSize);
    };
  }, []);

  return size;
}
Enter fullscreen mode Exit fullscreen mode

You don't always have to return somehting when you use hooks.

function useReset({
  isSubmited,
  setIsSubmited,
  reset
}) {
  React.useEffect(() => {
    if (!isSubmited) return;
    reset();
    setIsSubmited(false);
  }, [reset, isSubmited, setIsSubmited]);
}

Enter fullscreen mode Exit fullscreen mode

Love hooks! They will save you from many headaches and prevent you from "Wrapper Hell" and having an evergrowing component. Do you like my article? What are the favorite hooks you made?

Top comments (5)

Collapse
 
hacker4world profile image
hacker4world

For people reading this make sure that the hook name must start with use otherwise react won't recognize it as a hook and won't allow you to use other hooks inside it

Collapse
 
jm066 profile image
Mina

Thanks! yes it would be violating the rules of hooks.

Collapse
 
islomjonmirzakbarov profile image
Islom Mirzakbarov

Thanks for your knowledge sharing!

Collapse
 
jm066 profile image
Mina

Thank you, Islom!

Collapse
 
loriever profile image
LoriEver

great to see you are focusing on every small thing . i really appreciate your work . How to perform istikhara for marriage

50 CLI Tools You Can't Live Without

The top 50 must-have CLI tools, including some scripts to help you automate the installation and updating of these tools on various systems/distros.