React Native Typescript Typing for "Backwards" ScrollView

Viewed 4274

I've got a ScrollView which was working first as JavaScript and then also as TypeScript; this ScrollView uses the ref property to capture a reference of the ScrollView and then invoke one of its functions, scrollToEnd(). But I've recently beefed up my linting and I'm needing to figure out the proper way to get this reference property instance typed.

I started off with this:

import React from "react";
import {ScrollView, StyleProp, ViewStyle} from "react-native";

const standardStyle = {flex:1, backgroundColor:'black'};

export default class ViewClass {
    static produceBackwardScrollingView(customStyle : StyleProp<ViewStyle>, contents : any, horizontal : boolean) {
        let scrollView : any;
        return (
            <ScrollView
                nestedScrollEnabled={true}
                ref={ref => {
                scrollView = ref
            }}
                onContentSizeChange={() => scrollView.scrollToEnd({animated: true})}
                style={[standardStyle, customStyle]}
                horizontal={horizontal}>{contents}</ScrollView>
        );
    }
}

After a lot of wrestling with the types, I came up with this which I think is in the right direction, but I'm still getting TypeScript errors.

import React from "react";
import {ScrollView, StyleProp, ViewStyle} from "react-native";

const standardStyle = {flex:1, backgroundColor:'black'};

export default class ViewClass {
    static produceBackwardScrollingView(customStyle : StyleProp<ViewStyle>, contents : any, horizontal : boolean) {
        let scrollView : React.RefObject<ScrollView> | ScrollView | null = React.createRef();
        return (
            <ScrollView
                nestedScrollEnabled={true}
                ref={ref => {
                    // below: scrollView maybe assigned "ScrollView" or "null"
                    scrollView = ref
                }}
                // below: scrollView is possibly null
                // also below: "Property 'scrollToEnd' does not exist on type 'ScrollView | RefObject<ScrollView>'."
                onContentSizeChange={() => scrollView.scrollToEnd({animated: true})}
                style={[standardStyle, customStyle]}
                horizontal={horizontal}>{contents}</ScrollView>
        );
    }
}

My scenario of course uses the ScrollView, so I guess if there's a better way to make the ScrollView start from the bottom that should probably fix my code in this instance. But I think this issue is generalizable in the sense that really my main problem is that I can't figure out what the type of my object reference needs to be and how to then invoke a function on it.

Note: I'm still new to React Native and TypeScript, so I may be slow to follow along.

1 Answers

Your code is mixing up the value of the ref object with the ref object itself. A ref is an object with a property current containing the value you've saved. Your type let scrollView: React.RefObject<ScrollView> | ScrollView says that it can be either the component itself or a ref for that component. That's why you're getting all these errors.

There are multiple ways to implement refs, but let's stick to what you are doing which is creating the ref with React.createRef. This creates a ref object which we use to to store the component ref. I'm going to rename it scrollRef for clarity.

const scrollRef = React.createRef<ScrollView>(); // has type React.RefObject<ScrollView>

When you use the ref in your callback, you need to use scrollRef.current to get the ScrollView object. The value of React.RefObject<ScrollView>['current'] is either a ScrollView or it can be null, so we need an optional chaining ?. before calling scrollToEnd.

onContentSizeChange={() => scrollRef.current?.scrollToEnd({animated: true})}

For the ref prop of the ScrollView, we can just pass our whole ref object.

ref={scrollRef}

Putting that all together, we get:

export default class ViewClass {
    static produceBackwardScrollingView(customStyle : StyleProp<ViewStyle>, contents : any, horizontal : boolean) {
        const scrollRef = React.createRef<ScrollView>();
        return (
            <ScrollView
                nestedScrollEnabled={true}
                ref={scrollRef}
                onContentSizeChange={() => scrollRef.current?.scrollToEnd({animated: true})}
                style={[standardStyle, customStyle]}
                horizontal={horizontal}>{contents}</ScrollView>
        );
    }
}

Typescript Playground Link

Related