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 — getElementById
or 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.
Unexpected behaviors
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.
With 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.
useRef for form validation
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.
With 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 handleSubmit()
function.
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.
useRef to animate DOM elements
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 box
and 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.
Where else can I go?
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