← Back toMaster of React ref

String refs

Written byPhuoc Nguyen
Created
08 Oct, 2023
In our last post, we learned about using the `findDOMNode()` function to access a component's underlying DOM node. However, this only gives us access to the root node. What if we want to access other nodes in the component's markup tree?
Imagine we're building an accordion component with multiple items, each with their own markup. We can identify the body of each item with the `item__body` CSS class, and the content with the `item__content` class.
tsx
<div className="item">
<button className="item__heading">
<span className="item__arrow" />
{/* Title */}
</button>
<div className="item__body">
<div className="item__content">{/* Content */}</div>
</div>
</div>
We could try accessing these elements by starting at the root node and using `querySelector()`
tsx
const node = ReactDOM.findDOMNode(this);

const bodyEle = node.querySelector('.item__body');
const contentEle = node.querySelector('.item__content');
But that doesn't feel very "React-y", and it's not very reliable. If another engineer changes the CSS classes, the component could break.
In this post, we'll show you how to use string refs to avoid those issues and make your component more maintainable.

Syntax of string refs

To create a reference to an element, we simply pass a string as a `ref` attribute on that element. Here's an example:
tsx
<div className="item__body" ref="body">
...
</div>
In this example, we create a reference called `body` using a string and attach it to an element with the `ref` attribute. We can then access the underlying DOM node using `this.refs.body`.

Building an accordion item component

As with the rest of this series, we learn best by doing. In this post, we'll build an accordion item component that lets users expand or collapse content by clicking the heading.
To keep track of whether an item is open or closed, we'll use an internal boolean state called `isOpened`. By default, the content is hidden, so we set the state to `false` initially.
Here's an example of what the `AccordionItem` component could look like:
tsx
class AccordionItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpened: false,
};
}
}
Clicking the heading will toggle the `isOpened` state. In the code below, we handle the `click` event of the heading element and use the `setState` function to toggle the state:
tsx
handleToggle() {
this.setState((prevState) => ({
isOpened: !prevState.isOpened,
}));
}

render() {
return (
<button
className="item__heading"
onClick={this.handleToggle}
>
{this.props.title}
</button>
);
}
The content is only shown when the item is expanded:
tsx
render() {
const { isOpened } = this.state;

return (
<div className="item__body">
{isOpened && (
<div className="item__content">
{this.props.children}
</div>
)}
</div>
);
}
So far, nothing too fancy. We've used basic things like the `setState` function to manage internal states of a class component and render things accordingly depending on the state.
Now, let's stop reading and play around with the accordion item component in the demo below by clicking its heading:

Adding animation to improve user experience

Let's take the accordion item component to the next level by adding an animation when users expand or collapse the content. But before we dive into how we can achieve this in React, let's explore how we can add animation in CSS.
The most common way to do this is by using the `transition` property. By changing the height of the body element, we can run an animation over a duration of, say, 250 milliseconds.
css
.item__body {
overflow: hidden;
transition: height 250ms;
}
But how do we determine the width of the item body? That's where string refs come in. Since we have full control over setting refs to any element we want, we can set the refs for the body and content elements as follows:
tsx
<div className="item__body" ref="body">
<div className="item__content" ref="content">
{/* Content */}
</div>
</div>
Initially, the item is collapsed, so we set the `height` property of the body element to zero:
tsx
<div
className="item__body"
ref="body"
style={{
height: 0,
}}
>
...
</div>
Next, we need to modify the `handleToggle()` function. Instead of changing the `isOpened` state instantly, we need to update the height of the body element to trigger the animation. Here's how the modified version of `handleToggle()` function could look like:
tsx
handleToggle() {
const { isOpened } = this.state;
this.refs.body.style.height = !isOpened
? this.refs.content.clientHeight
: 0;
}
In this example, if the item is being opened, we set the height of its body element to be equal to its content element's client height to display it smoothly with an animation effect. On the other hand, if the item is being closed, we set its body element's height back to zero.
Thanks to string refs, we can access the body and content elements by the name of associated refs which are `this.refs.body` and `this.refs.content`.
By updating only the `height` property of the CSS style instead of changing all styles for each toggle action, we can achieve a smooth animation effect without any performance issues.
Now, we need to update the `isOpened` state accordingly when the animation is completed. We can do this by handling the `transitionEnd` event of the body element:
tsx
handleTransitionEnd() {
this.setState((prevState) => ({
isOpened: !prevState.isOpened,
}));
}

render() {
return (
<div
className="item__body"
onTransitionEnd={this.handleTransitionEnd}
>
...
</div>
);
}

Enhancing your component with arrows

For a more polished and professional look, consider adding an arrow to the heading of your component to indicate its expanding state. Don't worry if you're not sure how to do this – we've got you covered. Check out this helpful post for a details guide on how to add arrows to your design.
Ready to see the final product? Take a look at our demo and see for yourself how much of a difference this simple addition can make.

The limits of String refs

String refs can be helpful in certain situations, but they have some limitations that can cause issues and make them less flexible than other techniques, such as callback refs or ref hooks. Here are the main limitations of using string refs in React components:
  • They can't be used with functional components: functional components don't have instances, so they can't support string refs because they work by creating a reference to the underlying instance.
  • They may cause naming collisions: if you use string refs multiple times within a component, there's a risk of naming collisions between different refs. This can happen if two or more elements have the same ref name.
  • They don't provide access to the current value of the ref: unlike callback refs, which allow you to access the current value of the ref at any time, string refs only give you access to the DOM node when it's being mounted or unmounted.
  • They are not as flexible as callback refs: since string refs rely on attaching a name directly to an element, they're not as flexible as callback refs, which allow you to pass arguments and additional data along with the ref function.
Overall, while string refs can be useful in certain scenarios, their limitations make them less versatile and more prone to errors than other techniques. Since React 16.3, string refs have been deprecated in favor of other techniques for better performance and flexibility. Although string refs are no longer recommended, they were an important concept and React has been improved with more powerful refs based on this older concept.
If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn