PagerView works on swipe but not programmatically with setPage - react-native

I am working with react-native-pager-view and I am having trouble setting the page programmatically using nativeEvent.setPage()
I have tried a lot of things, went through all the issues regarding it but no luck.
So far I've tried:
Changing the children to simple views exactly like the examples
Hard set the value in a useEffect and call it constantly
Use a separate value to keep track of current page
The swipe gesture is working fine on it and the pages transition, but if I press the button to go forward it doesn't.
If anyone has dealt with this component before please let me know if I'm missing anything.
Code that I'm trying to run. I've changed the View to simple react native view but it didn't work.
//Outside the component
const AnimatedPager = Animated.createAnimatedComponent(PagerView)
const [activeScreenKey, setActiveScreenKey] = useState(0)
const pagerViewRef = useRef<PagerView>(null)
// This function to sync the activeScreenKey if user swipes instead of clicking button
const getActiveKey = (e: PagerViewOnPageScrollEvent) => {
const position = e?.nativeEvent?.position
setActiveScreenKey(position)
}
// This calls the `setPage` and also puts it in a state var that I'm tracking
const setPage = (screenKey: number) => {
pagerViewRef.current?.setPage(screenKey)
setActiveScreenKey(screenKey)
}
<LinearGradient colors={['#3936BD', '#8448DB']} tw="flex-1">
<AnimatedPager
ref={pagerViewRef}
initialPage={0}
scrollEnabled={false}
onPageSelected={getActiveKey}
tw="flex-1"
>
<CustomView
key="1"
title={'title'}
subtitle={'subtitle'}
buttonTitle={'Next ➡️'}
onPress={() => setPage(1)}
/>
<CustomView
key="2"
title={'title'}
subtitle={'subtitle'}
buttonTitle={'Next ➡️'}
onPress={() => setPage(2)}
/>
</AnimatedPager>
</LinearGradient>

Related

Swipe up or pull up to refresh in React-Native

I am trying to implement a feature in which whenever I went to end the end of the FlatList and then when I swipe up or pull up for some small time, then this acts as a refresh and required data get loaded. Also the flatlist is long so we reach the end of the list in some time. Please help in this as I can't get any resources available for the same.
I tried using various packages like react-native-gesture-handler etc. but couldn't get the solution which I am hoping for.
Reaching the end of FlatlList amd pulling up are two different thing
For Reaching end of the list You can detect by onEndReached callback.
For Refreshing (Pull to refresh) You can use [RefreshControl][1]
Please see the below Example
// Logic for onEndReached
const onEndReached= ()=>{
do Your styff
}
<FlatList
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}>
data={yourArray}
renderItem={yourRenderItem}
onEndReached= {onEndReached}
/>
you can use onMomentumScrollEnd which is provided by FlatList
const handleMomentumScrollEnd = () => {
clearTimeout(scrollTimeout.current);
if (prevY.current > 50) {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}
};
you can check this expo snack link to view the full code.
Expo

How to prevent user interaction during screen transition animation?

When navigating between screens using the StackNavigator with a fade transition, a user is able to click during the transition animation and possibly hit a TouchableOpacity on the screen that is being navigated away from. The TouchableOpacity registers the hit and thus the app responds accordingly. This is causing issues for "fast clicking" users where they click a button to navigate to a new screen and immediately click where they think a new button will be, but in reality is clicking a button on the previous screen.
Is there a way to prevent any user interaction during these transition animations? I have tried setting the transition duration to 0 like so:
transitionConfig: () => ({
transitionSpec: {
duration: 0
}
})
but the issue still occurs.
I do not want to disable the animation completely, because it is quick enough for most users and they like the animation.
So in your case you can do several things
You can use React Native Activity Indicator -> View
You can use Overlay Library -> react-native-loading-spinner-overlay -> View GitHub
If you like to make loading like facebook / instagram -> then use react-native-easy-content-loader -> View GitHub
you need to flag screen before navigating away; disabling all touchs.
an easy way would be to have a reusable hook that return a transparent absolute positioned View that cover entier page and a callback to enable it;
so you flow will be; enable this which will overlap whole screen and capture any clicks basically disabling them;
something more like:
function useOverlay(){
const [isVisible, toggle] = React.useState(false);
const Component = React.memo(()=><View style={styles.transparentAbsolute} />,[])
return [toggle, isVisible ? Component : null];
}
then inside your Screen before you call navigate just call toggle
and include Component at top of you screen;
export default function TabOneScreen({ navigation }: RootTabScreenProps<'TabOne'>) {
const [ toggle, component ] = useOverlay();
return (
<View style={styles.container}>
{component}
<Button onPress={()=>{toggle(true); navigation.navigate('Home');} title="go home" />
</View>
);
}

How do I detect if a language has been changed when using the goBack() in React Native?

I have 3 pages that will be interacting with each other. a Login page, a Settings page, and a HomeScreen page. The login page contains a toolbar which has a clickable back arrow image(uses goBack()) and a clickable settings image which redirects to a settings page where a language can be picked.
There is no problem detecting a language change on the settings page because state is updated upon a change in language. However, if the user taps the backarrow image, the login page does NOT detect a change in state. How do I make sure that the login page can detect if the language has been changed(on the Settings page)?
I found on question that is similar to my problem here however, the answer provided uses navigate, whereas I'm using goBack. I know they're similar, but I'm unsure as to how/where I could pass a callback function on my settings page, and where to use refresh() on my Login page.
I use this method on my Login page
componentWillMount() {
AsyncStorage.getItem('language', (err, result) => {
langCode = result;
this.setState({
currLang: result,
})
});
}
On my Settings page:
onLangChange(value, index){
Config.langCode = value;
this.setState({ currLang: Config.langCode });
AsyncStorage.setItem('language', value)
console.log( 'here is your new lang ' + Config.langCode);
}
and then
render(){
const {navigate} = this.props.navigation;
const {goBack} = this.props.navigation
const langText = Config.langText[this.state.currLang]; // Object that has text in current language.
return(
<ScrollView style={styles.mainContainer}>
<View style={styles.headerContainer}>
<View style={styles.iconContainer}>
<TouchableOpacity onPress={ () => goBack('') } >
<Image source={Config.images.leftArrowIcon} style={styles.leftArrowIcon} />
</TouchableOpacity>
Use redux for global states like language in your example. It's just much simpler. If you have like 30 screens in your app, and every screen must display texts in correct language, this means that you have to send language back and forth between 30 screens, that's just horrible. redux is a state container, it's strongly recommended that you put those global states (states that many screens share) in redux and send them to each screen. If you don't know how to use redux yet, don't worry, it's not hard to understand, there're plenty good tutorials online. Here is the LanguagePickerExample I wrote for this question using redux, simply
npm install
or
yarn
then
react-native link
The result looks like this:

React-Native: Adding a Button on press of a Button

A simple question I can't wrap my head around is this:
Let's say I have a button, if I press that button, I want another button to appear. Imagine that buttoncreation as something that loops, so I can't have a predefined list of buttons I just show or hide.
React can create with react.createElement(), but I don't seem to get the correct usage of createElement.
Can I steer around having to create Elements through react? (Kind of feels like against the nature of react to actually create new html into something)
Easier said: I'm currently developing an app that calculates a common route between multiple departures and destinations, I want to give the user the ability to enter as many departures and destinations as he'd like, since the backend algorithm can handle it.
You can try something like this:
state = {
buttons: []
}
createButton() {
let { buttons } = this.state
const button = (
<Button
onPress={this.createButton.bind(this)}
/>
)
buttons.push(button)
this.setState({
buttons
})
}
renderItem({ item }) {
return (
<View>
{item}
</View>
)
}
render() {
return() {
<FlatList
data={this.state.buttons}
renderItem={(item) => this.renderItem()}
/>
}
}

Using FlatList#onViewableItemsChanged to call a Component function

I'm currently attempting to implement a form of LazyLoading using the FlatList component, which introduces a neat little feature called onViewableItemsChanged which gives you a list of all of the components that are no longer on the screen as well as items that are now on the screen.
This is a custom LazyLoad implementation and as such is more complicated than most LazyLoad open-sourced libraries that are available, which is why I'm working on my own implementation. I'm already looked into react-native-lazy-load and others.
Basically, I need to be able to call a function that's part of the component being rendered in the FlatList, I've tried creating a reference to the item rendered in the FlatList and calling it as such, but it doesn't seem to work.
For example:
<FlatList data={...}
renderItem={(item) => <Example ref={(ref) => this[`swiperRef_${item.key}`] = ref}}
onViewableItemsChanged={this.onViewableItemsChanged}
/>
onViewableItemsChanged = ({viewableItems}) => {
viewableItems.forEach((item) => {
const { isViewable, key } = item;
if(isViewable && !this.cachedKeys.includes(key)) {
const ref = this[`swiperRef_${key}`];
if(!ref) return console.error('Ref not found');
ref.startLoading();
this.cachedKeys.push(key);
}
});
}
Now in the <Example /> component I would have a function called startLoading which should be called when a new visible item is brought onto the screen, however the ref never exists.
I was actually doing everything correctly, but I accidently forgot to deconstruct the parameter returned from the renderItem function, so (item) should have been ({ item })
That's all there was to it.