DEV Community

Cover image for How to Scroll to a Component by Clicking a Button with React
Artem Beresnev
Artem Beresnev

Posted on

How to Scroll to a Component by Clicking a Button with React

Imagine you have a really long form with dynamically generated fields, and you have to scroll to an invalid field message by clicking the Submit button. All the form fields are independent and are not managed by any form library.

The example form

Expected behavior

Expected behavior

Solution #1

The first thing that comes to my mind is to use native API element.scrollIntoView() or Window.scrollTo(), but the critical question is how to find the target element to pass it or its coordinates to the scroll function?

The second thing comes to my mind is to find this element by ID using document.querySelector() or document.getElementById(). So, the code will look something like that:

import {validateFormField, formFieldId} from "../form-field"

const handleFormSubmit = (e) => {
    e.preventDefault(); // prevent form submit
    const isUniqueFieldValid = validateFormField();
    if (!isUniqueFieldValid) {
        const uniqueField = document.getElementById(formFieldId);
        uniqueField?.scrollIntoView();
        return;
    }
}
Enter fullscreen mode Exit fullscreen mode

Issue

But did you mention that the suggested approach breaks the core React principles – the concept of the components. We violate this principle when trying to turn to something inside the component (form field) from the business logic. This time we are starting to manipulate DOM directly in a jQuery-like style.

Solution #2

To avoid this dirty trick, I propose returning to the components approach and encapsulating scrolling logic inside the form field component. Thus, by clicking on submit button, I don't need to find the element and scroll into it. I need to dispatch the event to notify that element should be shown:

import {validateFormField} from "./business-logic/validate-form-field";

const handleSubmit = (e) => {
    e.preventDefault();
    // ⚠️ complex business logic to check if <InputField /> is invalid
    const isUniqueFieldValid = validateFormField();
    if (!isUniqueFieldValid) {
        emitErrorInFormField();
    }
}
Enter fullscreen mode Exit fullscreen mode

By dispatching the event, we could resolve the issue of UI-logic encapsulation. Inside the component, we could legally apply to elements and call functions like element.scrollIntoView() because we may use `ref's from React API, which gives us a link to the DOM node.

In a simple React/Redux application without an event bus, the easiest way to trigger the scroll inside the <FormField /> component is to switch the prop shouldScrollToElement to true and switch it to false immediately`.

import React, {useEffect, useRef} from "react";

export const FormField = (props: {
    shouldScrollToElement?: boolean, errorMessage?: string, onScrollToElement?: () => void
}) => {
    const {shouldScrollToElement, errorMessage, onScrollToElement} = props;
    const ref = useRef<HTMLDivElement>(null);

    useEffect(() => {
        if (shouldScrollToElement) {
            ref?.current?.scrollIntoView();
            onScrollToElement?.();
        }
    }, [shouldScrollToElement, onScrollToElement])

    return (<div ref={ref}>
        <label />
        <input />
        {errorMessage ? (<div>{errorMessage}</div>) : null}
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Demo


Conclusion

To sum up, let's repeat the key steps to implement the suggested solution:

  • Invoke the validation function by clicking the 'Submit' button
  • If there are some validation errors, dispatch the action
  • The action changes prop shouldScrollToElement of <FormField /> component
  • <FormField /> handles prop change. We get an element to scroll using React ref and invoke the .scrollIntoView() method on this element
  • Immediately after the scrolling, the component should dispatch the event to reset prop shouldScrollToElement

This is a simple example of scrolling implementation in React applications that does not break the component encapsulation principle.

Cheers!

Top comments (0)