FlatList is not re-rendering on the state change - javascript

On pressing the button(ButtonComponent), the state of the button gets changed. Now I'm passing that state to the <FlatListItem> (a child component ) inside <FlatList>. Depending on that status, the each item in the <FlatList> should be re-arranged.
I just got to know about this extraData prop but not sure how to make use of it in the code.
It's not something new but it is like simple checkbox implementation.
Everything is working fine but when I press the select all button all the remaining select buttons are not getting toggled to selected.
class FlatListItem extends Component{
constructor(props){
super(props)
const{ isSelected }=this.props
this.state={
selectedStatus:isSelected,
}
}
changeSelectStatus=(key)=>{
this.setState({selectedStatus:!this.state.selectedStatus});
return key;
}
render(){
return(
<View style={{flex:1,
flexDirection:'row',
backgroundColor:'white'}}>
<View>
<Image
source={{uri:this.props.item.imageUri}}
style={{width:50, height:50, margin:5}}>
</Image>
</View>
<View>
<Text style={{color:'black', padding:10, fontSize:16}}>{this.props.item.name}</Text>
</View>
<View style={{flex:1, alignItems:'flex-end', paddingRight:-10}}>
{this.state.selectedStatus?
**<ButtonComponent buttonColor={"black"} buttonTextColor={"white"} fullRounded={true}
borderHighlight={true} buttonWidth={70} buttonHeight={30}
onPress={()=>this.props.showSelected(this.changeSelectStatus(this.props.item.key)) }>
Selected
</ButtonComponent>
:
<ButtonComponent buttonColor={"white"} buttonTextColor={"black"} fullRounded={true}
borderHighlight={true} buttonWidth={70} buttonHeight={30}
onPress={()=>this.props.showSelected(this.changeSelectStatus(this.props.item.key)) }>
Select
</ButtonComponent>
}
</View>
</View>
)
}
}
export default class SelectMembersBody extends Component {
constructor(props){
super(props)
this.state={
selectedButtons:[],
selectAllBtnStatus:false,
}
}
selectAllMembers=()=>{
let allMembers=[];
if(!this.state.selectAllBtnStatus){
membersData.forEach(element => {
if(!this.state.selectedButtons.includes(element.key))
allMembers.push(element.key)
});
this.setState({
selectAllBtnStatus:!this.state.selectAllBtnStatus,
selectedButtons:[...this.state.selectedButtons, allMembers]
})
}
else{
this.setState({
selectAllBtnStatus:!this.state.selectAllBtnStatus,
selectedButtons:[...allMembers]
})
}
}
showSelected=(callback)=>{
let val = callback;
if(!this.state.selectedButtons.includes(val))
this.setState({selectedButtons:[...this.state.selectedButtons, val]});
else{
let newMarkers=[...this.state.selectedButtons]
let index = newMarkers.indexOf(val);
if (index >= 0) {
newMarkers.splice( index, 1 );
}
this.setState({selectedButtons:newMarkers});
}
}
render(){
return(
<View style={{flex:1, }}>
<Text>{this.state.selectedButtons}</Text>
<View>
{this.state.selectAllBtnStatus?
<ButtonComponent buttonColor={"black"} buttonTextColor={"white"} fullRounded={true}
borderHighlight={true} buttonWidth={85} buttonHeight={30} onPress={this.selectAllMembers}>
Selected All
</ButtonComponent>
:
<ButtonComponent buttonColor={"white"} buttonTextColor={"black"} fullRounded={true}
borderHighlight={true} buttonWidth={85} buttonHeight={30} onPress={this.selectAllMembers}>
Select All
</ButtonComponent> }
</View>
<FlatList data={membersData} extraData={this.state}
renderItem={({item, index})=>{
return(
<View>
<FlatListItem item={item} index={index} isSelected={this.state.selectAllBtnStatus} showSelected={this.showSelected} ></FlatListItem>
</View>
)
}
}></FlatList>
</View>
)
}
}

I know that it's quite harder to step into someone's shoes. The code I provided in my question might not be that effective since I'm a learner. That's why it is uncomfortable to get on to the code flow. So, I decided to answer my own question after reading some articles, docs and similar questions on StackOverFlow.
So, my question is why the flatlist was not getting re-rendered on setState. To be more precise, if you've gone through the pictures above, on selecting the Select All button all the buttons in the items should be toggled to selected state.
To make the flatlist re-render, we need to add an additional prop "extraData"
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
You can get it's full documentation here.
In my case, I set extraData = {this.state} since the status of buttons in each of the flatlist item depends on the array which is present in the parent component.
So, I've sent it to child component via props.
Now the flatlist is working fine and re-rendering on every state update.

Related

Giving a state value from another component class through using a prop

I am completely new in React Native and I am trying to improve my self with some making sample apps. For this scenario, I only want that when the Button is pressed, the state and the value of the toggle change, such as CLOSED and OPEN. And Make Open is not changed. Now, I have three class components. I am not going to give App's class. I've alread completed it. In this class, I have added a prop:
class A extends Component
{
constructor(props)
{
super(props);
this.state = {change: true};
}
render(){
return(
<View>
<View ...
<Text>CLOSED</Text>
</View>
</View>
);
}
}
Here is my second class B:
class B extends Component
{
state = {isOpen : false};
render()
{
onPress = () => {
...
})
}
return(
<View>
<View ...
<A/>
<Button
title="Make Open"
onPress={this.onPress}
/>
</View>
</View>
);
}
}
I am struggling with the prop. My question is that I want to pass the isOpen value from the class B's state to class A by use of this prop. I think that I could not create state in class B. What is the easiest way to solve this problem ? If I gave not enough information, sorry for that.
When you call component A from component B, you have to pass your state like so:
<A isOpen={this.state.isOpen} />
Then in component A, you can simply access it with props.isOpen.

React Native FlatList - Re-Renders / Mounts child items more than once [duplicate]

I have this code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: []
}
this._handleRenderItem = this._handleRenderItem.bind(this);
this._keyExtractor = this._keyExtractor.bind(this);
}
componentDidMount() {
let success = (response) => {
this.setState({ dataSource: response.data });
};
let error = (err) => {
console.log(err.response);
};
listarProdutos(success, error);
}
_keyExtractor = (item, index) => item._id;
_handleRenderItem = (produto) => {
return (
<ItemAtualizado item={produto.item} />
);
}
render() {
return (
<Container style={styles.container}>
<Content>
<Card>
<CardItem style={{ flexDirection: 'column' }}>
<Text style={{ color: '#323232' }}>Produtos atualizados recentemente</Text>
<View style={{ width: '100%' }}>
<FlatList
showsVerticalScrollIndicator={false}
data={this.state.dataSource}
keyExtractor={this._keyExtractor}
renderItem={this._handleRenderItem}
/>
</View>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
export default Home;
The function _handleRenderItem() is being called twice and I can't find the reason. The first time the values inside my <ItemAtualizado /> are empty, but the second was an object.
This is normal RN behavior. At first, when the component is created you have an empty DataSource ([]) so the FlatList is rendered with that.
After that, componentDidMount triggers and loads the updated data, which updates the DataSource.
Then, you update the state with the setState which triggers a re render to update the FlatList.
All normal here. If you want to try, load the datasource in the constructor and remove the loading in the componentDidMount. It should only trigger once.
If you want to control render actions, you can use shouldComponentUpdate method.
For example:
shouldComponentUpdate(nextProps, nextState){
if(this.state.friends.length === nextState.friends.lenght)
return false;
}
it will break render if friends count not change.
I recreated the issue in this snack. https://snack.expo.io/B1KoX-EUN - I confirmed you can use shouldComponentUpdate(nextProps, nextState) to diff this.state or this.props and return true/false - https://reactjs.org/docs/react-component.html#shouldcomponentupdate docs say this callback should be used only for optimization which is what we're doing here.

How can I remove an item from a FlatList and then update that list in React Native?

I am making a To Do list app using React Native, where I add events to a FlatList and then have a button that removes that event once it it finished. So far this is what I have. It seems very hacky to me, but most of it works.
import React from 'react';
import { StyleSheet, Text, View, TextInput,TouchableOpacity, FlatList} from 'react-native';
export default class App extends React.Component {
constructor(props){
const data = [];
super(props);
this.state ={
text: 'Enter activity here',
data: data,
color: true,
currNum: 0,
}
}
updateText(){
this.setState({data:this.state.data.concat({key:this.state.text,index:this.state.currNum})});
this.state.currNum++;
}
removeText(item){
this.setState({data:this.state.data.pop(item.index)});
this.state.currNum--;
}
render() {
return (
<View style={styles.container}>
<Text></Text>
<View style = {{flexDirection:'row',justifyContent:'flex-end'}}>
<TextInput style = {{fontSize:30,borderColor:'black', flex:1, marginTop:20}} onChangeText = {(text) => this.setState({text})}value = {this.state.text}/>
<TouchableOpacity style = {{marginTop:20}}onPress = {()=>(this.updateText())}>
<Text>Add to list</Text>
</TouchableOpacity>
</View>
<View style = {{flex:1, flexDirection:'row'}}>
<FlatList
data = {this.state.data}
extraData = {this.state}
renderItem = {({item}) => <View><Text style={styles.text} >{item.key}</Text><TouchableOpacity onPress = {() => this.removeText(item)}><Text>Remove</Text></TouchableOpacity></View>}
/>
</View>
</View>
);
}
}
When I press the "remove" button, I delete an element from the list of data that the FlatList uses. However, whenever I do this I get an error saying "Tried to get frame for out of range index NaN". Is there a way for me to regularly update and remove a FlatList, and to re-render the FlatList once I have removed an item? I have tried using the extraDate prop, but it hasn't worked. I believe I am using it wrong though. Thank you for all the help.
I think you should use .filter
removeText(item){
this.setState({
data: this.state.data.filter((_item)=>_item.key !== item.key)
});
}
And what this.state.currNum - is for?
use this instead you shouldn't mutate this.state and Array.prototype.pop() mutates it
removeText(item, index){
this.setState({data: [
...this.state.data.slice(0, index),
...this.state.data.slice(index + 1)
]});
this.state.currNum--;
}

React native: rendering conditional component based on state value change in Modal

I have a requirement of showing a tab like behavior inside a react native Modal
I have started by creating a state variable selectedSub which is initialized with value 'from'
Now my modal has 2 TouchableHighlight as below
_availabiltyModal() {
return (
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'from' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'from' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'from' && styles.activeText}>From</RkText>
</TouchableHighlight>
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'to' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'to' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'to' && styles.activeText}>To</RkText>
</TouchableHighlight>
{this._renderPicker()}
)}
These two are responsible to change the state param selectedSub as required.
Based on this state param i am conditionally showing another component i made and imported as below
_renderPicker() {
if (this.state.selectedSub == 'from') {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.from }} />
} else {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.to }} />
}
}
I have called this function in the Modal below the TouchableHighlight's
Now as per the RN docs when ever the state variable is updated with this.setState() method the component should re-render. Everything else is working fine (TouchableHighlight styles changing) and also the updates to the state are reflecting in the console. But the _renderPicker function does not return the changed view and is always stuck on the view which was set when the parent was initialized as pointed earlier.
Could somebody help me point the problem here which i might have been overlooking. Also on the side note all this calls are actually directly made outside the main render() method. Could that be a possible issue
------update------
here is the complete reference
render() {
return({this._availabiltyModal()}
<View style={appStyles.tagsWrapper}>
{this.state.week.map((day, i) => {
return (
<TouchableHighlight
key={i}
activeOpacity={0.9}
style={[appStyle.mr10, appStyle.mb10]}
onPress={() => {
this.setModalVisible(true, day);
}}>
<Text style={appStyle.tag}>{day}</Text>
</TouchableHighlight>
)
})}
</View>
)
}
Move the _renderPicker method inside the render() method like...
render() {
...
{this._renderPicker()}
...
}
Looking at the code of the MODAL component from react-native
render(): React.Node {
....
const innerChildren = __DEV__ ? (
<AppContainer rootTag={this.context.rootTag}>
{this.props.children}
</AppContainer>
) : (
this.props.children
);
return (
<RCTModalHostView
....>
<View style={[styles.container, containerStyles]}>
{innerChildren}
</View>
</RCTModalHostView>
);
}
The state you are changing are of the component that use the modal component thats render its children through the upper function.. when the state update it only re render the component whose state is updated.. so somewhere down rendering child inside component it does not get re render until force re render is applied.
Here is an helpful article to further explain how exactly this functions, forcefully re-rendering the child component

ReactNative error onPress

on clicking the text, i get an error saying "undefined is not an object (evaluating '_this2.categoryClicked.bind')"
I think the error is "onPress={()=>this.categoryClicked.bind(this)}" there must be a different way to call the categoryClicked function when the button is clicked. What is wrong in my code ?
class CategoriesView extends React.Component {
constructor(props){
super(props)
}
categoryClicked(){
this.props.categoryPressed(this.props.Category);
}
renderSubCategory(){
return(
this.props.Category.sub_category.map(function(subCategory, i){
return(
<View style={styles.abcd}>
<TouchableHighlight onPress={()=>this.categoryClicked.bind(this)}>
<Text>{subCategory.title}</Text>
</TouchableHighlight>
</View>
)
})
)
}
render(){
return(
<View style={{flex:1}}>
<View style={styles.avf}>
<Text>{this.props.Category.heading}</Text>
</View>
<View style={styles.ddd}>
{this.renderSubCategory()}
</View>
</View>
)
}
}
I believe what you want to do is onPress={this.categoryClicked.bind(this)} instead of an arrow function. .bind(this) returns a function with the context correctly binded to this, therefore, it does not get invoked.
Also, I suggest putting the binding in constructor, as you don't want the binding to happen every time the component re-renders.
e.g.
constructor(props) {
super(props);
this.categoryClicked = this.categoryClicked.bind(this);
}
Then just use onPress={this.categoryClicked}
If you want to pass down sub-category, you can do
constructor(props) {
super(props);
this.subcategoryClicked = props.Category.sub_categories.map(sub_category => this.categoryClicked.bind(this, sub_category));
}
then use like this in render:
this.props.Category.sub_category.map(function(subCategory, i) {
<View style={styles.abcd}>
<TouchableHighlight onPress={this.subcategoryClicked[i]}>
<Text>{subCategory.title}</Text>
</TouchableHighlight>
</View>
P.S, I am not sure if this is a good pattern to follow. Stick to this.categoryClicked(bind, subcategory) if you are not comfortable with doing this. This is one of those things that I am not sure if the optimization is worth it.
this in onPress={()=>this.categoryClicked.bind(this)}> points to sub_category.map function. It should instead point to the class. Can be done this way instead
return (
this.props.Category.sub_category.map((subCategory, i) => { // <--- this way context is preserved
// other lines
onPress={()=>this.categoryClicked.bind(this, subCategory)}>
// other lines
})
);
in categoryClicked method should be accessible
categoryClicked(category){
this.props.categoryPressed(category);
}

Categories

Resources