Tools: Your Users Shouldn't Have to Scroll to Find What Just Loaded

Tools: Your Users Shouldn't Have to Scroll to Find What Just Loaded

Source: Dev.to

Meet scrollIntoView() ## A Real Example: Auto-Scrolling to AI-Generated Content ## 1. Create a ref ## 2. Attach it to the element you want to scroll to ## 3. Scroll when the recipe loads ## Why useRef instead of an ID? ## Quick note on refs ## Where else is this useful? Here's a small thing that bugs me: you click a button, wait for something to load, and then... nothing happens on screen. The content loaded, but it's hiding below the fold. You have to notice the scrollbar changed and scroll down yourself. We can do better. And it takes like 3 lines of code. Every DOM element has this method built in. Call it, and the browser scrolls until that element is visible. That's literally it. Smooth animated scroll, right to the element. Done. I was building a recipe generator app in React. The user adds ingredients, clicks "Get a Recipe," and an AI returns a recipe. The problem? On smaller screens, the recipe loads completely off-screen. No visual feedback at all. Here's how I fixed it. In React, we use useRef to grab DOM elements instead of document.getElementById() — it plays nicer with reusable components since you won't end up with duplicate IDs. That's the whole thing. When recipe updates, the page gently scrolls down so the user can see it. No confusion, no hunting around. Totally fair question. getElementById would work! But React components are meant to be reusable. If your component ever renders twice on a page, you'd have duplicate IDs — which is invalid HTML and leads to weird bugs. Refs are scoped to the component instance, so they're always safe. If you're new to useRef, here's the two-sentence version: refs are like state, but changing them doesn't trigger a re-render. They're an object with a .current property — when you attach a ref to a DOM element, .current becomes that actual DOM node. Anywhere new content appears and you want to guide the user's eyes to it — new chat messages, form validation errors, search results loading in, accordion sections opening. It's a tiny detail, but it's the kind of thing that makes an app feel thoughtful. Sometimes the best UX improvements are the smallest ones. Three lines of code, and your users never have to wonder "did that work?" again. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: element.scrollIntoView({ behavior: "smooth" }); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: element.scrollIntoView({ behavior: "smooth" }); CODE_BLOCK: element.scrollIntoView({ behavior: "smooth" }); CODE_BLOCK: import { useRef, useEffect, useState } from "react"; function Main() { const [recipe, setRecipe] = useState(""); const recipeSectionRef = useRef(null); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { useRef, useEffect, useState } from "react"; function Main() { const [recipe, setRecipe] = useState(""); const recipeSectionRef = useRef(null); CODE_BLOCK: import { useRef, useEffect, useState } from "react"; function Main() { const [recipe, setRecipe] = useState(""); const recipeSectionRef = useRef(null); COMMAND_BLOCK: return ( <div> {/* ingredients form up here */} <div ref={recipeSectionRef}> {recipe ? <Recipe data={recipe} /> : <p>Ready for a recipe?</p>} </div> </div> ); } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: return ( <div> {/* ingredients form up here */} <div ref={recipeSectionRef}> {recipe ? <Recipe data={recipe} /> : <p>Ready for a recipe?</p>} </div> </div> ); } COMMAND_BLOCK: return ( <div> {/* ingredients form up here */} <div ref={recipeSectionRef}> {recipe ? <Recipe data={recipe} /> : <p>Ready for a recipe?</p>} </div> </div> ); } COMMAND_BLOCK: useEffect(() => { if (recipe && recipeSectionRef.current) { recipeSectionRef.current.scrollIntoView({ behavior: "smooth" }); } }, [recipe]); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: useEffect(() => { if (recipe && recipeSectionRef.current) { recipeSectionRef.current.scrollIntoView({ behavior: "smooth" }); } }, [recipe]); COMMAND_BLOCK: useEffect(() => { if (recipe && recipeSectionRef.current) { recipeSectionRef.current.scrollIntoView({ behavior: "smooth" }); } }, [recipe]); COMMAND_BLOCK: const myRef = useRef(null); // after render: myRef.current === the actual <div> element Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const myRef = useRef(null); // after render: myRef.current === the actual <div> element COMMAND_BLOCK: const myRef = useRef(null); // after render: myRef.current === the actual <div> element