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
Related
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>
)
}
I'm using react native navigation bottom tabs, and I want to create a custom "bottom sheet" popup component, but I want that component to come over the bottom tabs. Does anybody know how to position elements on top of the bottom tabs?
Yes! You have to use react-native-portalize. Just wrap the elements you want to be rendered on top in a <Portal></Portal>. This will place it above a Bottom Tab navigator.
import { Portal } from 'react-native-portalize';
const FooterButton = () => {
return(
<Portal>
<View>
<Text>I appear above the Tab Navigator!</Text>
</View>
</Portal>
);
export default FooterButton;
Don't forget to wrap the whole app in the the Host:
//In app.js
import { Host } from 'react-native-portalize';
const App = () => {
return (
<Host>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</Host>
)
}
export default App;
NOTE: The elements inside the Portal, do not clear when the navigator navigates to another screen. So to get around this, you have to only display the Portal, when the screen is active. Thankfully React Navigation 5+ provides a useIsFocused hook that accomplishes this perfectly.
import { Portal } from 'react-native-portalize';
import { useIsFocused } from '#react-navigation/native';
const FooterButton = () => {
const isFocused = useIsFocused();
// Only display the button when screen is focused. Otherwise, it doesn't go away when you switch screens
return isFocused ? (
<Portal>
<View style={styles.buttonContainer}>
<View style={styles.footer}>{props.children}</View>
</View>
</Portal>
) : null;
};
export default FooterButton;
If you want a modal-style popup, you can wrap react-native-modalize and wrap it with react-native-modalize
Thanks to livin52 on Reddit for the solution
I have this problem with ios but not with android. It only disturb the add task input the task edit and the list name edit. The input addList(It's the one with "What to do?" on the draw) in the header works fine.
UI drawing
Achitecture of components
I console log my component and I can see it rerender everytime I add a letter in the input field.
I checked on google and follow this:(can we link other website here?) https://www.codegrepper.com/code-examples/javascript/react+native+textinput+lost+focus+after+charter+type
Tried the the first solution with onBlurr and onFocus.
I tried to make a TextInput component for add task.
I even try with my component addList but it didn't solve the problem.
Anyone have faced this problem before? Is there anyway to by pass this?
My code without the import/style look like this:
const TaskList: FunctionComponent<TasksListProps> = ({
addTask,
deleteTask,
toggleTask,
editTaskName,
...props
}) => {
console.log('props', props);
const [nameOfTask, setNameOfTask] = useState('');
console.log('name', nameOfTask);
const textHandler = (enteredName: string) => {
setNameOfTask(enteredName);
};
const handleSubmitTask = () => {
if (nameOfTask === '') {
return;
}
addTask(props.listId, nameOfTask);
setNameOfTask('');
};
return (
<View style={styles.tasksListContainer}>
{props.tasks.map(task => (
<SingleTask
key={task.id}
task={task}
listId={props.listId}
deleteTask={deleteTask}
toggleTask={toggleTask}
editTaskName={editTaskName}
/>
))}
<View style={styles.taskInputContainer}>
<TextInput
style={styles.tasksTextInput}
value={nameOfTask}
onChangeText={textHandler}
placeholder="Write a task to do"
/>
<TouchableOpacity onPress={handleSubmitTask}>
<Image source={require('./Img/add-button.png')} />
</TouchableOpacity>
</View>
</View>
);
};
You can create a HOC and wrap your screen width DismissKeyboard
import { Keyboard } from 'react-native';
const DismissKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
That because Re render.
Try to make the input with the main component of the page to test it.
Then check where the error with re-render
<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
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