React Native - Focus a TextInput (Inside custom Component) that is inside of a Flat List element - react-native

I'm a React Native beginner and I'm working with a Flat List and custom Rows. Inside of each custom row, I have elements like Text, TextInput, and Button. The problem is that I need to press one of these buttons that enables and triggers a focus to the TextInput.
I tried implementing that with refs but everything freezes so I don't know how to do that correctly.
My constructor
class EditProfileScreen extends React.Component {
constructor(props) {
super(props);
this._rowRefs = {}
}
My FlatList and MyCustomRow
_renderItem = ({item, index}) => {
return (
<MyCustomRow
fieldname={item.fieldname}
value={item.value}
isEditable={item.isEditable}
editFieldHandler={ this.editFieldHandler }
allowsEdition={item.allowsEdition}
ref={ref => { this._rowRefs[index] = ref}}
/>
)
}
_keyExtractor = (item, index) => index.toString();
render() {
return (
<View style={styles.mainContainer}>
<FlatList
ref={this.flatListRef}
style={styles.flatList}
data={this.state.fields}
renderItem={this._renderItem}
keyExtractor= {this._keyExtractor}
extraData={this.state.refresh}
/>
</View>
);
}
Trying to print the refs:
enableField(name) {
console.log('Printing refs...')
console.log(this._rowRefs) // the Button pressed freezes here
}
I expect to see what's inside of _rowRefs, but instead of that, the button just gets frozen and I never see that result in the console

Try to give the key for each ref (not index)
ref={ref => { this._rowRefs['key1'] = ref}}
and invoke focus action like this:
this._rowRefs['key1'].focus()

Can you try the above code after removing
console.log(this._rowRefs)

Related

React native changing button color not working properly

I have my reusable component for Button :
import React, { Component } from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { styles } from "./styles";
class TransactionFees extends Component {
state = {
pressed: false,
};
render() {
const { pressed } = this.state;
const { speed, eth, usd } = this.props;
return (
<View>
<TouchableOpacity style={ pressed ? styles.pressedButton : null } onPress={() => this.setState({ pressed: !pressed })}>
<Text style={styles.transactionFeeTitle}>{speed}</Text>
<Text style={styles.transactionFeeSubText}>{eth} ETH</Text>
<Text style={styles.transactionFeeSubText}>$ {usd} </Text>
</TouchableOpacity>
</View>
);
}
}
export default TransactionFees;
This is how I use it in another component :
<View style={styles.transactionFeeChoices}>
<TransactionFees speed={"Slow"} eth={"0.000010"} usd={"0.02"} />
<TransactionFees speed={"Average"} eth={"0.000030"} usd={"0.03"} />
<TransactionFees speed={"Fast"} eth={"0.000050"} usd={"0.04"} />
</View>
When i press the button the background color changes to blue and the problem is When I click on second button,the first button is not going to default background color
Any solutions on how to solve this please?
The way your TransactionFees component is currently created means that each TransactionFees occurrence has its own internal state. So when one TransactionFees changes, that doesn't mean the others are automatically updated. You can see them as autonomous components.
In your case however, the TransactionFees occurrences shouldn't be automomous, a change in one occurence should reflect in the others. This is a classic react pattern where the most common solution is to "lift the state up" from the TransactionFeeds component into the parent. There even is a react article about in the official docs
Steps to take
have one state variable in the parent that keeps the selected TransactionFees
pass in pressed as a prop to the TransactionFees occurrences
pass in a change handler function that the TransactionFees component can call when one of the buttons is pressed
In code, this will be more or less:
class Manager extends Component {
constructor(props) {
super(props);
this.state = {
selected: '',
};
}
onChangeSelection = selected => {
this.setState({
selected,
});
}
render() {
const { selected } = this.state;
return (
<View style={styles.transactionFeeChoices}>
<TransactionFees speed={"Slow"} eth={"0.000010"} usd={"0.02"} pressed={selected === 'Slow'} onPress={() => this.onChangeSelection('Slow')} />
<TransactionFees speed={"Average"} eth={"0.000030"} usd={"0.03"} pressed={selected === 'Average'} onPress={() => this.onChangeSelection('Average')} />
<TransactionFees speed={"Fast"} eth={"0.000050"} usd={"0.04"} pressed={selected === 'Fast'} onPress={() => this.onChangeSelection('Fast')} />
</View>
);
}
}
class TransactionFees extends Component {
render() {
const { speed, eth, usd, pressed, onPress } = this.props;
return (
<View>
<TouchableOpacity style={ pressed ? styles.pressedButton : null } onPress={onPress}>
<Text style={styles.transactionFeeTitle}>{speed}</Text>
<Text style={styles.transactionFeeSubText}>{eth} ETH</Text>
<Text style={styles.transactionFeeSubText}>$ {usd} </Text>
</TouchableOpacity>
</View>
);
}
}
You have created 3 instances of TransactionFees and they will have their own separate state.
That's why click on the second button does not change the state of the first button.
If you want to make either of button-click trigger that style change, you need to use shared value between all those TransactionFees instances.
There can be 2 ways to do this depending on where to store that value
You can store it as a state of the parent component and pass it down to TransactionFees component
You can store it in redux store and use it inside TransactionFees by connecting the component to the store.
To do so
You need to keep the stat in the parent
And pass to each button the function to change the state
And move the current stat to each button
like this
<TransactionFees speed={"Slow"}
setClicked(()->{
this.setState({clicked:true})
clicked={this.state.clicked}
eth={"0.000010"} usd={"0.02"}
/>

Warning while using ListItem, FlatList inside ScrollView

I get this warning when I tried using Flatlist inside View but same error occurs:
VirtualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead.
The component I am using FlatList inside is:
class About extends Component
{
constructor(props)
{
super(props);
this.state = {
leaders: LEADERS
};
}
render(){
const renderLeaders = ({item,index}) => {
return(
<ListItem
key = {index}
title = {item.name}
subtitle = {item.description}
hideChevron = {true}
leftAvatar = {{ source: require('./images/alberto.png') }}
/>
);
};
return(
<ScrollView>
<History />
<Card title = "Corporate Leadership">
<FlatList
data = {this.state.leaders}
renderItem = {renderLeaders}
keyExtractor = {item => item.id.toString()}
/>
</Card>
</ScrollView>
);
}
}
And can I use leader in place of item here? I tried using it but there was an error.
one way to hack this would be to use react-native-gesture-handler flatlist
it will sort of take over your parenting ScrollView if you absolutely need to have nested scrollers, at least that worked for me when i had this same problem.
As per second part, without delving too deep of what is what, try reading this https://www.barrymichaeldoyle.com/destructuring/#:~:text=Renaming%20Variables%20while%20Destructuring&text=In%20order%20to%20rename%20a,rename%20the%20destructured%20variable%20to or google any other theory on 'destructuring renaming'
if you absolutely need to rename 'item'.

Best method to optimize performance of FlatList Items

This a simple FlatList:
class Products ..
render() {
return (
<FlatList
renderItem={this._renderItem}
);
}
I want to create a list of items and navigate to Detail Page by onPress items.
Can Please tell me which method is better?
Method 1:
Insert navigate to Detail page in child component(CardProduct component) like this:
_renderItem = ({item}) => (
<CardProduct
id={item.id}
title={item.title}
/>
);
and in CardProduct component:
render() {
const { id,title } = this.props;
return (
<Card style={{flex:1}}>
<CardItem cardBody button onPress={() => this.props.navigation.navigate('Details',{productId:id})}>
...
);
}
Method 2:
Insert navigate to Detail page in current component(Products component) like this:
_onPressItem = (id: string) => {
this.props.navigation.navigate('Details',{productId:id});
};
_renderItem = ({item}) => (
<CardProduct
id={item.id}
title={item.title}
onPressItem={this._onPressItem}
/>
);
and in CardProduct component:
_onPress = () => {
this.props.onPressItem(this.props.id);
};
render() {
const { id,title } = this.props;
return (
<Card style={{flex:1}}>
<CardItem cardBody button onPress={this._onPress}>
...
);
}
I used to do the method 1, but I read this guide.
Short answer:
You should go for method2.
Explanation:
In method1 you are using an arrow function in CardItem's onPress, so everytime CardProduct is re-rendered a new reference of onPress is created, which forces CardItem to re-render, even if all the other props are staying the same. In method 2 you are binding the function to context, which won't force a re-rendering of the CardItem.
By the way, in general it is a good idea to prevent the usage of arrow functions in render().
One step for performance optimization in react-native flatlist, is using a stateless functional component for the renderItem. and you should always give each item a unique key.

Adding a button in a FlatList in React Native

I have a FlatList and I am trying to but a button below the items and when you click on the button it should display the name of the item in an alert.
class TopMovies extends React.Component {
constructor(props) {
super(props);
this.state = {
apiTopLoaded: false,
topPopularMovies: [],
}
this.conditionalTopPopular = this.conditionalTopPopular.bind(this);
this.mybuttonclick = this.mybuttonclick.bind(this);
}
componentDidMount() {
fetch('someurls')
.then((response)=>{
response.json()
.then((popularMovies) => {
this.setState({
apiTopLoaded: true,
topPopularMovies: popularMovies.results,
})
})
})
mybuttonclick() {
Alert.alert(item.original_title)
}
conditionalTopPopular() {
if(this.state.apiTopLoaded===true) {
return(
<FlatList
horizontal={true}
data={this.state.topPopularMovies}
renderItem={({ item }) => (
<View>
<Text>{item.original_title}</Text>
<Button onPress={this.mybuttonclick} title="hello"/>
</View>
)}
keyExtractor={item => item.id}
/>
</View>
)
}
}
I can see all the movie names in the list and I see buttons below the movie names, but when I click on it, it says "cant find variable item". The function mybuttonclick should alert the item.original_title prop because it displays correctly in the flatlist. Please help.
Change your function like this:
mybuttonclick(movieTitle) {
Alert.alert(movieTitle)
}
And pass in the the movie title like this:
<Button onPress={this.mybuttonclick(item.original_title)} title="hello"/>
You should use the fat arrow function in the onPress like this.
<Button onPress={() => this.mybuttonclick(item.original_title)} title="hello"/>

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