Sunny

Making sense of forward refs

I started my week with a bizarre UX bug, which I introduced in a recent refactor while I was componentizing a FlatList in a React Native project.

The issue was I refactored one of the views, which was a typical FlatList wrapped in a ScrollView, which works like ViewPager. In the process, I lost all the refs related logic that I’ve composed earlier, which includes resetting the scroll position for specific data changes.

Anyone who worked with React understands how refs provide a way to directly access DOM nodes or React elements created in the render method. This implementation is quite familiar while working with platform elements such as TextInput, ScrollView, or list views.

Here’s how we do it in a simple FlatList.

import React from "react";
import { FlatList } from "react-native";

class SimpleList extends React.Component {
  render() {
    return <FlatList ref={(ref) => (this.simpleList = ref)} />;
  }
}

The ref here gives us access to component’s methods to manage scroll. I mean, for example, you can scroll to the end of the list by calling this.simpleList.scrollToIndex() and so on.

It looks quite straightforward, right?

But as I discussed earlier, I failed to retain this functionality when I componentized it, i.e., making this component reusable. This implementation makes SimpleList a child component, and in various cases, you can’t access the methods from a parent with the refs as we did first.

So I started looking to get access to the refs in this reusable component and found a way that looked similar to prop drilling. We can get access by passing down the ref as one of the props to the child component, but the child component uses it differently. We forward this ref prop to the child component, and it’s methodically called ref forwarding.

From React documentation;

Ref forwarding is a technique for automatically passing a ref through a component to one of its children. This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries.

Great! How do we use it? Let’s see.

We have our awesome list with a bunch of custom configurations, which can be used across the application without duplication (DRY).

import React from "react";
import { FlatList } from "react-native";

class AwesomeList extends React.Component {
  render() {
    return (
      <FlatList scrollsToTop bounces={false} ref={this.props.forwardRef} />
    );
  }
}

// Forwarding refs
export default React.forwardRef((props, ref) => (
  <AwesomeList {...props} forwardRef={ref} />
));

Now the list is ready to accept refs as props. Let’s see how we can access it in a parent component.

import React from "react";
import { View } from "react-native";

import AwesomeList from './AwesomeList'

class ListView extends React.Component {
    listView = React.createRef()

    _scrollToIndex = (index) => this.listView.current.scrollToIndex(index),

    render() {
      return (
        <AwesomeList ref={this.listView} />
      );
    }
}

In the above example, we created a ref, using React’s createRef, and attached it to the element using ref, which is then forwarded to the child component. With this course, we enable access to child component methods and consume them but differently as declared in the _scrollToIndex function. Similarly, we can access all the other functionality of a child component using forward refs in React/React Native.

Awesome, this solved my issue, but something else triggered my curiosity. How does this work with hooks? Let me explain that in the subsequent post.