Coming from vanillajs the ideal approach of accessing and manipulating elements on a webpage involves the use of
document.querySelector(), and I've carried this ideology into my thought process in React. It is wrong. I shouldn't have been doing that, but, I did not know any better.
In React, it is recommended to use the
useRef() hook rather than any of the document object's API —
querySelector — to perform DOM manipulation operations. Why? You might ask.
Well, this is because manipulating the DOM directly with these APIs/methods can lead to unexpected behaviors sometimes, and it can be less efficient than using the virtual DOM.
Some of the unexpected behaviors that you may experience when you try to manipulate DOM elements directly;
Race conditions: When multiple components are trying to manipulate the same DOM element, it can lead to race conditions and unpredictable behavior. For example, one component might remove an element from the DOM while another component is still trying to manipulate it.
Accessibility issues: Manipulating the DOM directly can also cause accessibility issues for users who rely on screen readers or other assistive technologies. If you manipulate the DOM in a way that changes the order or structure of the content, it can make it difficult or impossible for these users to access the information.
Inefficient updates: When you manipulate the DOM directly, the browser needs to recalculate the layout and repaint the affected elements, which can be a slow and expensive operation. This can lead to performance issues and slow down your application.
useRef() you can create a reference to a DOM element and access its properties and methods without any of the complexities listed above. Guillermo Rauch wrote an article — 7 principles of rich web applications.
In this particular section, he talks about the necessity of updating the DOM and went ahead to mention Dan Abramov's Hot Reloading in React concept. Unrelated but this sorta fixes the Inefficient updates behavior you'd encounter when you manipulate the DOM directly.
Let's walk through the process of validating a form component in React with useRef. The approach I use occasionally involved manipulating the DOM directly. I'd find myself doing something like this;
And when you have multiple input fields, you'll start modifying the contents of
handleSubmit() to accommodate the DOM elements.
useRef, the function that validates the user input —
handleSubmit() — and the state variable,
name is modified like so. I've also added an
error variable to watch the state of the input.
In the modified version of
<FormComponent /> above, I created a reference to the input element using
useRef() and used it to access its value in the
I created a variable to store the
error state and update it based on the input value in the
handleChange() function. Then I proceed to render the error message if the
error state is true.
Yes, it is similar to the way we can alter the style of an element in vanillajs, say when an action is fired. It could be the click of a button, or when an element scrolls into view.
In the component below, the
useRef hook is used to create a reference to the
div element that we want to animate. There's a state variable
isAnimating which keeps track of whether or not the animation is currently playing.
When the "Start Animation" button is clicked, the
handleStartAnimation() function is called. This function sets
isAnimating to true and applies a
transform property to the
boxRef.current element using the DOM API. This causes the element to move 300 pixels to the right.
setTimeout resets the element's
transform property after 1 second. This allows the element to return to its original position and complete the animation.
You'll also notice how we're conditionally adding a CSS class —
is-animating — to the box element based on the isAnimating state variable. This allows us to apply additional styling to the element during the animation, such as changing its color or opacity.
Here's what the
is-animating classes looks like;
When you observe the snippet above closely, you'll see that it uses a
@keyframes rule to initialize the animation. When the
isAnimating state is true, it increases the size of the box and moves it
300px away from its original position and back.
With refs in React, we can minimize the aforementioned unexpected behaviors since it allows us to create a reference to any DOM element, and access its properties and methods in a more controlled and efficient way.
Another example of what you can do with
useRef() can be found here, in this guide