ListItem not rerendering in FlatList when observable array changes - react-native

I have component called CheckListItem that contains:
<ListItem
leftAvatar={this.props.leftAvatar}
title={this.props.title}
leftIcon={this.state.icon}
onPress={(event) => {
this.props.onSelect();
}}
containerStyle={{ backgroundColor: this.state.color }}
switch={{ value: this.props.isSelected, disabled: true }} // <-- this switch
/>
I export it as an observer:
export default (observer(CheckListItem));
And use it from my so called CountryPage:
render() {
console.log(toJS(this.props.store.countries)); // <-- prints the expected updated list
return (
<View style={{ flex: 1 }}>
<Text>{this.state.ouputText}</Text>
<FlatList
data={this.props.store.countries}
renderItem={({ item }) =>
<CheckListItem
title={item.name}
leftAvatar={{ source: { uri: item.flag } }}
onSelect={() => { SearchParameters.getInstance().setCountrySelected(item.key); }}
isSelected={item.isSelected} //<-- won't update
/>
}
/>
</View>
);
}
I inject my store which is the singleton instance of SearchParameters
export default inject('store')(observer(CountryPage));
Whenever I get my SearchParameters instance and change any of the properties inside the list (isSelected) that resides there. CountryPage rerenders and log the expected updated list. My problem is that this don't update CheckListItem - and rerenders it. How can I make my CheckListItem component receive the updated props whenever the list elements change and rerender?

I found my answer is the second answer here: React Native mobx binding to FlatList not working. Adding Observer around CheckListItem made it so that the 'isSelected' was passed down thus updating the switch.
render() {
console.log(toJS(this.props.store.countries));
return (
<View style={{ flex: 1 }}>
<Text>{this.state.ouputText}</Text>
<FlatList
data={this.props.store.countries}
renderItem={({ item }) =>
<Observer>
{() =>
<CheckListItem
title={item.name}
leftAvatar={{ source: { uri: item.flag } }}
onSelect={() => { SearchParameters.getInstance().setCountrySelected(item.key); }}
isSelected={item.isSelected}
/>
}
</Observer>
}
/>
</View>
);
}

Related

Is there a way to get the Title of the selected item from another component in React Native

I have two different components "HomePage" & "ListItemCA"
In HomePage, I have a FlatList and a modal popup
<FlatList
data={ listData}
keyExtractor={list => list.Title}
renderItem={({ item }) => <ListItemCA data={item} onLongPress={openModal} />}
/>
and each list item is called from another component ListItemCA
function ListItemCA({data, onLongPress}) {
return (
<TouchableOpacity onLongPress={onLongPress} >
<View style={styles.container}>
<Text style={styles.title}>{data.Title}</Text>
<View style={styles.metaContainer}>
<Text style={styles.meta}>{( data.totalMonths != null ? data.totalMonths : '0' )} Months</Text>
<Text style={styles.meta}>{( data.members != null ? data.members.length : '0' )} Members</Text>
</View>
</View>
</TouchableOpacity>
);
}
What I want to acheive?
I want to get the selected list item title on my HomePage component. When a user longpress on a list item that title should be displayed on a modal popup. How do I pass the selected list item title to the HomePage component using longpress?
If your goal is to display data from the long pressed item in the modal, you could add the data as a parameter of your openModal function:
function openModal(data) {
// your function
return (
<Text>{data.Title}</Text>
)
}
Then, in your FlatList, modify the props of ListItemCA to call openModal for the selected item:
renderItem={({ item }) => <ListItemCA data={item} onLongPress={openModal(item)} />}
If you also want to save the data from the long pressed item in your HomePage component for other uses, you could save it in the state. In your HomePage component:
import React, { useState } from 'react'
function HomePage() {
const [itemData, setItemData] = useState()
// your code
}
Then, in your flatlist:
<FlatList
data={listData}
keyExtractor={list => list.Title}
renderItem={({ item }) =>
<ListItemCA
data={item}
onLongPress={ () => {
setItemData(item)
openModal(item)
}}
/>
}
/>
You can achieve this by passing(return) parameter from your component like this -
function ListItemCA({data, onLongPress}) {
return (
<TouchableOpacity onLongPress={() => {
onLongPress(data.Title);
//return data.Title when onLongPressed clicked
}}>
<View style={styles.container}>
...
</View>
</TouchableOpacity>
);
}
then get it in props -
<FlatList
data={listData}
keyExtractor={list => list.Title}
renderItem={({ item }) =>
<ListItemCA
data={item}
onLongPress={(title) => {//this **title** return from **onLongPress(data.Title)**
openModal();
setTitle(title);// or directly can pass that title in openModal func.
}}
/>
}
/>

How can I change the numColumns of Flatlist when changing the orientation

I created the function name getOrientation() and put it at useEffect so that whenever I rotate the device, it will rerender the component and show me which orientation the device is.
I also created the variable to determine the orientation by using hook
getOrientation()
const [orientation, setOrientation] = useState("")
const window = useWindowDimensions()
const getOrientation = () => {
if (window.height < window.width) {
setOrientation("LANDSCAPE")
} else {
setOrientation("PORTRAIT")
}
return orientation
}
at useEffect
useEffect(() => {
getOrientation()
})
console.log(orientation)
My problem is that I want to set the numsColumns = 2 in Flatlist (LANDSCAPE) and equals to 1 for the portrait mode but the error popup telling me that I cannot change the numColumns on the fly. What should I do ?
Here is my flatlist
<View style={styles.container}>
<FlatList
contentContainerStyle={{
paddingLeft: insets.left,
paddingRight: insets.right,
}}
data={dishes.dishes}
numColumns={orientation == "LANDSCAPE" ? 2 : 1}
renderItem={({ item, index }) => (
<Tile
style={styles.tileItem}
key={index}
title={item.name}
caption={item.description}
onPress={() => navigation.navigate('Dishdetail_Screen', { dishId: item.id })} // passing infor from one to another screen
// chevron={false}
featured
imageSrc={{
uri: baseUrl + item.image
}}
/>
)}
keyExtractor={item => item.id.toString()} />
</View>
This creepy error
enter image description here
P/s: I'm the fresh React-Native developer. Thanks everyone who is checking my problems
Try adding a key prop to your Flatlist like so with the value being ur orientation:
<FlatList
key={orientation} // add key prop here
contentContainerStyle={{
paddingLeft: insets.left,
paddingRight: insets.right,
}}
data={dishes.dishes}
numColumns={orientation == "LANDSCAPE" ? 2 : 1}
renderItem={({ item, index }) => (
<Tile
style={styles.tileItem}
key={index}
title={item.name}
caption={item.description}
onPress={() => navigation.navigate('Dishdetail_Screen', { dishId: item.id })} // passing infor from one to another screen
// chevron={false}
featured
imageSrc={{
uri: baseUrl + item.image
}}
/>
)}
keyExtractor={item => item.id.toString()} />

Force FlatList to re-render after sorting data

I have a jobs app. In my JobsComponent, where I display the jobs, I have added a sort option that allows users to sort the list of jobs by different criteria. The flow is this: 1) I get the jobs from the server -> 2) the user sorts the jobs -> 3) the sorted list of jobs is re-rendered on the screen.
The problem is that step 3) is not working. The actual list of jobs is being sorted (I can see that in the logs), but my FlatList is not being re-rendered.
What I have tried
I have a flag, sortOrderChanged, set in my state. Whenever the user selects a sorting option, I change this flag in my componentDidMount() method:
this.setState({
sortOrderChanged: !this.state.sortOrderChanged,
selectedSortOrder: dataFromChild
});
and pass it to FlatList as extraData:
<FlatList
data={sort_array}
extraData={props.sortOrderChanged}
renderItem={renderJobItem}
keyExtractor={(item, index) => index.toString()}
style={{marginTop: 10}}
/>
This does not help though. I have also tried sending the whole state to the FlatList and passing it to extraData, but it also didn't work. I assume the problem is that my data is not actually being changed, but sorted. However, I do not know how to force it to re-render. Can someone help me out, please?
Below is my JobsComponent.js:
function RenderJobs(props) {
var json = JSON.parse(props.jobsData);
var sort_array = [];
for (var _id in json) {
sort_array.push({
_id:_id,
jobtitle: json[_id].jobtitle,
company: json[_id].company,
duration_driving_value:json[_id].duration_driving.value,
duration_transit_value: json[_id].duration_transit.value,
duration_walking_value: json[_id].duration_walking.value,
duration_driving:json[_id].duration_driving.text,
duration_transit:json[_id].duration_transit.text,
duration_walking:json[_id].duration_walking.text,
date: json[_id].date,
formatedDescription: json[_id].formatedDescription,
applyUrl: json[_id].applyUrl
});
}
//sort the list based on user selection
if (props.sortOrder === props.sortArray[0]) {
sort_array.sort(function(x,y){return new Date(y.date) - new Date(x.date)});
}
else if (props.sortOrder === props.sortArray[1]) {
sort_array.sort(function(x,y){return x.duration_driving_value - y.duration_driving_value});
}
else if (props.sortOrder === props.sortArray[2]) {
sort_array.sort(function(x,y){return x.duration_transit_value - y.duration_transit_value});
}
else {
sort_array.sort(function(x,y){return x.duration_walking_value - y.duration_walking_value});
}
const renderJobItem = ({item}) => {
var durationCarApi, durationPublicTransportApi, durationWalkApi, formattedApiDate, formattedJobDescription;
//format data
return (
<Panel //custom component used to display each job
jobTitle={item.jobtitle}
company={item.company}
durationCar={durationCarApi}
durationTram={durationPublicTransportApi}
durationWalking={durationWalkApi}
dateAdded={formattedApiDate}
onPress={() =>
{
props.navigation.navigate('JobDetails', {
jobTitle: item.jobtitle,
company: item.company,
durationCar: durationCarApi,
durationTram: durationPublicTransportApi,
durationWalking: durationWalkApi,
jobDescription: formattedJobDescription,
applyUrl: item.applyUrl
})
}
}/>
);
}
//handle loading/error scenarios
return (
<FlatList
data={sort_array}
extraData={props.sortOrderChanged}
renderItem={renderJobItem}
keyExtractor={(item, index) => index.toString()}
style={{marginTop: 10}}
/>
);
}
class Jobs extends Component {
constructor(props) {
super(props);
this.state = {
jobTitle: this.props.navigation.getParam('jobTitle', ''),
address: this.props.navigation.getParam('address', 'error'),
sortOrderChanged: false,
sortArray: [0,1,2,3],
selectedSortOrder: 1 //default is sort_driving
};
}
componentDidMount() {
handleSorting = (dataFromChild) => {
console.log('Sort order clicked: ' + dataFromChild);
this.RBSheet.close();
this.setState({
sortOrderChanged: !this.state.sortOrderChanged,
selectedSortOrder: dataFromChild
});
}
render() {
return(
<ScrollView contentContainerStyle={styles.bkg}>
<RenderJobs
jobsData={JSON.stringify(this.props.jobs.jobs)}
isLoading={this.props.jobs.isLoading}
errMess={this.props.jobs.errMess}
navigation={this.props.navigation}
sortOrder={this.state.selectedSortOrder}
sortArray={this.state.sortArray}
sortOrderChanged={this.state.sortOrderChanged}
/>
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<RBSheet //custom component used to render the sorting options
ref={ref => {this.RBSheet = ref;}}
height={200}
duration={250}
customStyles={{
container: {
justifyContent: "center",
alignItems: "center"
}
}}>
<SortSheet //this is the child component used to render the sorting options
sortOrder={this.handleSorting}
sortArray={this.state.sortArray}/>
</RBSheet>
</View>
</ScrollView>
)
}
}
Move your data from local variable to state.
Or add forceUpdate after else.
The solution was to change the keyExtractor to item._id:
<FlatList
data={props.jobsData}
extraData={props.sortOrderProps}
renderItem={renderJobItem}
keyExtractor={(item, index) => item._id}
style={{marginTop: 10}}
/>

Conditionally style not working in react native

I followed this answer to dynamically style my component.
Here is my render method :
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.images}
numColumns={2}
keyboardShouldPersistTaps={'always'}
keyboardDismissMode={'on-drag'}
keyExtractor={item => item.localIdentifier}
renderItem={({ item, index }) =>
<TouchableHighlight
underlayColor='transparent'
onPress={() => this.openImage(index)}
onLongPress={() => this.startSelection(item)}
>
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
</TouchableHighlight>
}
/>
</View>
);
}
As you can see I am displaying image thumbnail with TouchableHighlight and FlatList. When user will press and hold on any image thumbnail I called startSelection() with particular flatlist item which then add that item to state. I used that state to set style dynamically of my image as :
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
Here is startSelection() method :
startSelection(item) {
let temp = this.state.selectedItems;
temp.push(item);
this.setState({
selectedItems : temp
});
}
Here is my stylesheet :
const styles = StyleSheet.create({
selectedItem: {
borderWidth: 3,
borderColor: '#22aaff',
},
unselectedItem: {
borderColor: '#000000',
}
});
But when user press and hold that view, item will added to state but style is not changing.
Please help me what's going wrong here !!!
This can be found on FlatList docs:
This is a PureComponent which means that it will not re-render if props remain shallow-equal. Make sure that everything your renderItem function depends on is passed as a prop (e.g. extraData) that is not === after updates, otherwise your UI may not update on changes. This includes the data prop and parent component state.
So you can add extraData to your FlatList component like this:
FlatList Component:
<FlatList
data={this.state.images}
extraData={this.state} //add this!
numColumns={2}
keyboardShouldPersistTaps={'always'}
keyboardDismissMode={'on-drag'}
keyExtractor={item => item.localIdentifier}
renderItem={({ item, index }) =>
<TouchableHighlight
underlayColor='transparent'
onPress={() => this.openImage(index)}
onLongPress={() => this.startSelection(item)}
>
<View style={[styles.albumContainer, (this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]}>
<Image
style={styles.albumThumbnail}
source={item.image}
/>
</View>
</TouchableHighlight>
}
/>
P.S: If your component state has variables which should not re-render FlatList, you would be better of using extraData = {this.state.selectedItems}, but then you need to make sure you pass a different reference to selectedItems when you call setState on startSelection. Like this:
startSelection(item) {
let temp = [...this.state.selectedItems];
temp.push(item);
this.setState({
selectedItems : temp
});
}
Wrap them with extra []
style={[styles.albumContainer, [(this.state.selectedItems.indexOf(item)>-1)?styles.selectedItem:styles.unselectedItem]]}

ReactNative - FlatList not updated until scroll

I have a problem with FlatList component which does not update until scrolled.
I tried add log to renderItem and keyExtractor both methods called with correct data but list didn't update.
Here is a render method:
render() {
const messages = this.props.messages
const message = this.props.message
return (
<View style={[styles.container]}>
<FlatList
ref={"flatList"}
contentContainerStyle={styles.list}
data={messages}
renderItem={(listItem) => {
return <MessageBuble message={listItem.item}/>
}}
keyExtractor={(item: Message) => {
return item.id
}}
/>
<View style={[styles.textInputContainer]}>
<TextInput
style={styles.textInput}
value={message}
multiline={true}
onChangeText={this.props.messageChanged}
/>
<Button title={"Odeslat"} onPress={() => {
if (this.props.sendMessage) {
this.props.sendMessage(this.props.message)
}
}}/>
</View>
</View>
)
}
Add extraData in FlatList and retry
<FlatList
extraData={this.props}
....
Tried the extraData, but that does not work.
There was an issue on Android where content was not visible when I returned back from another page to home screen (where the flatlist was present). The content was visible when I scrolled it a bit.
I assigned the main list to the extraData attribute, and could see that it changed in size via console logs. But the content remained invisible. Finally, used
onContentSizeChange={() => {
if (list.length > 0) {
ref.current.scrollToOffset({ animated: true, x: 0 });
}
}}
and it worked.