13

Summary of Problem

I'm using react-native-snap-carousel and it's not rendering properly. It only renders after being swiped and I need to it render when the screen initially renders. When I have the screen assigned to the initial route of my BottomTabNavigator or the initial route in my Stack Navigator in React Navigation, then the carousel renders perfectly. When I assign the exact same screen to any other route in a Stack Navigator, then it doesn't render the carousel until I swipe it.

I need to use the screen with the carousel as the second route in my Stack Navigator and I can't figure out how to make it work properly.

What I've tried

  1. I've tried taking everything else out of the screen
  2. I've also tried creating a new screen from scratch.
  3. I've tested the screen as the initial route in the Stack Navigator and it works perfectly but I still can't get the carousel to render when the screen loads.
  4. I've also tried switching to a class based react component and that hasn't helped.

Code

Component with Carousel

import React, { useState } from "react";
import { View, Text } from "react-native";
import { useDispatch } from "react-redux";
import styles from "./StatSelectorStartComp.style";
import HeaderText from "~/app/Components/HeaderText/HeaderText";
import Carousel from "react-native-snap-carousel";
import LargeButton from "~/app/Components/Buttons/LargeButton/LargeButton";
import NavigationService from "~/app/services/NavigationService";
import { saveStartCompStatCategory } from "~/app/Redux/actions/dailyCompActions";

const StatSelectorStartComp = ({}) => {
  const dispatch = useDispatch();
  const ENTRIES1 = ["Kills", "Wins", "K/D", "Win %"];
  const [selectedStat, setSelectedStat] = useState(ENTRIES1[0]);

  const _renderItem = ({ item, index }) => {
    return (
      <View style={styles.slide}>
        <Text style={styles.compSelectStatCarousel}>{item}</Text>
      </View>
    );
  };

  return (
    <View style={styles.container}>
      <View style={styles.headerTextView}>
        <HeaderText header={"Configure Competition"} />
      </View>
      <Text style={styles.h5Secondary}> Which stat will you track?</Text>
      <View style={styles.selectStatView}>
        <Text style={styles.mediumGreyedOut}>Most {selectedStat} Wins</Text>
        <Carousel
          ref={c => {
            _carousel = c;
          }}
          data={ENTRIES1}
          renderItem={_renderItem}
          sliderWidth={375}
          itemWidth={100}
          onSnapToItem={index => {
            setSelectedStat(ENTRIES1[index]);
          }}
        />
      </View>
      <View style={styles.buttonView}>
        <LargeButton
          onPress={() => {
            dispatch(saveStartCompStatCategory(selectedStat));
            NavigationService.navigate("CompAddFriends");
          }}
          buttonText="Add Friends"
        />
      </View>
    </View>
  );
};

export default StatSelectorStartComp;

Styles for Component with Carousel

import { StyleSheet } from "react-native";
import { backgroundColor } from "~/app/Constants";
import {
  h5Secondary,
  mediumGreyedOut,
  compSelectStatCarousel
} from "~/app/FontConstants";

export default StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "space-between",
    backgroundColor
  },
  headerTextView: {
    flex: 1
  },
  h5Secondary,
  selectStatView: {
    flex: 3
  },
  mediumGreyedOut,
  compSelectStatCarousel,
  buttonView: {
    flex: 2
  }
});

React Navigation Configuration

const StartCompStack = createStackNavigator({
  StartFriendsComp: {
    screen: StartFriendsComp
  },
  StatSelectorStartComp: {
    screen: CarouselTest
  },
  CompAddFriends: {
    screen: CompAddFriends
  },
  FinalCompScreen: {
    screen: FinalCompScreen
  }
});

const ProfileStack = createStackNavigator({
  Profile: {
    screen: ProfileScreen
  },
  Settings: {
    screen: SettingsScreen
  }
});

const BottomTabNav = createBottomTabNavigator(
  {
    Home: {
      screen: HomeScreen
    },
    Competitions: {
      screen: Competitions
    },
    StartComp: {
      screen: StartCompStack,
      navigationOptions: () => ({
        tabBarVisible: false
      })
    },
    CompScreen: {
      screen: CompScreen
    },
    Friends: {
      screen: FriendsDrawer
    },
    Profile: {
      screen: ProfileStack
    },
    FacebookFriendsList
  },
  {
    tabBarComponent: CustomTabNav,
    initialRouteName: "Home" 
  }
);

Pictures outlining the problem

When screen loads, carousel not rendered When screen loads, carousel not rendered

After swiping on carousel After swiping on carousel

bzlight
  • 1,066
  • 4
  • 17
  • 43

5 Answers5

28

There is an experimental configuration (currently v3.8.4) - removeClippedSubviews - which is set to true by default. Setting it to false fixed the issue form me.

I strongly recommend not using delays as it is not deterministic and changes per device.

Thanks to @auticcat who wrote this in a comment.

ronenmiller
  • 1,117
  • 15
  • 25
  • Thanks, it really helped. I am curious about that. Why when we set the flag as a false, it works? – Halil İbrahim Özdoğan Feb 08 '20 at 10:15
  • 1
    I believe the purpose of this feature is to prevent redundant rendering of components that are out of the current view (or clipped). But this might not be what you need depending on your usage of the carousel. – ronenmiller Feb 26 '20 at 15:38
7

The same problem was coming on our project and a little trick help us . We have set default loading state to true and in componentDidMount set state to false to show Carousel

Try this , it may help you

state = { loading: true };

  componentDidMount() {
    setTimeout(() => {
      this.setState({ loading: false });
    }, 10);
  }
  render() {
    if(this.state.loading) {
      return null;
    }

    // return component render which contain Carousel
    ......... 
  }

Mehran Khan
  • 3,616
  • 1
  • 13
  • 10
  • Thanks @Mehran Khan! It worked perfectly! with a timeout of 10 I still had an issue but I changed it to 100 and it's working perfectly. Before you had responded, I had a feeling this may be a react lifecycle issue and after seeing you solution I really think that's the case. Any guess why this might be? I'll let you know if I figure out what the actual problem is! I also open an issue on their github – bzlight Sep 05 '19 at 06:28
  • 14
    I solved it by simply using `removeClippedSubviews={false}` – Auticcat Sep 05 '19 at 07:57
4

I solved it by simply using removeClippedSubviews={false}

0
<Carousel
          ref={c => {
            _carousel = c;
          }}
          data={ENTRIES1}
          renderItem={_renderItem}
          sliderWidth={375}
          itemWidth={100}
          onSnapToItem={index => {
            setSelectedStat(ENTRIES1[index]);
          }}
        />
      </

I think the problem lies here giving static height and width. Try calculating the height and width dynamically and then show it. Dynamically here means calculating the deivce height and width and then making calculation on them.

Rishav Kumar
  • 4,979
  • 1
  • 17
  • 32
  • Thanks for the advice. Why do you think the issue lies with a static width and height? I tried changing them and that didn't help. At the end of the day even if its calculated dynamically, it still uses a number to set the width and height, doesn't it? – bzlight Sep 05 '19 at 06:20
  • probably one component may be over ridden over another component – Rishav Kumar Sep 05 '19 at 06:21
  • I don't think that's the case since it renders properly when it's not inside a stack navigator. I followed Mehran's suggestion and it worked well. Thanks for your help though! – bzlight Sep 05 '19 at 06:29
-1

You can create your own custom carousel. The Carousel end result looks like this-

enter image description here

     goToNextPage = () => {
    const childlenth = this.getCustomData().length;
    selectedIndex = selectedIndex + 1;
    this.clearTimer();
    if (selectedIndex === childlenth) {
        this.scrollRef.current.scrollTo({ offset: 0, animated: false, nofix: true });
        selectedIndex = 1;
    }
    this.scrollRef.current.scrollTo({
        animated: true,
        x: this.props.childWidth * selectedIndex,
    });
    this.setUpTimer();
}

// pushing 1st element at last
 getCustomData() {
    const {data} = this.props;
    const finaldata = [];
    finaldata.push(...data);
    finaldata.push(data[0]);
    return finaldata;
}

This is the main logic used behind looped carousel. Here we are pushing the first item at last in the list again and then when scroll reaches at last position we are making the scrollview to scroll to first position as first and last element are same now and we scroll to first position with animation like this

this.scrollRef.current.scrollTo({ offset: 0, animated: false, nofix: true });

For further reference go through the link provided.

https://goel-mohit56.medium.com/custom-horizontal-auto-scroll-looped-carousel-using-scrollview-42baa5262f95

Mohit Goel
  • 722
  • 1
  • 8
  • 18