I am using FlatList to render items. Each item is a separate card style component. Each item has onPress event handler which changes the component.
Here is my Flatlist.
<FlatList
data={data}
renderItem={({ item }) => {
return <CardItem courseData={item} />
}}
ref={this.flatList}
keyExtractor={
(item) => { return item.content_address }
}
initialNumToRender={10}
showsVerticalScrollIndicator={false}
style={{ marginTop: 50 }}
/>
Here is the CardItem Component
constructor{
this.state = {change:false}
}
_onPress = () => {
this.setState({change: true})
}
render() {
if (this.state.change) {
return (//return changes)
} else {
return (
<TouchableOpacity
ref="myRef"
activeOpacity={0.5}
onPress={this._onPress}>
...
</TouchableOpacity>
)
}
}
Now what I want is to have only one card component changed at a time.
So when a user touches on 1st card component, it should change. But when a user touches 2nd card component, 1st should change back to the previous state and 2nd should change.
I saw FlatList documentation here but not sure which methods can help me?
If you store your toggled item in parent state you can check and render accordingly. Also storing toggled value in child state will cause a bug where if the item moves enough off to the screen it will be unmounted and the internal state of the child component will be reset. This would cause undesired toggle in your list. Storing state in parent component will help to overcome this issue.
Example
class App extends Component {
constructor() {
this.state = { toggledItem: null }
}
onPressItem = (itemId) => {
this.setState({toggledItem: itemId})
}
render() {
<FlatList
data={data}
renderItem={({ item }) => {
return <CardItem
courseData={item}
onPress={this.onPressItem}
toggeled={item.id === this.state.toggledItem}
/>
}}
ref={this.flatList}
keyExtractor={
(item) => { return item.content_address }
}
initialNumToRender={10}
showsVerticalScrollIndicator={false}
style={{ marginTop: 50 }}
/>
}
}
class CardItem extends Component {
_onPress = () => {
this.props.onPress(this.props.courseData.id)
}
render() {
if (this.props.toggeled) {
return (//return changes)
} else {
return (
<TouchableOpacity
ref="myRef"
activeOpacity={0.5}
onPress={this._onPress}>
...
</TouchableOpacity>
)
}
}
}
Related
I am rendering a list in React Native which currently has about 900 list items. I'm mapping through the list and rendering one component for each item. It currently takes about 3 seconds for React to do this which is unacceptable - I would like it to be near instant. Props are passed to the list item component from the redux store and the list items are nested inside the React Native ScrollView Component.
How can I can I increase the performance of rendering these components so there is not such a huge lag?
Here is my Contacts component:
class Contacts extends Component {
renderRegisteredUsers = (contacts) => {
return contacts.items.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.user.address_book_name}
surname={''}
phoneNumber={contact.user.phone}
isRegisteredUser={true}
ccId={contact.user.id}
/>
))
}
renderContacts = (contacts) => {
if (contacts) {
return contacts.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.firstName}
surname={contact.surname}
phoneNumber={contact.phoneNumber}
isRegisteredUser={false}
/>
))
} else {
return (
<>
<Loader />
</>
)
}
}
render() {
return (
<>
<ScrollView>
<Text style={{ fontSize: 22 }}>
Your Contacts Using Fleeting
</Text>
{this.renderRegisteredUsers(this.props.user.registeredContacts)}
<Text style={{ fontSize: 22 }}>
Phone Contacts
</Text>
{this.renderContacts(this.props.user.parsedContacts)}
</ScrollView>
</>
)
}
}
const mapStateToProps = (state) => {
const { user } = state;
return { user }
};
export default connect(mapStateToProps)(Contacts);
And my ContactListItem component:
class ContactListItem extends Component {
constructor(props) {
super(props)
}
handleOnClick = () => {
this.props.calleeId(this.props.ccId)
Actions.TimeInput();
}
render() {
return (
<View style={{ margin: 20, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
<Text>
{this.props.firstName + ' ' + this.props.surname + ' ' + this.props.phoneNumber}
</Text>
<Icon name="adduser" size={40} color="green" style={{ alignSelf: "flex-end" }} onPress={this.handleOnClick} />
</View>
)
}
}
const mapDispatchToProps = {
calleeId,
};
export default connect(null, mapDispatchToProps)(ContactListItem);
Thanks in advance.
1- You can use PureComponent instead of Component. PureComponent re-renders only when its props change and not re-rendering on each parent re-render. More Information: https://reactjs.org/docs/react-api.html#reactpurecomponent
2- Use unique keys when you're mapping on your items.
3- You can use FlatList instead of ScrollView. It supports Scroll Loading. You can set a number of initial numbers and render the others on scroll. More Information: https://facebook.github.io/react-native/docs/flatlist
const renderItem = ({ item }) => (<Text key={item.key}>{item.key}</Text>);
const getItemLayout = (data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
);
const items = [{ key: 'first' }, { key: 'second'}, ...+1000];
function render () => (
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={getItemLayout}
initialNumToRender={5}
maxToRenderPerBatch={10}
windowSize={10}
/>
);
Look into using pagination instead- https://www.npmjs.com/package/react-paginate, https://www.npmjs.com/package/react-native-pagination. If your contacts are coming from an api request, you will have to look into paginating the requests too.
import React, { Component } from 'react';
import {AppRegistry,StyleSheet,View,FlatList,} from 'react-native';
import ContactItem from './Pages/widgets/ContactItem'; // https://github.com/garrettmac/react-native-pagination/blob/master/ReactNativePaginationExample/Pages/widgets/ContactItem.js
import faker from 'faker';//assuming you have this.
import _ from 'lodash';
import Pagination,{Icon,Dot} from 'react-native-pagination';//{Icon,Dot} also available
export default class ReactNativePaginationExample extends Component {
constructor(props){
super(props);
this.state = {
items: this.props.contacts,
};
}
//create each list item
_renderItem = ({item}) => {
return (<ContactItem index={item.id}
onPressItem={this.onPressItem.bind(this)}
name={item.name}
avatar={item.avatar}
description={item.email}
tag={item.group}
createTagColor
/>)
};
//pressed an item
onPressItem = (item) => console.log("onPressItem:item ",item);
_keyExtractor = (item, index) => item.id;
onViewableItemsChanged = ({ viewableItems, changed }) =>this.setState({viewableItems})
render() {
return (
<View style={[s.container]}>
<FlatList
data={this.state.items}
ref={r=>this.refs=r}//create refrence point to enable scrolling
keyExtractor={this._keyExtractor}//map your keys to whatever unique ids the have (mine is a "id" prop)
renderItem={this._renderItem}//render each item
onViewableItemsChanged={this.onViewableItemsChanged.bind(this)}//need this
/>
<Pagination
// dotThemeLight //<--use with backgroundColor:"grey"
listRef={this.refs}//to allow React Native Pagination to scroll to item when clicked (so add "ref={r=>this.refs=r}" to your list)
paginationVisibleItems={this.state.viewableItems}//needs to track what the user sees
paginationItems={this.state.items}//pass the same list as data
paginationItemPadSize={3} //num of items to pad above and below your visable items
/>
</View>
)
}
};
const s = StyleSheet.create({
container: {
flex: 1,
// backgroundColor:"grey",//<-- use with "dotThemeLight"
},
});
AppRegistry.registerComponent('ReactNativePaginationExample', () => App);
I want to call a function inside an imported render class. I tried the following but no success.
class1.js
import class2 from "./class2";
export default class1 MainCategoriesScreen extends React.PureComponent {
renderItem({ item }) {
return <Class2 product={item}/>
}
changeCategoryId(id){
console.log(id);
}
render() {
return (
<FlatList
data={this.state.products}
renderItem={this.renderItem}
...
}
and for class2
render() {
return (
<Card style={{flex:1}}>
<CardItem cardBody button
onPress={this.changeCategoryId(product.id)}>
...
</CardItem>
...
}
export default withNavigation(class2 );
Also I tried these:
this.changeCategoryId(product.id)
this.changeCategoryId(product.id)
this.changeCategoryId.bind(product.id)
this.props.changeCategoryId(product.id)
You can pass the changeCategoryId method from class 1 to class 2 as a prop, and then call it like this.props.changeCategoryId():
// class 1
constructor(props) {
super(props);
...
this.renderItem = this.renderItem.bind(this);
this.changeCategoryId = this.changeCategoryId.bind(this);
}
renderItem({ item }) {
return <Class2 product={item}
changeCategoryId={this.changeCategoryId}/>
}
// class 2
render() {
return (
<Card style={{flex:1}}>
<CardItem cardBody button
onPress={this.props.changeCategoryId(product.id)}>
...
</CardItem>
...
Note that you need to bind both changeCategoryId and renderItem in class 1.
I recently had the same issue, just do this:
export default class1 MainCategoriesScreen extends React.PureComponent {
renderItem = ({ item }) => {
return <Class2 product={item} onPress={this.myfunction} />
}
myfunction = (id) => {
console.log(id);
}
render() {
return (
<FlatList
data={this.state.products}
renderItem={this.renderItem}
...
and
render() {
return (
<Card style={{flex:1}}>
<CardItem cardBody button
onPress={() => this.props.onPress(product.id)}>
...
</CardItem>
...
You can also try this:
renderItem = ({ item }) => {
return <Class2 product={item} onPress={id => this.myfunction(id)} />
}
myfunction(id) {
console.log(id);
}
renderItem({ item }) {
return <Class2 product={item}/>
}
You appear to be missing passing the prop into Class2, which will handle the changeCategoryId.
renderItem({ item }) {
return <Class2 changeCategoryId={this.changeCategoryId} product={item}/>
}
This means, Class2 will now have access to a prop, called changeCategoryId which will be Class1's changeCategoryId function.
Then in the render function within your Class2, you can do:
<CardItem cardBody button
onPress={() => this.props.changeCategoryId(product.id)}>
...
I have subdivided my components and I want to change state of text using deleteName function from child component. However I have used onPress={this.props.delete(i)} in my child component which is not working. The error that occurs for me is:
undefined variable "I"
Here is my code:
App.js
export default class App extends Component {
state = {
placeName: '',
text: [],
}
changeName = (value) => {
this.setState({
placeName: value
})
}
deleteName = (index) => {
this.setState(prevState => {
return {
text: prevState.text.filter((place, i) => {
return i!== index
})
}
}
}
addText = () => {
if (this.state.placeName.trim === "") {
return;
} else {
this.setState(prevState => {
return {
text: prevState.text.concat(prevState.placeName)
};
})
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<Input changeName={this.changeName}
value={this.state.placeName} />
<Button title="Send" style={styles.inputButton}
onPress={this.addText} />
</View>
<ListItems text={this.state.text} delete={this.deleteName}/>
{/* <View style={styles.listContainer}>{Display}</View> */}
</View>
);
}
}
and child component ListItems.js
const ListItems = (props) => (
<View style={styles.listitems}>
<Text>{this.props.text.map((placeOutput, i) => {
return (
<TouchableWithoutFeedback
key={i}
onPress={this.props.delete(i)}>
onPress={this.props.delete}
<ListItems placeName={placeOutput}/>
</TouchableWithoutFeedback>
)
})}
</Text>
</View>
);
You need to bind the index value at the point of passing the props to the child.
delete = index => ev => {
// Delete logic here
}
And in the render function, you can pass it as
items.map((item, index) => {
<ChildComponent key={index} delete={this.delete(index)} />
})
In your child component, you can use this prop as
<button onClick={this.props.delete}>Click me</button>
I have created a Sandbox link for your reference
Instead of onPress={this.props.delete(i)}, use onPress={() => this.props.delete(i)}
In order to have the cleaner code, you can use a renderContent and map with }, this);like below. Also you need to use: ()=>this.props.delete(i) instead of this.props.delete(i) for your onPress.
renderContent=(that)=>{
return props.text.map((placeOutput ,i) => {
return (
<TouchableWithoutFeedback key={i} onPress={()=>this.props.delete(i)}>
onPress={this.props.delete}
</TouchableWithoutFeedback>
);
}, this);
}
}
Then inside your render in JSX use the following code to call it:
{this.renderContent(this)}
Done! I hope I could help :)
I'm passing a function from Parent to Children components using FlatList but for some reason, the error occurs that undefined is not a function referring to updateState function.
Here's the parent component:
class Home extends Component {
/*
data items here
*/
updateState(data) {
this.setState({ data: data });
}
renderItem() {
return (
<ItemView updateParentState={ (data) => this.updateState(data) } />
)
}
render() {
return (
<FlatList
style={styles.fragmentContainer}
data={this.state.items}
keyExtractor={(item, index) => index.toString()}
renderItem={this.renderItem} />
)
}
}
Here's the child view:
class ItemView extends Component {
constructor(props) {
super(props);
this.state = {
data: "some data"
};
}
render() {
return (
<TouchableOpacity onPress={ () => this.props.updateParentState(this.state.data) }>
<Text>Item name here</Text>
</TouchableOpacity>
)
}
}
This should fix your problem:
render() {
return (
<FlatList
style={styles.fragmentContainer}
data={this.state.items}
keyExtractor={(item, index) => index.toString()}
renderItem={this.renderItem.bind(this)} />
)
}
You should always use the .bind(this) function if you're going to pass a function as props so that this does not get lost. Or you could use the arrow function if you're not a fan of .bind.
Ex:
{() => this.renderItem()}
I'm building search page similar to Facebook or instagram. Basically if we press search button, it navigates to 'SearchScreen'. When its component is mounted, I want to set the search header is focused (cursor).
My problem is when I set TextInput ref as a prop. And I'm getting Stateless function components cannot have refs error. Is this right approach? Why is it not working? Do you know any better approach other than this?
I added _renderHeader private function to renderHeader props in FlatList.
This is _renderHeader
_renderHeader = () => {
return (
<View style={styles.layoutheader}>
<View style={styles.containerheader}>
<RkTextInput
rkType='row'
ref="sbar" /////////////////////HERE////////////
autoCapitalize='none'
autoCorrect={false}
label={<RkText rkType='awesome' style={{color:'white'}}>{FontAwesome.search}</RkText>}
placeholder='Search'
underlineWidth="1"
underlineColor="white"
style={styles.searchBarheader}
inputStyle={{color:'white'}}
labelStyle={{marginRight:0}}
value={this.state.inputText}
onChangeText={(inputText)=>{this.setState({inputText})}}
/>
<View style={styles.left}>
<RkButton
rkType='clear'
style={styles.menuheader}
onPress={() => {
this.props.navigation.goBack()
}}>
<RkText style={styles.titleText} rkType='awesome hero'>{FontAwesome.chevronLeft}</RkText>
</RkButton>
</View>
</View>
</View>
)
}
componentDidMount() {
this.refs.sbar.focus(); ////////// Here I want to focus RkTextInput when it's loaded
}
UPDATE here is actual code as requested
class SearchScreen extends Component {
static navigationOptions = ({navigation}) => ({
header: null
})
state = {
active: false,
inputText: ''
}
...
_renderRow = (row) => {
...
);
}
_renderHeader = () => {
...
}
render() {
return (
<FlatList
data={null}
renderItem={this._renderRow}
renderHeader={this._renderHeader}
keyExtractor={this._keyExtractor}
ListHeaderComponent={this._renderHeader}
/>
);
}
componentDidMount() {
this.refs.sbar.focus();
}
}
What seems to me is that you are not using the refs the right way. The way you are using them has been deprecated. You should follow this syntax:
<input
type="text"
ref={(input) => { this.textInput = input; }}
/>
and when you want to access it you can do this.textInput. In your case, this.textInput.focus().
You're using RkTextInput which is a functional component and it cannot have a ref. That's why you can't focus it.
I don't see a way to focus the input other than wrapping the Component, getting the ref of the root and finding your input element in order to focus it. A rough example :
class RoughExample extends React.Component {
componentDidMount() {
//find the input from your root
this.input = this.root.querySelector('input');
//if it exists, focus
this.input && this.input.focus();
}
render() {
<div ref={ (node) => {this.root = node;} }>
<RkTextInput />
</div>
}
}