by Dane David
How to manage viewport intersection and input focus with custom React Hooks
React Hooks have been here for a while, and there are many packages, recipes and discussions going on about the same. This article goes through a similar path. It explains through two custom hook implementations to show how well code can be reused. There may be libraries or gists that perform the exact (or maybe improved) functionality that this article explains. It always helps to write code tailored for your needs, code that you can reason about.
This article assumes basic understanding and knowledge of React and React hooks. If not, you can read the docs and find out more about the React Hooks API.
Instead of being revealed at the end, we can first take a look at what functionality we’re trying to build. The live version of what we’re about to build is hosted here: https://danedavid.github.io/use-focus.
The app consists of a horizontal list of React components that can be scrolled through. Each component may be different or the same ( here, two different types of components are used, only to show that code can be reused across components ). The one thing they all have in common: an input field.
Our Goal: Always make the element inside the viewport the “active” element. Give focus to the input field inside that component, throughout scrolling.
We can manage an
activeElement state inside our root component, which stores an ID corresponding to the element active at any given time. The code for the root component is then:
TextInputFormElement are very similar components. The first one renders an
input[type="number"]. While the latter renders a text input field. That’s the only difference.
The first hook to be added is the one that will set the active element once the element reaches the viewport. That is, the hook
useActiveOnIntersect must call
setActiveElement passed from the parent, once the element is on the viewport. There is a well-known browser API for the same:
window.IntersectionObserver. If you’re not familiar, I suggest you read more about it here.
The hook must basically create an instance of
IntersectionObserver and listen for when it comes into view. We have given an
id=intersector attribute for the outermost
div will act as the root element for IntersectionObserver. The element must listen to when it is totally (or at least 95%) inside this root element. We use the
useEffect hook for registering the IntersectionObserver instance. And pass a callback function that invokes
setActiveElement on the intersection ( in our case, when the intersection ratio is greater than 95% ). The code for
useActiveOnIntersect is given below:
The hook does what it’s supposed to do: register an
observer instance that listens for intersection with the root element, given in the options as
document.querySelector('#intersector'), and invoke
setActiveElement if the element is intersecting.
elementRef is the React ref pointing to the DOM container element.
setActiveElement know which element is the active one? We’ll pass it from inside the component when we call this hook:
useActiveOnIntersect(() => setActiveElement(id), containerEl);
id is the value to be set as the
containerEl is the React ref referencing the input field’s container.
Now that we are sure that
activeElement always points to the element that is inside the viewport ( root element ) at any given time, we need to make sure that the input field inside the component gets focus. Again, we use the
useEffect hook for focusing the input field once it is active, and the cleanup function blurs the input field once it is not the active element anymore. The code is concise and straightforward:
inputRef is the React ref pointing to the input field, and
active is the active state of the containing element.
The code for a component that uses both these hooks is given below:
We have used
useRef hook to create refs for container and input elements and pass it on to their respective hooks. The functionality can even be written inside a single hook. The reason behind writing two separate hooks is because each represents a different effect.
The complete code can be found here: https://github.com/danedavid/use-focus.
Hope this article was helpful & short! Go ahead and write your own custom React hook now!
If you liked the article, hit the clap button below. You can also follow me on medium or twitter for more!!