How to disable highlighting effect of TouchableOpacity when scrolling? - react-native

<TouchableOpacity style={{ flex: 1 }} >
<ImageBackground
source={require('../../images/home.jpg')}>
<View style={styles.item} collapsable={false}>
<H3>{contentData[i].name}</H3>
<Text>{contentData[i].description}</Text>
</View>
</ImageBackground>
</TouchableOpacity>
I have a list of TouchableOpacity inside a ScrollView. I want to disable highlighting effect of TouchableOpacity. When scrolling I want to highlight only when onPress event is triggered. Because it may confuse the user that it is pressed.

Simply pass activeOpactity prop with value 1.
<TouchableOpacity activeOpacity={1}>....</TouchableOpacity>
Make sure you import TouchableOpacity from "react-native" not from "react-native-gesture-handler".

Try setting the activeOpacity prop on the TouchableOpacity to 1 when scrolling. Use default settings when the user stops scrolling.
https://facebook.github.io/react-native/docs/touchableopacity#activeopacity

You can try changing param delayPressIn. Look doc.
<TouchableOpacity delayPressIn={150} >
{children}
</TouchableOpacity>

You can make use of onScrollBeginDrag and onScrollEndDrag props.
state = {
scrollBegin: false
}
scrollStart = () => this.setState({scrollBegin: true})
scrollEnd = () => this.setState({scrollBegin: false})
<ScrollView onScrollBeginDrag={this.scrollStart} onScrollEndDrag={this.scrollEnd}>
... Other stuff
</ScrollView>
and set activeOpacity={1} for TouchableOpacity when this.state.scrollBegin=true

You could try replace TouchOpacity with RectButton in 'react-native-gesture-handler'. And don't forget to replace the ScrollView import from 'react-native' to 'react-native-gesture-handler'.
I found this solution in here.
It just said:
provides native and platform default interaction for buttons that are placed in a scrollable container (in which case the interaction is slightly delayed to prevent button from highlighting when you fling)

We implemeted a custom Touchable component using TouchableOpacity as click element and a wrapper View handling the opacity of the children elements.
By setting activeOpacity={1} to default and the pressed state to true when clicking, we can delay the rest of the onPress functionality by a unnoticeable 100ms to display an opacity shift when clicking. Which is shipped to the wrapper View. The View is wrapped inside the touchable instead of outside to better preserve styling.
We also added cleanup when component is unmounted in useEffect()
import React, { useEffect, useState } from "react";
import { View, TouchableOpacity } from "react-native";
const Touchable = (props) => {
const { children, onPress } = props;
const [pressed, setPressed] = useState(false);
useEffect(() => {
return setPressed(false);
}, []);
return (
<TouchableOpacity
{...props}
activeOpacity={1}
onPress={() => {
setPressed(true);
setTimeout(() => {
setPressed(false);
onPress();
}, 100);
}}
>
<View style={{opacity: pressed ? 0.8 : 1}}>
{children}
</View>
</TouchableOpacity>
);
};
export default Touchable;

I had the same issue, so I wrote this class that I use instead of <TouchableOpacity> in my code:
import React, { Component } from 'react';
import { TouchableOpacity } from 'react-native';
import TimerMixin from 'react-timer-mixin';
class TouchableOpacityScrollable extends Component {
_onPress() {
const { onPress } = this.props;
// Looking in the TouchableOpacity source code we see that
// the touch Opacity is 150, and that it comes back in 250 milliseconds.
// #see https://github.com/facebook/react-native/blob/c416b40542ece64e26fb2298485ae42eeebc352a/Libraries/Components/Touchable/TouchableOpacity.js
this.refs.touchableOpacity.setOpacityTo(0.2, 150);
TimerMixin.setTimeout(() => {
onPress();
this.refs.touchableOpacity.setOpacityTo(1, 250);
}, 150);
}
render() {
const { style, children } = this.props;
return (
<TouchableOpacity
ref="touchableOpacity"
style={style}
activeOpacity={1.0}
onPress={() => this._onPress()}
>
{children}
</TouchableOpacity>
);
}
}
export default TouchableOpacityScrollable;
You will have to install react-timer-mixin to prevent possible crashes.
Enjoy!

after upgrading RN version to 0.63.2 TouchableOpacity is working like it should, during scrolling, hover effect doesn't appears

Related

React Native - Variable link using a prop

I'm making an app with some products that I got from my Wordpress database. On the homescreen, I have an overview of all the products, each in a tile. I want to be able to put a button in each tile, which links to the specific product page. But, since it works with a component, I need to be able to do this with a prop. And, if possible, based on the title of the API.
This is my code for the screen with all the products:
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, FlatList, Image, Button } from 'react-native';
import SuitcaseItem from '../components/SuitcaseItem';
const AllSuitcasesScreen = ({ navigation }) => {
const [suitcases, setSuitcases] = useState([]);
const getSuitcases = async () => {
try {
const response = await fetch("https://evivermeeren.com/wp-json/wp/v2/posts?categories=59", {
}
)
const json = await response.json();
console.log(json);
setSuitcases(json);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
getSuitcases();
}, []);
return (
<View style={styles.screen}>
<View style={styles.flexbox2}>
<Text style={styles.products}>Onze koffers</Text>
<View style={styles.shoppingcart}>
<Image
style={styles.icon}
source={{uri: 'https://cdn-icons-png.flaticon.com/512/1413/1413908.png'}}
/>
<Text style={styles.number}>0</Text>
</View>
</View>
<View style={styles.list}>
<FlatList
data={suitcases}
renderItem={({ item }) => (
<SuitcaseItem
title={item.title.rendered}
imageUri={{uri: 'https://www.samsonite.be/on/demandware.static/-/Sites/default/dw851ab6f0/images/misc/sams_share-image.jpg'}}
desc={item.slug}
buttonText={item.title.rendered}
/>
)}
/>
</View>
</View>
);
}
export default AllSuitcasesScreen;
And this is the result:
Now, when I click the black button, I go to the page 'Evo L', which I also made. This is the button that I use:
<Pressable style={styles.seeProduct} onPress={() => navigation.navigate("Evo L")}>
<Text style={styles.text}>Bekijk product: {props.buttonText}</Text>
</Pressable>
This is in another file, the 'SuitcaseItem'.
So, I should be able to put something like navigation.navigate("props.buttonNav") with buttonNav = {item.title.rendered} so it goes to the page Evo L if I click on that one and then Evo M when I click on that tile and so one. Does anyone have an idea?
You can pass props to a screen. See this excellent official documentation for React Navigation on passing props.
-> Make a generic item detail screen like ItemDetail (instead of Evo L).
-> Modify the navigation.navigate("props.buttonNav") to:
navigation.navigate("ItemDetail", {itemTitle: props.buttonText})
You can access these props in the ItemDetail screen as:
function ItemDetail({ navigation, route }) {
return(
<Text>route.params.itemTitle</Text>
)
}

React Native with hooks, Modal not updating on state change unless I close and reopen

The only other similar question I could find here involved redux which I'm not familiar with. I don't have a lot of code, I'd like a simple Filter to pop up with a list of stores represented by checkboxes. It seems to work when I console.log to see my object state, except that the checkboxes don't visibly show a change until I close and then reopen the modal. Perhaps I'm misunderstanding how modals or state works. Example: I check box 1 and console log shows the state changed but the box still looks unchecked. I close the modal and reopen, box 1 is now checked.
import React, { useState } from "react";
import {
Button,
FlatList,
Modal,
StyleSheet,
TouchableOpacity,
View,
} from "react-native";
import AppText from "./AppText";
import { MaterialCommunityIcons } from "#expo/vector-icons";
import colors from "../config/colors";
import { CheckBox } from 'react-native-elements';
function FilterPicker({
filterStores,
setFilterStores,
}) {
const [modalVisible, setModalVisible] = useState(false);
function onCheckChanged(id) {
const index = filterStores.findIndex(x => x.id === id);
filterStores[index].checked = !filterStores[index].checked;
setFilterStores(filterStores);
console.log(JSON.stringify(filterStores));
}
return (
<>
<TouchableOpacity onPress={() => setModalVisible(true)}>
<View style={styles.Filter}>
<MaterialCommunityIcons
name="tune"
color={colors.black}
size={30}
style={styles.Icon}
/>
</View>
</TouchableOpacity>
<Modal visible={modalVisible} onRequestClose={()=> setModalVisible(false)} animationType="slide">
<View>
{
filterStores.map((item,key) => <CheckBox title={item.key} key={key} checked={item.checked} onPress={()=>onCheckChanged(item.id)}/>)
}
</View>
</Modal>
</>
);
}
export default FilterPicker;
try after modifying your onCheckChanged function like following.
you are mutating filterStores
function onCheckChanged(id) {
const index = filterStores.findIndex(x => x.id === id);
const newFilterStores = [...filterStores];
newFilterStores[index] = {...newFilterStores[index],checked:!newFilterStores[index].checked};
setFilterStores(newFilterStores);
console.log(JSON.stringify(newFilterStores));
}
In my opinion, because the state doesn't change so it doesn't re-render

React Native Scrollview: scroll to top on button click

So I have a component with ScrollView which contains many elements, so you have to scroll a long way down.
Now there should be a button at the bottom of the page that on click will scroll the page back to top.
I already created the button as a FAB (floating action button) in an extra component.
It is integrated in a parent component, where the ScrollView is located.
What I found was that you have to create a ref in the ScrollView component and implement a button right there that uses this ref to make scrolling work. Simplified, here is what I have so far:
imports ...
const ParentComponent: React.FC<Props> = () => {
const scroll = React.createRef();
return (
<View>
<ScrollView ref={scroll}>
<SearchResult></SearchResult> // creates a very long list
<FloatingButton
onPress={() => scroll.current.scrollTo(0)}></FloatingButton>
</ScrollView>
</View>
);
};
export default ParentComponent;
As you can see, there is the component FloatingButton with the onPress() method.
Here is the implementation:
import React, {useState} from 'react';
import {Container, Content, Button, Icon, Fab} from 'native-base';
const FloatingButton: React.FC<Props> = () => {
return (
<Fab
position="bottomRight"
onPress={(???}>
<Icon name="arrow-round-up" />
</Fab>
);
};
export default FloatingButton;
Now the problem: Where should I do the onPress() method? Because if I leave it in the parent component, it won't work because it is not directly located in the Fab (in FloatingButton). I would like to do the onPress() logic in Fab, but if I do so, the ScrollView that it needs is not available, because it's in the parent component. My idea was to maybe passing the ref as prop into FloatingButton, but for some reason this didn't work.
Can someone please help me?
You could either let the parent hook into the FloatingButton's onPress function or pass the ref down to the FloatingButton directly.
export const Parent : FC<ParentProps> = props => {
const scrollRef = useRef<ScrollView>();
const onFabPress = () => {
scrollRef.current?.scrollTo({
y : 0,
animated : true
});
}
return (
<View>
<ScrollView ref={scrollRef}>
{/* Your content here */}
</ScrollView>
<FloatingButton onPress={onFabPress} />
</View>
);
}
export const FloatingButton : FC<FloatingButtonProps> = props => {
const { onPress } = props;
const onFabPress = () => {
// Do whatever logic you need to
// ...
onPress();
}
return (
<Fab position="bottomRight" onPress={onFabPress}>
<Icon name="arrow-round-up" />
</Fab>
);
}
You should determine the horizontal or vertical value you want to scroll to, like this code snippet.
onPress={()=>
this.scroll.current.scrollTo({ x:0, y:0 });
}
Please have a look at my snack code. Hope it might be helpful for you.
https://snack.expo.io/#anurodhs2/restless-edamame

React Native: Not smooth scrolling with DrawerNavigator

Current Behavior
My code:
class App extends Component {
render() {
return <Drawer /> {/* rigid scrolling effect */}
return <Stack /> {/* smooth scrolling effect if I comment above return statement */}
}
}
const Drawer = DrawerNavigator({
Feed: { screen: Feed }
})
const Stack = StackNavigator({
Feed: { screen: Feed }
})
And Feed component's render is just a bunch of lines:
render() {
return <View style={{flex:1}}>
<ScrollView>
<Text>random line...</Text>
// .. more lines to make it scrollable
</ScrollView>
</View>
}
Expected Behavior
The expected behavior is to get smooth scrolling effect in both cases. However, DrawerNavigator screen's scrolling effect is extremely rigid. When I swipe my finger quickly from up to down, it doesn't keep scrolling smoothly automatically like it should in Stacknavigator example.
How to reproduce
Create App.js file above and create a simple Feed.js component which has a bunch of lines to make ScrollView work.
Can anybody help?
Update: Live demonstration: https://snack.expo.io/Hk8Np7nPG
Try this
render() {
return (
<View style={{flex:1}}>
<ScrollView>
<View>
{Your Contnet}
</View>
</ScrollView>
</View>
)}
it is worked for me...
hope it'll also worked for you
You can Use NativeBase with standard tabs Container and Contet (like ScrollView ) Header and...
first try :
npm install native-base --save
then:
npm i
react-native link
and here is your full example code:
import React, { Component } from 'react';
import { Content , View , Text } from 'native-base'; //don't need import 'react-native' components
export default class GeneralExample extends Component {
render() {
return (
<Content >
<View>
{Your Contnet}
</View>
</Content>
)}
}
and if you wanna change the speed just ScrollView try:
<ScrollView decelerationRate={0.5}>
<View/>
</ScrollView>
in NativeBase Use THIS LINK

React Native - how to scroll a ScrollView to a given location after navigation from another screen

Is it possible to tell a ScrollView to scroll to a specific position when we just navigated to the current screen via StackNavigator?
I have two screens; Menu and Items. The Menu is a list of Buttons, one for each item. The Items screen contain a Carousel built using ScrollView with the picture and detailed description of each Item.
When I click on a button in the Menu screen, I want to navigate to the Items screen, and automatically scroll to the Item that the button represent.
I read that you can pass in parameters when using the StackNavigator like so: but I don't know how to read out that parameter in my Items screen.
navigate('Items', { id: '1' })
So is this something that is possible in React Native and how do I do it? Or perhaps I'm using the wrong navigator?
Here's a dumbed down version of my two screens:
App.js:
const SimpleApp = StackNavigator({
Menu: { screen: MenuScreen},
Items: { screen: ItemScreen }
}
);
export default class App extends React.Component {
render() {
return <SimpleApp />;
}
}
Menu.js
export default class Menu extends React.Component {
constructor(props){
super(props)
this.seeDetail = this.seeDetail.bind(this)
}
seeDetail(){
const { navigate } = this.props.navigation;
navigate('Items')
}
render(){
<Button onPress={this.seeDetail} title='1'/>
<Button onPress={this.seeDetail} title='2'/>
}
}
Items.js
export default class Items extends React.Component {
render(){
let scrollItems = [] //Somecode that generates and array of items
return (
<View>
<View style={styles.scrollViewContainer}>
<ScrollView
horizontal
pagingEnabled
ref={(ref) => this.myScroll = ref}>
{scrollItems}
</ScrollView>
</View>
</View>
)
}
}
P.S I am specifically targeting Android at the moment, but ideally there could be a cross-platform solution.
I read that you can pass in parameters when using the StackNavigator like so: but I don't know how to read out that parameter in my Items screen.
That is achieved by accessing this.props.navigation.state.params inside your child component.
I think the best time to call scrollTo on your scrollview reference is when it first gets assigned. You're already giving it a reference and running a callback function - I would just tweak it so that it also calls scrollTo at the same time:
export default class Items extends React.Component {
render(){
let scrollItems = [] //Somecode that generates and array of items
const {id} = this.props.navigation.state.params;
return (
<View>
<View style={styles.scrollViewContainer}>
<ScrollView
horizontal
pagingEnabled
ref={(ref) => {
this.myScroll = ref
this.myScroll.scrollTo() // !!
}>
{scrollItems}
</ScrollView>
</View>
</View>
)
}
}
And this is why I use FlatLists or SectionLists (which inherit from VirtualizedList) instead of ScrollViews. VirtualizedList has a scrollToIndex function which is much more intuitive. ScrollView's scrollTo expects x and y parameters meaning that you would have to calculate the exact spot to scroll to - multiplying width of each scroll item by the index of the item you're scrolling to. And if there is padding involved for each item it becomes more of a pain.
Here is an example of scroll to the props with id.
import React, { Component } from 'react';
import { StyleSheet, Text, View, ScrollView, TouchableOpacity, Dimensions, Alert, findNodeHandle, Image } from 'react-native';
class MyCustomScrollToElement extends Component {
constructor(props) {
super(props)
this.state = {
}
this._nodes = new Map();
}
componentDidMount() {
const data = ['First Element', 'Second Element', 'Third Element', 'Fourth Element', 'Fifth Element' ];
data.filter((el, idx) => {
if(el===this.props.id){
this.scrollToElement(idx);
}
})
}
scrollToElement =(indexOf)=>{
const node = this._nodes.get(indexOf);
const position = findNodeHandle(node);
this.myScroll.scrollTo({ x: 0, y: position, animated: true });
}
render(){
const data = ['First Element', 'Second Element', 'Third Element', 'Fourth Element', 'Fifth Element' ];
return (
<View style={styles.container}>
<ScrollView ref={(ref) => this.myScroll = ref} style={[styles.container, {flex:0.9}]} keyboardShouldPersistTaps={'handled'}>
<View style={styles.container}>
{data.map((elm, idx) => <View ref={ref => this._nodes.set(idx, ref)} style={{styles.element}}><Text>{elm}</Text></View>)}
</View>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexGrow: 1,
backgroundColor:"#d7eff9"
},
element:{
width: 200,
height: 200,
backgroundColor: 'red'
}
});
export default MyCustomScrollToElement;
Yes, this is possible by utilising the scrollTo method - see the docs. You can call this method in componentDidMount. You just need a ref to call it like: this.myScroll.scrollTo(...). Note that if you have an array of items which are all of the same type, you should use FlatList instead.
For iOS - the best way to use ScrollView's contentOffset property. In this way it will be initially rendered in a right position. Using scrollTo will add additional excess render after the first one.
For Android - there is no other option rather then scrollTo