String refs
Tags: webdev, javascript, tutorial, react
Published: 2023-12-02 09:45:27

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:

{% codesandbox 43shxm %}

## 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](https://phuoc.ng/collection/css-layout/triangle-buttons/) 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.

{% codesandbox rl4dnp %}

## 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.

---

It's highly recommended that you visit the [original post](https://phuoc.ng/collection/react-ref/string-refs/) to play with the interactive demos.

If you found this series helpful, please consider giving the [repository](https://github.com/phuocng/master-of-react-ref) a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:

-   [Twitter](https://twitter.com/_phuocng)
-   [GitHub](https://github.com/phuocng)