React Native - Show View while Scrolling in Flatlist - react-native

I need to SHOW a view when scroll is started and HIDE a view when scroll is stopped.
To detect the scroll movement, there is two ways:
Called when the user begins to drag the scroll view.
onScrollBeginDrag={this.showView}
onScrollEndDrag={this.hideView}
Called when the momentum scroll starts and Ends
onMomentumScrollBegin={this.showView}
onMomentumScrollEnd={this.hideView}
Exptected Behaviour:
If continues scroll is happening, it should not hide the view even onScrollEndDrag is called and still show the view until onMomentumScrollEnd.
If continues scroll is not active, its should hide when onScrollEndDrag called
Actual Behaviour:
If continues scroll is happening, its hide the view when onScrollEndDrag is called and shows the view again until onMomentumScrollEnd. So in between view is disappeared and then its appears when drag released.

Call a debounced function in onScroll.
Debouncing will mean it is called at the end (or start) of a bunch of
events. More Info
// Debounce
this.ViewVisibility = lodash.debounce(this.ViewVisibility, 100);
onScroll={() => {
this.ViewVisibility();
}}
ViewVisibility = () => {
console.log('Debouncing');
this.hideView();
}

Related

Using useRef to make a tap on a container ScrollView put focus in a TextInput

This is for a React Native app.
One of my screens has a ScrollView wrapped around a TextInput. The ScrollView has a lot of height - moreso than the TextInput when it's blank - so the behavior I want is that if a user taps on any of the 'blank space' in the ScrollView, it will put focus in the TextInput.
The code I have so far boils down to this:
export function MainInput() {
const ref_textinput = useRef();
const onTapScrollViewBody = () => {
console.log("Detected tap")
ref_textinput.current?.focus()
}
return (
<TouchableWithoutFeedback onPress={onTapScrollViewBody}>
<ScrollView style={styles.parentScrollView}>
<TextInput
ref={ref_textinput}
...
It's not working unfortunately. When focus isn't in the TextInput, the first time I tap in the blank space of the ScrollView, I do see the console log, but focus doesn't go into the TextInput, nor do further taps on the blank space trigger more console logs. However, tapping into the TextInput (which obviously puts focus back on it) then tapping back out will trigger the console log.
Am I misunderstanding how useRef works here?
Your understanding of useRef is fine, it's the understanding of the ScrollView that needs some help. If two elements that share coordinates are touchable, React Native will give the touch to the "top" element (whether by z-index, render order, etc). Your example creates the following hierarchy:
|-TouchableWithoutFeedback
|-ScrollView
|-TextInput
If you press within the TextInput's area, it will always capture the touch, as you found. If you press within the ScrollView's area, but outside of the text input, your touch is captured by the ScrollView, which will try to use your touch to scroll it. Only when you touch outside the ScrollView should your TouchableWithoutFeedback activate.
You still want the ScrollView to scroll, so when do you want your tap to focus the text input? You could delete your Touchable and use an event exposed by ScrollView, like
<ScrollView
onScrollEndDrag={() => ref_textinput.current?.focus()}
// and/or
onMomentumScrollEnd={() => ref_textinput.current?.focus()}
...
A solution that would handle tapping differently from scrolling could be achieved using react-native-gesture-handler, but it would be more involved.

How to stay in position when new messages are loaded in a ScrollView in React-Native?

I have a ScrollView containing messages (most recent messages are further) in a chat application.
I limit the number of pre-loaded messages, and I dynamically load a new batch when the Scroll View is scrolled to the top. So when new messages are loaded, the current scrollPos is at 0.
The problem is that when the new messages arrive, the scrollPos stays at 0, so the user is teleported to the oldest newly loaded message.
I have tried to deal with it by manually scrolling back down to the position using the size of the content change, but this is not satisfying as the user sees a back and forth scrolling.
Can someone think of a way to do this so that the user does not see any change when the new messages appear an can simply gradually scroll up to see them.
I found a way to do it.
The idea comes from the Invertible Scroll View component: https://github.com/expo/react-native-invertible-scroll-view
I didn't use the component but implemented the idea directly on the Scroll View to have minimal changes in my code.
To explain, we translate vertically the Scroll View using the style of the Scroll View and transform: [{ scaleY: -1 }]. We do the same for the children. Then, we revert the order of the messages.
In that setup, the scrollPos() measures from the visual bottom. To trigger the loading of the new messagges, now I use
const isCloseToBottom = ({layoutMeasurement, contentOffset, contentSize}) => {
const paddingToBottom = 20;
return layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom;
};
The trick is that now, when the new messages appear on top, you have nothing to do as the distance from the user's point of view to the bottom does not change.
You can use the onContentSizeChange prop to scroll to the bottom anytime it detects a change in content size.
The scrollToEnd function may differ depending on RN version.
<ScrollView
...
onContentSizeChange={() => this.scrollView.scrollToEnd({animated: true})}>
</ScrollView>
Ref: https://reactnative.dev/docs/scrollview#scrolltoend

Modal in front of a modal in react native, not picking up state

I am using the Modal component from react-native to creat a slide up menu for users to select. The issue with this is, if you wish to dim the background and animate with 'slide', it does this ugly thing of sliding a dimmed box up the screen, instead of dimming the whole background THEN sliding the view in.
So I tried to solve for this by using two Modals. One modal for the dark background to fade in and second modal to slide in with the menu with a transparent background.
This actually works, but when this.doneDayPicker changes the state of this.state.showModalDayPicker so both modals are no longer visible, <DarkModal> still appears. I'm left with <DarkModal> permanently on the screen.
What can I change to let <DarkModal> dissapear when this.state.showModalDayPicker is changed?
return (
<DarkModal visible={this.state.showModalDayPicker}>
<GoalModalScreen
visible={this.state.showModalDayPicker}
done={this.doneDayPicker}
title='Health benefits'
height={this.state.goalModalHeight}
>
{this.flatList()}
</GoalModalScreen>
</DarkModal>
);
Initially I would say this is because there is no done property on DarkModal like goalModalScreen?
Or use a Ternary Operator
{this.state.showModalDayPicker ? <DarkModal> : undefined}
You might also setup DarkModal's own toggle in state.
{this.state.toggleDarkModal ? <DarkModal> : undefined}

Ignore rest of screen when keyboard active

So i am adding the final touches to my application, and one bug that has been present for a while is when they keyboard is active, i am able to use the navigation on the upper half of the screen with the keyboard still showing
Is there a simple way to dismiss all other touches on the upper half of the screen when the keyboard is active? If you could point me in the right direction that would be great.
Looking for something similar to this: When keyboard active then touch screen anywhere, close keyboard
EDIT: would be nice if the textInput that the keyboard is outputting to was still touchable
If the parent view of the TextInput is a ScrollView, then u can use the prop 'keyboardShouldPersistTaps',
<ScrollView keyboardShouldPersistTaps='handled' />
If you're using a View and dont want scrolling, then probably u can wrap it inside a
//This should disable scroll
<ScrollView scrollEnabled={false} >
Documentation for keyboardShouldPersistTaps (React Native website)
keyboardShouldPersistTaps?: enum('always', 'never', 'handled', false,
true) #
Determines when the keyboard should stay visible after a tap.
'never' (the default), tapping outside of the focused text input when
the keyboard is up dismisses the keyboard. When this happens, children
won't receive the tap. 'always', the keyboard will not dismiss
automatically, and the scroll view will not catch taps, but children
of the scroll view can catch taps. 'handled', the keyboard will not
dismiss automatically when the tap was handled by a children, (or
captured by an ancestor). false, deprecated, use 'never' instead true,
deprecated, use 'always' instead

How to properly anchor popovers in Bootstrap 3x

If you look at the bootstrap demo that is hosted here And scroll down to the demo that has all 4 buttons. Now click on the left popover, it shows properly. However, When I shrink or extend the screen the popover is no longer anchored to the proper point. Is there a way around this?
This has to do with the container property for the popover.
http://jsfiddle.net/yvkhbw4b/1/
See the attached example. Show both popovers then resize the window from xs breakpoint to sm breakpoint. You will see the top item will seperate from the button as the container defaults to body. (I'm also setting it to body though options). The second item is attached to the parent div so that one remains attached to the element even though the breakpoint changed.
Documentation on popover options
Appends the popover to a specific element. Example: container: 'body'.
This option is particularly useful in that it allows you to position
the popover in the flow of the document near the triggering element -
which will prevent the popover from floating away from the triggering
element during a window resize.
$('#btn1').popover({
'container': 'body'
});
$('#btn2').popover({
'container': '.col-sm-12'
});
Potential Fix: you could add the following code. Currently only handles bottom aligned popovers but you can add options for other alignments.
$(window).resize(function () {
$('.popover.in').each(function () {
var el = $('[aria-describedby="' + this.id + '"]');
if ($(this).hasClass('bottom')){
$(this).css('top',el.offset().top + el.outerHeight(true));
}
});
});
http://jsfiddle.net/yvkhbw4b/2/