gorhom/react-native-bottom-sheet | Increase the swipe down area of bottom sheet - react-native

Hi All,
I am using https://github.com/gorhom/react-native-bottom-sheet
I need to increase the swipe-down area of the bottom sheet as highlighted below. Currently, it is only enabled on the handle indicator as I have set the enableContentPanningGesture={false} so that content inside the sheet is scrollable.
I tried adding the PanResponder on the highlighted partof the content as below :
const ActionSheetHeader = (props) => {
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true, // detect pan down
onPanResponderRelease: () => {
sheetRef?.current.close();?
},
}),
).current;
return (
<View
{...panResponder.panHandlers}>
{/* Contents of header */}
</View>
)
<BottomSheetModal
snapPoints={['100%']}
name="MySheet"
onDismiss={onDismiss}
ref={sheetRef}
style={styles.container}
backgroundStyle={styles.backgroundStyle}
handleIndicatorStyle={styles.handleIndicatorStyle}
enableContentPanningGesture={false}
stackBehavior="push">
<BottomSheetView>
<ActionSheetHeader />
<AcionSheetContent /> // contents
</BottomSheetView>
</BottomSheetModal>
When I drag over the highlighted section, action sheet closes without any animation, is it possible to have the same behavior as we pull down the sheet from the handle Indicator?
Thanks.

I believe you can put that highlighted component inside the prop "handleComponent"
that way the whole highlighted section would be considered as the handle and should behave like expected

Related

How to use same screen for different tabs based on states

I have two tabs Tab1 and Tab2 both tabs will have same screen so I have created one screen but it will show the data based on states like I have 4 states i.e "new", "processing", "success", "failure". Now I want to show only "new" and "processing" states data in Tab1 and "success" and "failure" in Tabs. Please let me know how I can achieve this.
You could hava a parent component which passes down the required props to the Tab Screen:
const TabItem = ({new, processing, success, failure}) => {
//do stuff here depending on if new, processing, success or failure
return <View />
}
const TabContainer = () => {
return (
<View>
<TabItem new={"New stuff"} processing="Processing stuff" />
<TabItem success={"Success stuff"} failure="Failure stuff" />
</View>
)
}

How to use refreshControl on a ScrollView but prevent dragging direction?

I have content that I want to "refresh" with a RefreshControl but I do not want the content to scroll upwards. I was originally under the impression that setting bounces=false and scrollEnabled=true would achieve this.
How about you set a key to the Component, then update it when you want to refresh it.
When a key changes, React see it as a new component. As a result, it will literally refresh the component.
export default () => {
const [refreshKey, setRefreshKey] = React.useState('randomstring');
// call me when refresh required
const refresh = () => {
setRefreshKey(refreshKey + 'randomstring');
};
return (
<ScrollView key={refreshKey}>
{/* Some Awesome Contents */}
</ScrollView>
);
};

How to show the index and end of the Flatlist by 15 items on every click

I need to show the index and bottom of the list while click on the up and down button.
Is any option to show only 15 items up or down If I click on the up or down arrow.
For Eg) Consider a list has 500 items. It has an up and down arrow. If I click on the down arrow once I need to show only 15 items for the first time and If click on the down arrow next need to show the next 15 items.
Also If I click on the up arrow it needs to show 15 items above not all
In this usecase I need to move up and down of the screen. Any option to modify the scrollToIndex and scrollToEnd in Flatlist to achieve this use case
upButtonHandler = () => {
//OnCLick of Up button we scrolled the list to top
this.ListView_Ref.scrollToOffset({ offset: 0, animated: true });
};
downButtonHandler = () => {
//OnCLick of down button we scrolled the list to bottom
this.ListView_Ref.scrollToEnd({ animated: true });
};
<TouchableOpacity
activeOpacity={0.5}
onPress={this.downButtonHandler}
style={styles.downButton}>
<Image
source={{uri:'https://raw.githubusercontent.com/AboutReact/sampleresource/master/arrow_down.png',
}}
style={styles.downButtonImage}
/>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.5}
onPress={this.upButtonHandler}
style={styles.upButton}>
<Image
source={{uri:'https://raw.githubusercontent.com/AboutReact/sampleresource/master/arrow_up.png',
}}
style={styles.upButtonImage}
/>
</TouchableOpacity>
You can slice the data provided to the FlatList every time a button is pressed.As the FlatList is a pure Component you need to pass a extra prop to re render the FlatList after button is pressed
Maintain a state variable such as section which describes which part of data to display like (0,15),(15,30),...
Update this variable inside the up and down buttons,taking care of the boundaries so as not to get bad results.This is easily solved by wrapping setState inside a if condition so it will look roughly as
updateSectionLow = () => {
const { section } = this.state;
if (section > 0) {
this.setState({
section: section - 1,
});
}
};
updateSectionHigh = () => {
const { section, data } = this.state;
if (data.length > (section + 1) * 15) {
this.setState({
section: section + 1,
});
}
};
and the FlatList looks like this
<FlatList
data={this.state.data.slice(this.state.section*15,(this.state.section+1)*15)}
renderItem={({ item }) => {
return (
<View style={styles.row}>
<Text>{item.data}</Text>
</View>
);
}}
extraData={this.state.section}
/>
Here is a working expo demo
EDIT
After having discussion with the OP person,i have changed my code little bit.
Get the offset after scroll,
for a vertical list
onMomentumScrollEnd={e => this.scroll(e.nativeEvent.contentOffset)}
Inside the handler
this.setState({
index: Math.floor(offset.y / (ITEM_HEIGHT+SEPARATOR_HEIGHT)),
});
if there is no separator then you can put SEPARATOR_HEIGHT to be 0
and it is only matter of using scrollToOffset with ref as follows
for going down the list by ITEMS_DISP(like 15)
this.flatListRef.scrollToOffset({
offset:(this.state.index+ITEMS_DISP)*ITEM_HEIGHT+(this.state.index+ITEMS_DISP)*SEPARATOR_HEIGHT
});
for going top the list by some ITEMS_DISP
this.flatListRef.scrollToOffset({
offset:(this.state.index-ITEMS_DISP)*ITEM_HEIGHT+(this.state.index-ITEMS_DISP)*SEPARATOR_HEIGHT
});
Updated demo link

How to Highlight Updated Items onRefresh of FlatList

I set up a FlatList with an onRefresh function to update the state when the user drags down the screen. It works properly, however I was wondering how I can highlight items in the FlatList that have been updated after the refresh.
Say, for example, I want to change the background for a few seconds for any item in the list that was updated, then return to normal.
<FlatList
data={scores}
renderItem={({item}) => (
<View style={styles.scoreContainer}>
<ScoreRow data={item.away} />
<ScoreRow data={item.home} />
</View>
)}
keyExtractor={item => item.gameID}
refreshing={isRefreshing}
onRefresh={updateScores}
/>
The best I could do was add a useEffect in the ScoreRow component to detect if something changes within that component, but that only allows me to update one component at a time, not the entire View.
const [runUpdate, setRunUpdate] = useState(false)
const [runs, setRuns] = useState(data.R)
useEffect(() => {
if(runs !== data.R) {
setRunUpdate(true)
setRuns(data.R)
setTimeout(() => setRunUpdate(false), 10000)
}
}, [data.R])
I can't figure out how to detect a change on an an item in the View of the FlatList so that I can change the entire View the way I did each component.
You can achieve this by using data of FlatList. You have to make an extra parameter for this.
eg:
//Method to refresh data
_refreshMethod() {
// Do your code to fetch...
...
let newDataArray = data // Data fetch from server or some thing.
let updatedArray = []
newDataArray.map((data, index) => {
data["isNewItem"] = true;
updatedArray.push(data);
});
this.setState({scores: updatedArray})
this._callTimer()
}
// Method to update new item status after a delay
_callTimer() {
setTimeout(function() {
let updatedArray = []
this.state.scores.map((data, index) => {
data["isNewItem"] = false;
updatedArray.push(data);
});
this.setState({scores: updatedArray})
}, 3000); // The time you want to do...
}
Then change the style of row based on the state value.
<FlatList
data={this.state.scores}
renderItem={({item}) => (
<View style={item.isNewItem ? styles.yourNewItemStyle : styles.scoreContainer}>
<ScoreRow data={item.away} />
<ScoreRow data={item.home} />
</View>
)}
keyExtractor={item => item.gameID}
refreshing={isRefreshing}
onRefresh={updateScores}
extraData={this.state}
/>

Opening context menu on long press in React Native

I'd like to have a context menu triggered on long press different places using React Native.
I.e. in a dialer like the default dailer. You can long-click on any contact and get a 'copy number' menu. And also you can long-click on the name of the person once you've opened their 'contact card'.
The straight-forward way needs a lot of copy-pasted boilerplate, both components and handlers.
Is there a better pattern for doing this?
All Touchable components (TouchableWithoutFeedback, TouchableOpacity etc.) has a property called onLongPress. You can use this prop to listen for long presses and then show the context menu.
To eliminate code mess and doing lots of copy paste you can separate your context menu as a different component and call it when the long press happen. You can also use an ActionSheet library to show the desired options. React native has a native API for iOS called ActionSheetIOS. If you get a little bit more experience in react and react-native you can create a better logic for this but I'm going to try to give you an example below.
// file/that/contains/globally/used/functions.js
const openContextMenu = (event, user, callback) => {
ActionSheetIOS.showActionSheetWithOptions({
options: ['Copy Username', 'Call User', 'Add to favorites', 'Cancel'],
cancelButtonIndex: [3],
title: 'Hey',
message : 'What do you want to do now?'
}, (buttonIndexThatSelected) => {
// Do something with result
if(callback && typeof callback === 'function') callback();
});
};
export openContextMenu;
import { openContextMenu } from './file/that/contains/globally/used/functions';
export default class UserCard extends React.Component {
render() {
const { userObject } = this.props;
return(
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done')}>
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done'))}>
<Text>{userObject.name}</Text>
<Image source={{uri: userObject.profilePic }} />
</TouchableWithoutFeedback>
</TouchableWithoutFeedback>
);
}
}
Similarly as the previous answer combine onLongPress with imperative control for popup menu - something like
<TouchableWithoutFeedback onLongPress={()=>this.menu.open()}>
<View style={styles.card}>
<Text>My first contact name</Text>
<Menu ref={c => (this.menu = c)}>
<MenuTrigger text="..." />
<MenuOptions>
// ...
</MenuOptions>
</Menu>
</View>
</TouchableWithoutFeedback>
When it comes to a lot of boilerplate - in React you can do your own components that you can reuse everywhere thus reducing boilerplate (and copy&paste)
See full example on https://snack.expo.io/rJ5LBM-TZ