Custom Hooks and More!!! Ft. React

Custom Hooks and More!!! Ft. React

ยท

5 min read

Why Custom Hooks?

Ever since Hooks were introduced in React in its v16.8, people started falling in love by using it to build buttery smooth UI with less and readable code.

So did I.

Hooks made our lives easier than anything else ever did.

These are the most used React hooks these days.

But do you know we can make custom hooks ourselves.

Now what are custom hooks?

Custom hooks is a fancy word of quoting a normal function. But the key aspect where custom hooks is differentiated from normal functions is, custom hooks internally uses React hooks to make the functionality happen.

I hope you got a gist of how it works. If you didn't just stick around till the end to understand the intention on when to use it when to not.

We will take a real life example of YouTube thumbnails to get a closer understanding

youtube reference.gif

If you look at here closely, I first clicked on the dropdown of first video, then immediately clicked on the options of second video and as a result the first one's options got closed. Do you know even this is a feature.

We generally try to toggle it on and off only on click of the option menu button, not outside of it.

Just like this below example which I created

Code for below implementation is here

initial implementation.jpg


Ewwww!!!

ewww


const VideoCard = ({ video }) => {
  const { videoId, videoTitle, videoThumbnail } = video;
  const [showOptions, setShowOptions] = React.useState(false);
  return (
    <div key={videoId} className="video__card">
      <img src={videoThumbnail} alt="thumbnail1" />
      <div className="video_card__bottom">
        <p>{videoTitle}</p>
        <section className="video__actions">
            {/*  Toggling the boolean was just not enough here */}
          <button onClick={() => setShowOptions((state) => !state)}>
            &#8942;
          </button>
          <section
            className={`video__actions__options ${!showOptions && 'hidden'} `}
          >
            {/* another fork */}
            <span>Like</span>
            <span>Add To Playlist</span>
            <span>Add To Watch Later</span>
          </section>
        </section>
      </div>
    </div>
  );
};

We could use a function for the click handler, and check for the element where we click in the dom. If that element doesn't match the options element then toggle it off. But it would be hard to make it reusable as this situation might occur quite often at many places.

All this won't matter in React Styling frameworks as this would be handled and abstracted within.

But Rockstar Developer is who figures out about the abstractions. ๐Ÿ˜‰

We've talked way too much. So you know what's next if you know me :P

Talk is cheap, show me the code - Linus Torvalds

Let's create a custom hook for this problem

Instead of the below useState hook to toggle the element

const [showOptions, setShowOptions] = useState(false)

Implementation of Custom Hook

Lets create something like this

const {ref, isComponentVisible, setIsComponentVisible } = useClickOutside(false)

But wait, what is this. Lets breakdown

  • ref : You want to create a ref using useRef to detect the element from DOM which you are clicking on
  • isComponentVisible and setIsComponentVisible : You might have guessed these are just state variables and yes they are and it takes the initial value as false because the element would be hidden obviously.
  • useClickOutside : The hook we are going to implement.
const useClickOutside = (initialVisibleState  = false) => {
  const ref = useRef(null);
  const [isComponentVisible, setIsComponentVisible] = useState(initialVisibleState)

{/* Here goes the actual implementation */}

{/* These are the values that we use to set or refer while using the hook as shown in the above code snippet */}
  return {ref, isComponentVisible, setIsComponentVisible};
}

Now lets talk a little more:

Look at the cases which we need to deal:

  • The component should open on click of options menu.
  • The component should close on click of the same options menu.
  • The component should be closed on click of anywhere outside the it.

  • Add a handler for it in the hook

const handleClickOutside = (event) => {

{/* Check first whether the `ref` is valid or not, and next check whether the `ref` is not pointing to the actual button or buton's parent */}

{/* If so then hide the component , or keep it the same*/}

    if (ref.current && !ref.current.contains(event.target)) {
            setIsComponentVisible(false);
        }
}

Thats it.

But wait its not working.

Laugh

There is a little more to do and you'll be good to go. That is, to use this handler effectively everytime.

 useEffect(() => {
{/*The handler event occurs on click of that ref element, and run the event in capturing phase of parent to child hierarchy */}
        document.addEventListener('click', handleClickOutside, true);

{/* Do the same in the cleanup function as the unmounted part also should be handled */}
        return () => {
            document.removeEventListener('click', handleClickOutside, true);
        };
    }, []);

Boom! You have your custom hook that internally uses 3 React Hooks ready.

Now lets use this in out App.

import React from 'react';
import useClickOutside from './useClickOutside';

const VideoCard = ({ video }) => {
  const { videoId, videoTitle, videoThumbnail } = video;
  const { ref, isComponentVisible, setIsComponentVisible } =
    useClickOutside(false);
  return (
    <div key={videoId} className="video__card">
      <img src={videoThumbnail} alt="thumbnail1" />
      <div className="video_card__bottom">
        <p>{videoTitle}</p>
        <section ref={ref} className="video__actions">
          <button onClick={() => setIsComponentVisible((state) => !state)}>
            &#8942;
          </button>
          <section
            className={`video__actions__options ${
              !isComponentVisible && 'hidden'
            } `}
          >
            {/* another fork */}
            <span>Like</span>
            <span>Add To Playlist</span>
            <span>Add To Watch Later</span>
          </section>
        </section>
      </div>
    </div>
  );
};

export { VideoCard };

Fully implemented code

Live Preview

See the working example now.

final.gif

Let's take it a notch higher by making small refactoring

  • What if your dropdown action menu is totally another component and you need to take ref from that. Using ref prop on a component won't work normally as ref only works on HTML elements to take the reference from.
  • But there is a way to forward that ref and that's where forwardRef comes up.

This is how your code will change from previous one to this one

  • forwardRef takes a callback function as an argument

  • the callback function takes two arguments. One is the props passed to the component, the other is the ref passed to the component. Inside the callback function lies your JSX.

##Completed implementing Custom Hook with best practices

  • You created a custom hook.
  • It was used from parent component to child component using forwardRef and clean code.
  • Key takeaway . Only create custom hooks if the problem/feature dependency lies in more than one component, otherwise would make sense creating such amount of code.

Thanks for the read. Would appreciate your feedback and suggestions over the information that you got from the blog. ๐Ÿš€๐Ÿš€๐Ÿš€

ย