React-Native-Component not rendering when state is changed - react-native

I am making the show more and show less functionality inside a flat list but the state pressed is not working as expected .When I am setting the state value component is not being rendered when the state changes its value.
My constructor is set like below
this.state = {
accList: [],
expanded: false,
expandedText: "Show More"
}
In componentdidmount() I am updating the value of accList value like below
componentDidMount = () => {
this.setState({
accList:[{
"customer_name": "Shubhangi J Thakur",
"message":"Hello"
},
{
"customer_name": "Arthur S Campbell",
"message":"Hello_World"
},
{
"customer_name": "Susan R Brill",
"message":"hellow"
}]
});
}
I have defined the flatlist in render() like below
<FlatList
onScroll={this.handleScroll}
data={this.state.accList}
renderItem={this.renderItem}
keyExtractor={this._keyExtractor}
/>
renderItem = ({ item, index }) => (
<Card style={style.cardLayout} key={index}>
<CardItem header>
<Text>{item.customer_name}</Text>
</CardItem>
{this.seemorefunctionality(item)}
</Card>
);
seemorefunctionality = (item) => {
return <View>
{this.state.expanded ? this.expandedView(item) :null}
//To view Show More and Show Less Text
<TouchableOpacity onPress={this.expandedText}>
<Text> {this.state.expandedText}</Text>
</TouchableOpacity>
</View>
}
}
expandedText = () => {
console.log('Setting the expanded text value', this.state.expanded)
if (this.state.expanded) {
this.setState({
expandedText: "Show More"
});
}
else {
this.setState({
expandedText: "Show Less"
});
}
value=!this.state.expanded
this.setState({
expanded: value
});
}
expandedView = (item) => {
return <View>
{item.map((obj, index) => {
return (
<View key={index} >
<Text>{obj.message}</Text>
</View>
)
})}
</View>
When I am clicking on the this.state.expandedText value is getting changed when we see in the console but its not reflecting in the View also expandedView is not being rendered when this.state.expanded is set to true.
In View the value of this.state.expandedText is always showing Show More while I can see In console that the value is getting changed to Show more and Show Less on click

for re-rendering flatlist you have to add extraData={this.state} as mention on https://facebook.github.io/react-native/docs/flatlist

Related

Replace item in array react native

I'm new to js and react-native.
I have buttons on my app to change the languages, one button "English" and one button "Français".
const langues = [
{
id:0,
title: 'Français',
selected: true
},
{
id:1,
title: 'English',
selected: false
}
];
function Settings() {
return(
<View>
{langues.map(({title,selected}) => (
<View>
<Pressable style={styles.LanguesButton} >
{selected && <AntDesign size={15} name={'checkcircle'} /> }
<Text style={ {paddingLeft:selected ? 15 : 30} }>{title}</Text>
</Pressable>
</View>
))}
</View>
)
}
I would like to set selected to true for English, and false for Français when I click on "English" and same for "Français" but I don't understand how to do that.
PS: I already see that there are similar topics here but like I said I'm new to this language and I didn't understand the explanation on those topics ^^'.
First you need to create a state object that tells react native to render whenever it is updated like so;
import { useState } from "react";
const [languages, setLanguages] = useState([
{
id: 0,
title: 'Français',
selected: true
},
{
id: 1,
title: 'English',
selected: false
}
]);
Then you render this list. Whenever you press any language, you modify the state by calling setLanguages function like so;
const onPressed = (title) => {
let temp = [...languages];
temp.forEach(lang => {
if (lang.title == title) {
lang.selected = true;
}
else {
lang.selected = false;
}
});
setLanguages(temp);
}
function Settings() {
return (
<View>
{languages.map(({ title, selected }) => (
<View>
<Pressable style={styles.LanguesButton} onPress={() => {
onPressed(title);
}}>
</Pressable>
</View>
))}
</View>
)
}
After every press you will set the state and the screen will know that there has been a change and rerender.
Through onPress and find, like:
<Pressable onPress={ () =>
langues.find(lang.title === title).selected = true}>
https://reactnative.dev/docs/pressable#onpress

React Native Unmount component before navigation goBack()

I have a screen that has content that is updated by and api call. Whenever I leave the screen, I want to unmount the component so that the next time it is rendered again, because the api is called with different data. Right now, if I enter for the first time, everything works fine (activity indicator shows that it is loading and info is display), but if I go back and then enter again to view the info for another id, it shows the previously loaded information and only after a time it reloads (without the activity indicator displaying). I don't mind the delay, but at least I would like the activity indicator to be displayed while loading.
const AllReservationsScreen = ({navigation, route}) => {
const isFocused = useIsFocused();
const [data, setData] = React.useState({
tableHead: ['Car Number', 'Reserved From', 'Expiration Time', 'Reserved By'],
reservations: "",
isLoading: true
});
useEffect(() => {
setTimeout(async() => {
ReservationService.getReservationsByParkingSpot(route.params.parkingSpotId, route.params.token).then(
(response) => {
if (response.status !== 200) {
return;
}
setData({
... data,
isLoading: false,
reservations: convertReservationsToArray(response.data)
});
}
);
}, 1000);
}, [isFocused]);
const convertReservationsToArray = (reservations) => {
let reservationsArray = [];
reservations.map(
(reservation) =>
reservationsArray.push(
[
reservation.registrationPlateNumber,
moment(reservation.startTime).format('MMMM Do YYYY HH:mm'),
moment(reservation.endTime).format('MMMM Do YYYY HH:mm'),
reservation.user
]
)
);
return reservationsArray;
};
return (
<View style={styles.modal}>
<View style={styles.titleView}>
<View style={styles.titleDetails}>
<Icon
name='ios-arrow-back'
size={30}
color='#8ea7f8'
onPress={() => {
setData({
... data,
isLoading: true,
reservations: []
});
navigation.goBack()
}}
/>
</View>
</View>
<Text style={styles.modalTitle}>All Reservations</Text>
<View style={styles.modalForm}>
{
data.isLoading ? (
<View style={{flex:1,justifyContent:'center',alignItems:'center'}}>
<ActivityIndicator size="large"/>
</View>
) : (
<View style={styles.container}>
<Table borderStyle={{borderWidth: 1, borderColor: 'transparent'}} style={{borderRadius: 10}}>
<Row data={data.tableHead} style={styles.headStyle} textStyle={styles.headText}/>
<ScrollView>
{
//console.log(data.reservations)
data.reservations.map(
(reservation, i) => {
if (i % 2 === 0) {
return <Row
key={i}
data={reservation}
style={styles.evenRow}
textStyle={styles.tableText}
/>
} else {
return <Row
key={i}
data={reservation}
style={styles.oddRow}
textStyle={styles.tableText}
/>
}
}
)
}
</ScrollView>
</Table>
</View>
)
}
</View>
</View>
);
};
You can use navigation.pop() instead of the goBack(), they are exactly the same the only difference is that pop removes the current component.
see: https://reactnavigation.org/docs/stack-actions/#pop
And because you use useEffect you should in the beginning of that function set loading to true again! useEffect works like componentDidMount and componentDidUpdate so you want to be loading everytime you're calling the api. Or just use componentDidMount.

React Native change data displayed in the app according to selected button

In the top on my app, I have 3 buttons: Load1, Load2, Add.
Load1 => Load data1 and display content
Load2 => Load data2 and display content
Add => Add data
3 Buttons:
class Comp1 extends Component {
...
renderMainPage() {
switch (this.state.selectedButton) {
case 'load1':
return <ListComp status="load1" />;
case 'closed':
return <ListComp status="load2" />;
case 'add':
return <AddComp status="add" />;
default:
break;
}
}
...
render() {
<View style={{ flex: 1 }}>
<Button onPress={() => this.setState({ selectedButton: 'load1' })} > <Text>Load1</Text>
</Button>
<Button onPress={() => this.setState({ selectedButton: 'load2' })} > <Text>Load2</Text>
</Button>
<Button onPress={() => this.setState({ selectedButton: 'add' })} > <Text>Add</Text>
</Button>
{this.renderMainPage()}
</View>
}
}
Buttons are working just fine and loading the correct content.
The ListComp:
componentDidMount() {
this.getData();
}
getData = async () => {
if (this.props.status === 'load1') {
await this.props.getLoad1();
} else if (this.props.status === 'load2') {
await this.props.getLoad2();
}
this.setState({
myData: this.props.data
});
};
Then the AddComp is just a form Component. It default loads with the "Load1" data. Then, I click on "Load2" it gets the load2 data. Then, I click on "Add" and click back on "Load1". It goes to this.props.getLoad1(), but did not update the "myData".
If I keep switching between "Load1" and "Load2", it work just fine.

Edit state of every item of a FlatList

I made a page in which I use a FlatList. This FlatList uses an item component I made that display another view below itself when pressed by setting a state "hidden" to false. The main issue I have is that I can't find a way to change the "hidden" state to true when one of the item is pressed, hence always keeping only 1 item displaying the additional view at the time. In the same time, when I refresh/re-render my FlatList, it does not set all the "hidden" state back to true.
This is where I render my FlatList
_onRefresh() {
this.setState({refreshing: true}, () => this._loadList());
}
render() {
return (
<View style={[style.container, style.whiteBackground]}>
<CategoryFilter filterCallback={this._changeCategory}/>
<FlatList
data={this.state.list}
extraData={this.state}
renderItem={({item}) =>
<ListItemComponent item={item} category={this.state.category}/>
}
refreshing={this.state.refreshing}
onRefresh={() => this._onRefresh()}
/>
</View>
);
}
And this is where I render and display the hidden view
constructor(props) {
super(props);
this.state = {
hidden: true
};
}
componentDidMount() {
this.setState({hidden: true});
}
_onPress() {
this.setState({
hidden: !this.state.hidden
});
}
[...]
_renderOS(item) {
if (Platform.OS === 'android') {
return (
<TouchableNativeFeedback onPress={() => this._onPress()}>
{this._renderItem(item)}
</TouchableNativeFeedback>
);
} else if (Platform.OS === 'ios') {
return(
<TouchableOpacity onPress={() => this._onPress()}>
{this._renderItem(item)}
</TouchableOpacity>
);
}
}
[...]
_renderDescription(item) {
if (this.state.hidden === true) {
return null;
} else {
return (
<View style={listItemStyle.descriptionContainer}>
<Text style={listItemStyle.description}>
{item.description}
</Text>
</View>
);
}
}
I just want to be able to have only one of the list item with hidden set to false at the time and have said item to be set to hidden=true when the page is refreshed, but I never found anything that could help me.
So after thinking a lot I finally found a solution.
Instead of handling the hidden state in every item, I made a list of every hidden state associated to the items ids in the component where my flatlist is, adding a function that will set the previously opened item to hidden and open the new one, and passing it as a callback to my items so that it can be called when I press them.
_onPress(id) {
let items;
items = this.state.items.map((item) => {
if (item.id === this.state.openId)
item.open = false;
else if (item.id === id)
item.open = true;
return item;
});
this.setState({
items: items,
openId: (id === this.state.openId ? '' : id)
});
}
<FlatList
data={this.state.items}
extraData={this.state}
renderItem={({item}) =>
<ListItemComponent
onPress={this._onPress.bind(this)}
bet={item}
categoryList={this.state.categoryList}
open={item.open}/>
}
refreshing={this.state.refreshing}
onRefresh={() => this._onRefresh()}
/>

Handle Multiselect in a GridView

I'm trying to handle the multi-select with react-native-super-grid , here is my code :
<GridView
itemDimension={80}
items={items}
style={styles.gridView}
renderItem={item => (
<View style={[styles.itemContainer , { backgroundColor:' transparent '}]}>
<TouchableHighlight style={styles.buttonStyle} onPress={() => this.pressEvent() }>
<Text> {item.image}</Text>
</TouchableHighlight>
<Text style={styles.buttonText}> {item.name}</Text>
</View>)}
/>
I tried using this function :
pressEvent(arr){
if(this.state.pressStatus == false){
this.setState({ pressStatus: true})
this.state.arr.push(arr)
this.setState({ color : 'white'})
} else {
this.setState({ pressStatus: false})
this.setState({ color: 'red'})
}
}
but it somehow doesn't work , can someone help me ?
Thank you .
This short example should give you an idea what are you doing wrong. The items itself are not aware of the state. So what I would do, I would create a separate child component for grid item and handle press state locally. Then handle parent, which is holding all the item trough callback about the pressed item.
class MyGridView extends Component {
render() {
return (
<GridView
itemDimension={80}
items={items}
style={styles.gridView}
renderItem={item => (
<GridItem
item={item}
onItemPress={selected => {
// set grid view callback
if (selected) {
//if true add to array
this.addToPressedArray(item);
} else {
//false remove from array
this.removeFromPressedArray(item);
}
}}
/>
)}
/>
);
}
// You don't change the state directly, you mutate it trough set state
addToPressedArray = item => this.setState(prevState => ({ arr: [...prevState.arr, item] }));
removeFromPressedArray = item => {
const arr = this.state.arr.remove(item);
this.setState({ arr });
};
}
And the GridItem
class GridItem extends Component {
// starting local state
state = {
pressStatus: false,
color: 'red'
};
// handle on item press
pressEvent = () => {
this.setState(prevState => ({
pressStatus: !prevState.pressStatus, //negate previous on state value
color: !prevState.pressStatus ? 'white' : 'red' //choose corect collor based on pressedStatus
}));
// call parent callback to notify grid view of item select/deselect
this.props.onItemPress(this.state.pressStatus);
};
render() {
return (
<View style={[styles.itemContainer, { backgroundColor: ' transparent ' }]}>
<TouchableHighlight style={styles.buttonStyle} onPress={() => this.pressEvent()}>
<Text> {item.image}</Text>
</TouchableHighlight>
<Text style={styles.buttonText}> {item.name}</Text>
</View>
);
}
}
I also recommend to read about React.Component lifecycle. Its a good reading and gives you a better understanding how to achieve updates.
Since GridView has been merged into FlatGrid. Therefore, I've implemented the multi-select option in a pretty easy way. First of all I applied TouchableOpacity on top of the view in the renderItems prop of FlatGrid like this.
<TouchableOpacity
onPress={() => this.selectedServices(item.name)}>
...props
</TouchableOpacity>
SelectedServices:
selectedServices = item => {
let services = this.state.selectedServices;
if (services.includes(item) == false) {
services.push(item);
this.setState({ selectedServices: services });
} else {
let itemIndex = services.indexOf(item);
services.splice(itemIndex, 1);
this.setState({ selectedServices: services });
}
};
Using splice, indexOf, and push, you can easily implement multi-selection.
To change the backgroundColor of the currently selected item, you can apply a check on the backgroundColor prop of the view.
renderItem={({ item, index }) => (
<TouchableOpacity
onPress={() => this.selectedServices(item.name)}
>
<View
style={[
styles.itemContainer,
{
backgroundColor: this.state.selectedServices.includes(
item.name
)
? '#0052cc'
: item.code
}
]}
>
<Text style={styles.itemName}>{item.name}</Text>
</View>
</TouchableOpacity>
)}