Vineel
Vineel

Reputation: 265

onViewableItemsChanged is being called even after navigating to different screen in react native flatlist

I'm facing an issue with the onViewableItemsChanged prop from the flat-list. I have a list of videos that are being played when a single video comes viewable on the screen, I achived that.

But the issue is, once a video automatically starts playing while scrolling, I navigate to another where I have a text input field. Once the keypad is opened for the text input the onViewableItemsChanged is being called which is in another screen and the video continues playing in the text input screen.

Below is the code of two different screens:

FlatListSample.js

import React, {useState, useCallback} from 'react';
import {StyleSheet, Text, View, FlatList, TouchableOpacity} from 'react-native';
import YouTubeIFrame from 'react-native-youtube-iframe';

const data = [
  {
    id: '60524193a4e2070001537f51',
    title: 'React Native Tutorial #1 - Introduction',
    videoLink: 'ur6I5m2nTvk',
  },
  {
    id: '6051f9bba4e2070001537f50',
    title: 'React Native Tutorial #2 - Creating a React Native App',
    videoLink: 'pflXnUNMsNk',
  },
  {
    id: '6051f98accf9d60001f80429',
    title: 'React Native Tutorial #3 - Views, Text & Styles',
    videoLink: '_YydVvnjNFE',
  },
  {
    id: '6051f94accf9d60001f80428',
    title: 'React Native Tutorial #4 - Using State',
    videoLink: '1FiIYaRr148',
  },
  {
    id: '6051f921ccf9d60001f80427',
    title: 'React Native Tutorial #5 - Text Inputs',
    videoLink: 'c9Sg9jDitm8',
  },
  {
    id: '6051f8e8ccf9d60001f80426',
    title: 'React Native Tutorial #6 - Lists & ScrollView',
    videoLink: 'W-pg1r6-T0g',
  },
  {
    id: '6051f897a4e2070001537f4f',
    title: 'React Native Tutorial #7 - Flat List Component',
    videoLink: 'iMCM1NceGJY',
  },
  {
    id: '6051f84ca4e2070001537f4e',
    title: 'React Native Tutorial #8 - Touchable Components',
    videoLink: 'QhX25YGf8qg',
  },
  {
    id: '6051f817ccf9d60001f80425',
    title: 'React Native Tutorial #9 - Todo App (part 1)',
    videoLink: 'uLHFPt9B2Os',
  },
  {
    id: '6051f7dba4e2070001537f4d',
    title: 'React Native Tutorial #10 - Todo App (part 2)',
    videoLink: 'SGEitne8N-Q',
  },
];

const FlatListSample = props => {
  const [visibleItemIndex, setVisibleItemIndex] = useState();
  const [viewabilityConfiguration, setViewabilityConfiguration] = useState({
    waitForInteraction: true,
    viewAreaCoveragePercentThreshold: 40,
  });

  const onViewableItemsChangedHandler = useCallback(
    ({viewableItems, changed}) => {
      console.log('Viewable item');
      if (viewableItems && viewableItems.length !== 0) {
        setVisibleItemIndex(viewableItems[0].index);
      }
    },
    [],
  );

  const renderItem = ({item, index}) => {
    return (
      <View key={item.id} style={styles.videoSec}>
        <Text style={styles.videoTitle}>{item.title}</Text>
        <TouchableOpacity
          activeOpacity={0.9}
          onPress={() => {
            setVisibleItemIndex(null);
            props.navigation.navigate('TextInputSample');
          }}>
          <YouTubeIFrame
            videoId={item.videoLink}
            height={230}
            play={index === visibleItemIndex}
            initialPlayerParams={{rel: false, controls: false, loop: true}}
          />
        </TouchableOpacity>
      </View>
    );
  };
  return (
    <View style={styles.container}>
      <FlatList
        style={{flex: 1}}
        data={data}
        renderItem={renderItem}
        keyExtractor={item => item.id}
        viewabilityConfig={viewabilityConfiguration}
        onViewableItemsChanged={onViewableItemsChangedHandler}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F7F7F7',
  },
  videoSec: {
    flex: 1,
    paddingVertical: 10,
    borderBottomColor: '#FCEABC',
    borderBottomWidth: 3,
    backgroundColor: '#FFFFFF',
  },
  videoTitle: {
    fontSize: 16,
    color: '#1B110A',
    paddingHorizontal: 10,
    marginBottom: 5,
  },
});

export default FlatListSample;

TextInputSample.js

import React from 'react';
import {View, TextInput} from 'react-native';

const TextInputSample = () => {
  return (
    <View>
      <TextInput
        style={{height: 40, backgroundColor: 'azure', fontSize: 20}}
        placeholder="Type here to translate!"
        onChangeText={text => console.log(text)}
      />
    </View>
  );
};

export default TextInputSample;

When I navigate from FlatListSample.js after some scroll to TextInputSample.js and open keypad the video starts playing again. Can someone please find a solution to this!

Thanking in advance for the solution.

Upvotes: 1

Views: 7805

Answers (1)

Lakshman Kambam
Lakshman Kambam

Reputation: 1618

well, i just validated in both IOS and Android. It seems the issue is in android only. also it's weird behavior.

tried to find the cause of the problem by looking at react navigation screens stack, debugged a lot but couldn't track it yet.

in your case i have come up with 2 ways to solve your problem. (open for better solution)

  1. change focused boolean value with navigation eventListeners 'blur' and 'focus'
import React, {useState, useRef, useEffect} from 'react';
import { useCallback } from 'react';
import {StyleSheet, Text, View, FlatList, TouchableOpacity} from 'react-native';
import YouTubeIFrame from 'react-native-youtube-iframe'; 

const data = [
  {
    id: '60524193a4e2070001537f51',
    title: 'React Native Tutorial #1 - Introduction',
    videoLink: 'ur6I5m2nTvk',
  },
  {
    id: '6051f9bba4e2070001537f50',
    title: 'React Native Tutorial #2 - Creating a React Native App',
    videoLink: 'pflXnUNMsNk',
  },
  {
    id: '6051f98accf9d60001f80429',
    title: 'React Native Tutorial #3 - Views, Text & Styles',
    videoLink: '_YydVvnjNFE',
  },
  {
    id: '6051f94accf9d60001f80428',
    title: 'React Native Tutorial #4 - Using State',
    videoLink: '1FiIYaRr148',
  },
  {
    id: '6051f921ccf9d60001f80427',
    title: 'React Native Tutorial #5 - Text Inputs',
    videoLink: 'c9Sg9jDitm8',
  },
  {
    id: '6051f8e8ccf9d60001f80426',
    title: 'React Native Tutorial #6 - Lists & ScrollView',
    videoLink: 'W-pg1r6-T0g',
  },
  {
    id: '6051f897a4e2070001537f4f',
    title: 'React Native Tutorial #7 - Flat List Component',
    videoLink: 'iMCM1NceGJY',
  },
  {
    id: '6051f84ca4e2070001537f4e',
    title: 'React Native Tutorial #8 - Touchable Components',
    videoLink: 'QhX25YGf8qg',
  },
  {
    id: '6051f817ccf9d60001f80425',
    title: 'React Native Tutorial #9 - Todo App (part 1)',
    videoLink: 'uLHFPt9B2Os',
  },
  {
    id: '6051f7dba4e2070001537f4d',
    title: 'React Native Tutorial #10 - Todo App (part 2)',
    videoLink: 'SGEitne8N-Q',
  },
];

const FlatListExample = (props) => {

  const [visibleItemIndex, setVisibleItemIndex] = useState();
  const [focused, setFocused] = useState();

  const viewabilityConfig = {
    itemVisiblePercentThreshold: 40,
    waitForInteraction: true,
  };

  useEffect(() => {
    const subscribeFocusEvent = props.navigation.addListener('focus', () => {
      setFocused(true);
      console.log('focus', focused);
    });
    const subscribeBlurEvent = props.navigation.addListener('blur', () => {
      setFocused(false);
      console.log('blur', focused);
    });
    return (() => {
      subscribeFocusEvent;
      subscribeBlurEvent;
    });
  }, [focused]);
  
  
  const onViewableItemsChanged = useCallback(({ viewableItems, changed }) => {
    console.log({
      message: 'triggers change....1', 
      viewableItems, 
      changed,
      focused
    }, 'CHECK');
    if (changed && changed.length > 0) {
      setVisibleItemIndex(changed[0].index);
    }
  });

  const viewabilityConfigCallbackPairs = useRef([{ viewabilityConfig, onViewableItemsChanged }]);
  
  const renderItem = ({item, index}) => {
    return (
      <View key={item.id} style={styles.videoSec}>
        <Text style={styles.videoTitle}>{item.title}</Text>
        <TouchableOpacity
          activeOpacity={0.9}
          onPress={() => {
            setVisibleItemIndex(null);
            // props.navigation.navigate('Shop');
          }}>
          <YouTubeIFrame
            videoId={item.videoLink}
            height={230}
            play={index === visibleItemIndex}
            initialPlayerParams={{rel: false, controls: false, loop: true}}
          />
        </TouchableOpacity>
      </View>
    );
  };
  if(focused) {
    return (
      <View style={styles.container}>
        <FlatList
          style={{flex: 1}}
          data={data}
          renderItem={renderItem}
          keyExtractor={item => item.id}
          viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
        />
      </View>
    );
  }
  return null;
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F7F7F7',
  },
  videoSec: {
    flex: 1,
    paddingVertical: 10,
    borderBottomColor: '#FCEABC',
    borderBottomWidth: 3,
    backgroundColor: '#FFFFFF',
  },
  videoTitle: {
    fontSize: 16,
    color: '#1B110A',
    paddingHorizontal: 10,
    marginBottom: 5,
  },
});

export default FlatListExample;
  1. getCurrentRouteName of screen and render flatList. so that way it will render only if it's same screen.
export const FlatListScreen = ({ navigation, route }) => {

    console.log(route.name);

    // this screen title
    if(route.name === 'VideoScreen') {
        return (
            <FlatList 
                {...props}
            />
        );
    }
    return null;
};

let me know if you need any clarification any time.

Upvotes: 4

Related Questions