callstack / react-native-pager-view

React Native wrapper for the Android ViewPager and iOS UIPageViewController.
MIT License
2.74k stars 420 forks source link

Storing refs into a Map or Array leaves the "current" property as null #224

Closed jdperos closed 3 years ago

jdperos commented 4 years ago

Bug report

Summary

I am attempting to create an array of JSX Elements out of an array.map() lambda, but when setting and storing a ref for each of them, those refs return as objects with their "current" as null.

More information from this post: https://stackoverflow.com/questions/63651084/map-of-refs-current-is-always-null?noredirect=1#comment112557125_63651084

If we look at the source of ViewPager (react-native-viewpager), we will see children={childrenWithOverriddenStyle(this.props.children)} (line 182). If we dig into the childrenWithOverriddenStyle method, we will see that it is actually "cloning" the children being passed in via React.createElement.

While this doesn't fix the problem, this at least answers the question of why the refs are null in your Map. It actually may be worth creating an issue for the library in order to swap it to React.cloneElement, since cloneElement will preserve the ref.

Environment info

react-native info output:

System:
    OS: Windows 10 10.0.18362
    CPU: (8) x64 Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz  
    Memory: 8.33 GB / 31.93 GB
  Binaries:
    Node: 12.16.2 - C:\Program Files\nodejs\node.EXE       
    Yarn: 1.22.4 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
    npm: 6.14.4 - C:\Program Files\nodejs\npm.CMD
    Watchman: Not Found
  SDKs:
    Android SDK: Not Found
  IDEs:
    Android Studio: Not Found
  Languages:
    Java: Not Found
    Python: 3.7.6
  npmPackages:
    @react-native-community/cli: Not Found
    react: ~16.11.0 => 16.11.0
    react-native: https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz => 0.62.2
  npmGlobalPackages:
    *react-native*: Not Found

Library version: x.x.x

Steps to reproduce

  1. Create a Map/Array of React.RefObjects.
  2. Use an arrow function to create an array of JSX elements
  3. Within the arrow function, create and set a ref for each JSX Element, and store it in the Map/Array
  4. Try to use any of the refs later and all of their "current" properties will be "null"

Describe what you expected to happen:

  1. I expect the refs passed to the JSX Elements to be valid refs

Reproducible sample code

export default class QuizViewPager extends React.Component<QuizViewPagerProps, QuizViewPagerState> {

    quizDeck: Deck | undefined;
    quizRefMap: Map<number, React.RefObject<Quiz>>;
    quizzes: JSX.Element[] = [];
    viewPager: React.RefObject<ViewPager>;

    constructor(props: QuizViewPagerProps) {
        super(props);
        this.quizRefMap = new Map<number, React.RefObject<Quiz>>();
        this.viewPager = React.createRef<ViewPager>();
        for (let i = 0; i < this.quizDeck!.litems.length; i++) {
            this.addQuiz(i);
        }
    }

    addQuiz(page: number) {
        const entry = this.quizDeck!.litems[page];
        var ref: React.RefObject<Quiz> = React.createRef<Quiz>();
        this.quizRefMap.set(page, ref);
        this.quizzes.push(
            <Quiz
                key={page}
                index={page}
                ref={ref}
                setQuizPage={this.setQuizPage}
            />
        )
    }

    render() {
        return (
            <View style={{ flex: 1 }}>
                <ViewPager
                    ref={this.viewPager}
                    onPageSelected={(pageIndex) => { 
                        console.log(this.quizRefMap);
                    }}
                >
                    {this.quizzes}
                </ViewPager>
            </View >
        );
    }
};
arekkubaczkowski commented 3 years ago

@jdperos it's already fixed in https://github.com/callstack/react-native-viewpager/pull/284