NSInternalInconsistencyException crashes app
Environment
iOS
Description
I have some fairly simple code that populates pages and then navigates to one of them. For some reason, if I am generating more than 20/30 pages on iOS, the app crashes with:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unexpected view controller: <UIViewController: 0x7fe24088d100>'
Reproducible Demo
I populate maybe 20 pages, and then elsewhere in the app, asynchronously navigate to a page somewhere towards the end (but after the pages in theory have been populated). I can see for a split second that it's trying to land on a page that doesn't yet exist, then it crashes. I guess that it's trying to access this screen before it has time to generate it. Oddly, using a setTimeout to access the page does not help. I call the page with setPage on the ref.
The code of the component in question:
type Props = {
// Should always be in order
dates: SliderDate[]
onDateSelect: (date: Date) => void
}
function sliceIntoChunks(arr: any[], chunkSize: number) {
const res = []
for (let i = 0; i < arr.length; i += chunkSize) {
const chunk = arr.slice(i, i + chunkSize)
res.push(chunk)
}
return res
}
export const DateSlider = (props: Props) => {
const [weeks, setWeeks] = useState<SliderDate[][]>([])
const getWeeks = (dates: SliderDate[]): SliderDate[][] => {
const weekResult: SliderDate[] = []
/// Fill with blank dates
const dateTicker = new Date()
dateTicker.setHours(0, 0, 0, 0)
dateTicker.setDate(dateTicker.getDate() - 100) // <--- breaks it
// dateTicker.setDate(dateTicker.getDate() - 10) // <- this one is ok
// find closest monday
while (dateTicker.getDay() !== 1) {
dateTicker.setDate(dateTicker.getDate() + 1)
}
console.log("got here")
for (let i = -10; i < 30; i++) {
// console.log(dateTicker.getDay())
if (dateTicker.getDay() !== 0 && dateTicker.getDay() !== 6) {
weekResult.push({
date: new Date(dateTicker),
selected: false,
recurring: false,
ordered: false,
disabled: true,
})
}
dateTicker.setDate(dateTicker.getDate() + 1)
}
while (weekResult.length % 5 !== 0) {
console.log("got here2", weekResult.length)
if (dateTicker.getDay() !== 0 && dateTicker.getDay() !== 6) {
weekResult.push({
date: new Date(dateTicker),
selected: false,
recurring: false,
ordered: false,
disabled: true,
})
}
dateTicker.setDate(dateTicker.getDate() + 1)
}
/// Overwrite with dates from props
for (const date of props.dates) {
const realIndex = weekResult.findIndex(day => {
return day.date.toISOString() === date.date.toISOString()
})
weekResult[realIndex] = date
}
return sliceIntoChunks(weekResult, 5)
}
useEffect(() => {
const newWeeks = getWeeks(props.dates)
setWeeks(newWeeks)
const idx = newWeeks.findIndex(week => {
return !!week.find(day => day.selected)
})
if (idx < newWeeks.length) {
setTimeout(() => {
// @ts-ignore
pagerRef.current!.setPage(idx)
}, 500)
}
}, [props.dates])
const pagerRef = useRef(null)
return (
<View style={styles.main}>
<PagerView
style={styles.pagerView}
ref={pagerRef}
// transitionStyle="curl"
>
{weeks.map(week => {
return (
<View style={styles.datesHolder} key={week[0].date.toISOString()}>
<DateItem onSelect={props.onDateSelect} date={week[0]} />
<DateItem onSelect={props.onDateSelect} date={week[1]} />
<DateItem onSelect={props.onDateSelect} date={week[2]} />
<DateItem onSelect={props.onDateSelect} date={week[3]} />
<DateItem onSelect={props.onDateSelect} date={week[4]} />
</View>
)
})}
</PagerView>
</View>
)
}
@darajava could you try the next version yarn add react-native-pager-view@next ?
@darajava could you try the next version
yarn add react-native-pager-view@next?
This bug is still present. I don't have reproducible demo because I'm using react-native-tab-view based on this repo and I have updated pager-view to 5.4.15 but this problem was fixed only partially and everything works fine just 1/3 of time
Having the same issue with version 5.4.9 (that's the current recommended version for Expo managed projects).
My implementation of the PagerView:
import * as React from 'react';
import { StyleSheet, TouchableOpacity, View, ViewStyle } from 'react-native';
import PagerView, { PagerViewOnPageSelectedEvent } from 'react-native-pager-view';
import { sizes } from '@/constants/sizes';
import { ArrowLeft } from '@/components/svgs/ArrowLeft';
import { ArrowRight } from '@/components/svgs/ArrowRight';
import { colors } from '@/constants/colors';
import { WelcomeCard } from '@/components/molecules/WelcomeCard';
const AMOUNT_OF_CARDS = 12;
export function ToursScreen() {
const [selectedPage, setSelectedPage] = React.useState(0);
const refPagerView = React.useRef<PagerView>(null);
const onPressLeft = () => {
if (selectedPage > 0) {
refPagerView.current?.setPage(selectedPage - 1);
setSelectedPage(selectedPage - 1);
}
};
const onPressRight = () => {
if (selectedPage < AMOUNT_OF_CARDS - 1) {
refPagerView.current?.setPage(selectedPage + 1);
setSelectedPage(selectedPage + 1);
}
};
const onPageSelected = (event: PagerViewOnPageSelectedEvent) => {
setSelectedPage(event.nativeEvent.position);
};
const cardElements = React.useMemo(
() =>
Array(AMOUNT_OF_CARDS)
.fill(null)
.map((_, index) => (
// eslint-disable-next-line react/no-array-index-key
<View key={index} style={styles.cardWrapper}>
<WelcomeCard />
</View>
)),
[],
);
return (
<>
<PagerView onPageSelected={onPageSelected} ref={refPagerView} style={styles.flex} initialPage={0}>
{cardElements}
</PagerView>
<View style={styles.row}>
<TouchableOpacity
disabled={selectedPage === 0}
onPress={onPressLeft}
style={selectedPage === 0 ? styles.buttonDisabled : styles.button}
>
<ArrowLeft />
</TouchableOpacity>
<TouchableOpacity
disabled={selectedPage === AMOUNT_OF_CARDS - 1}
onPress={onPressRight}
style={selectedPage === AMOUNT_OF_CARDS - 1 ? styles.buttonDisabled : styles.button}
>
<ArrowRight />
</TouchableOpacity>
</View>
</>
);
}
const buttonStyle: ViewStyle = {
borderRadius: sizes.m,
backgroundColor: colors.grey2,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: sizes.s,
flex: 1,
};
const styles = StyleSheet.create({
button: buttonStyle,
buttonDisabled: {
...buttonStyle,
opacity: 0.5,
},
cardWrapper: {
padding: sizes.m,
},
row: {
flexDirection: 'row',
},
flex: {
flex: 1,
},
});
Issue still occurs on v6.0.1.
Seems to still occur on v6.2.1