loading...

Programmatically positioning elements with React Hooks

joepurnell1 profile image Joe Purnell ・2 min read

Let's look at how we can use React Hooks to tell a component where on our page it should render.

Imagine your favourite search engine - when we type in our search term we'd presumably see an autocompletion box appear beneath our search input: let's create that floating element.

TLDR;

Find a full example of the problem and the solution in this codesandbox.

So let's get cracking. Without too much detail right now, in the example, you'll find the go-to solution where we show an element if we have content to display. You'll also see this go-to shifts all the elements following downwards when shown. Not too great.

Making it Dynamic

First, let's add a quick piece of styling for our container so it floats above our other elements:

.searchresultsfloat {
  position: absolute;
  z-index: 1;
}

useRef

Now in our tale, we need to know which element we're trying to link our position too, this is where useRef steps in. We can use useRef to create a reference which we can then tie to a DOM element.

// Create our reference
const searchBarReference = React.useRef(null);

// Other React Pizzazz...

// Attach it to the element we want to judge our position against
<div ref={searchBarReference} className="box blue searchcontainer">

When our new referenced element renders, it will populate our reference with information about itself in the .current variable. We can then call the function getBoundingClientRect() on that reference to learn the positioning of the element.

Let's perform this inside a useEffect hook, so we know the page is rendered when we learn the position of our referenced element.

React.useEffect(() => {
  if (searchBarReference.current) {
    setSearchResultTop(searchBarReference.current.getBoundingClientRect().bottom);
  }
}, []);

Perfect! But not quite: we now have our element rendering where it should. But when we resize the screen you'll see our results element is in the wrong position:

Solution where container moves on resize

useLayoutEffect

This is where the useLayoutEffect hook can step in. This hook looks the same as our standard useEffect hook with the only difference being it triggers after all DOM mutation. We can use this to our advantage:

React.useLayoutEffect(() => {
  function updatePosition() {
    setSearchResultTop(searchBarReference.current.getBoundingClientRect().bottom);
  }
  window.addEventListener('resize', updatePosition);
  updatePosition();
  return () => window.removeEventListener('resize', updatePosition);
}, []);

Here we create a listener waiting for resize events. When one occurs, we reassert the boundaries of our referenced element so we can update our results to display where we want them to. So when we resize the screen, we see our results where they should be.

Be sure to return a function to remove your listener so when the page unmounts we don't leave our listener waiting for more events. And Voilà:

Solution which adjusts to resizing

That's it really.

So we used useRef to know where our element is and we used useLayoutEffect to update on DOM mutations. Everyone's happy!

As always: this isn't the only solution, just one I found. If you'd like to reach out, please do!

Discussion

pic
Editor guide