Remove one item from flatlist - reactjs

I'm using FlatList to show a list of data.
I was trying dozens of example how to remove one row from data, but couldn't find the right solution.
Right now I'm removing all data from state, but I want to remove just one item.
Here is my HomeScreen which displays list of data:
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data.products
};
}
static navigationOptions = {
title: "Products"
};
keyExtractor = (item, index) => item.id;
openDetails = data => {
this.props.navigation.navigate("Details", {
data
});
};
deleteItem = data => {
this.setState({ data: ''})
}
renderProduct = ({ item, index }) => {
return (
<Item
itemTitle={item.title}
openDetails={() => this.openDetails(item)}
itemUrl={item.imageUrl}
data={this.state.data}
deleteItem={() => this.deleteItem(item)}
/>
);
};
render() {
return (
<FlatList
data={this.state.data}
renderItem={this.renderProduct}
keyExtractor={this.keyExtractor}
/>
);
}
}
export default HomeScreen;
Here is my Item component which is showing one item and receiving deleteRow function as prop:
const Item = props => {
return (
<View>
<TouchableOpacity onPress={props.deleteItem}>
<Image
source={{ uri: props.itemUrl }}
style={{ width: "100%", height: 220 }}
/>
<Text style={styles.productTitle}>{props.itemTitle}</Text>
</TouchableOpacity>
</View>
);
};
export default Item;

Use below deleteItem function.
deleteItem = data => {
let allItems = [...this.state.data];
let filteredItems = allItems.filter(item => item.id != data.id);
this.setState({ data: filteredItems })
}
This should filter out the deleted item.

Related

React native VirtualizedList Re-render while scroll the list

I have Virtualized List initial render record up to 30 ,while render the data list automatically re render 2 to 4 times and also the new data added to the list
while rendering multi times we can't able to do any action like touch or navigate to another screen
My Code
class HomeDetails extends PureComponent {
constructor(props) {
super(props);
this.cellRefs = {};
this.flatListRef = React.createRef();
}
getItem = (data, index) => {
if (index in data) {
return {
key: `${data[index].id} - ${index}`,
id: data[index].id,
accountId: data[index].accountId,
displayName: data[index].displayName,
fullName: data[index].fullName,
};
}
};
keyExtractor(item, index) {
return `${item.id} - ${index}`;
}
getItemCount = data => {
return data.length;
};
_renderItem =({item,index}) => {
console.log(
'Rerendring',
item.accountId,
moment().format('MM/DD/YY hh:mm:ss a'),
);
return (
<View key={index} style={{height: 50, flexDirection: 'row'}}>
<Text>{`${item.accountId} ${moment().format(
'MM/DD/YY hh:mm:ss a',
)}`}</Text>
</View>
);
}
render(){
return (
<VirtualizedList
onScroll={this.onScrollHandler}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
scrollEventThrottle={16}
ref={this.flatListRef}
horizontal={false}
decelerationRate="normal"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
data={this.props.responseRecord}
pagingEnabled={true}
scrollToOverflowEnabled={false}
renderItem={this._renderItem}
keyExtractor={this.keyExtractor}
getItemCount={this.getItemCount}
getItem={this.getItem}
windowSize={21}
progressViewOffset={20}
initialNumToRender={15}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={100}
onEndReached={val => {
return this.props.getExtraData(2, 1);
}}
onEndReachedThreshold={0.1}
refreshing={this.props.postLoading}
extraData={this.props.refreshData}
disableIntervalMomentum={false}
removeClippedSubviews={true}
onRefresh={() => {
return this.props.getExtraData(1, 1);
}}
ItemSeparator={this.ItemSeparator}
ListFooterComponent={this.renderFooter}
/>
)
}
}
const mapStateToProps = ({post, auth, common}) => {
const {
responseRecord,
postLoading,
refreshData,
} = post;
return {
responseRecord,
postLoading,
refreshData,
};
};
const mapDispatchToProps = {
getExtraData,
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeDetails);
..........................................................................
1.For initial 30 record rendering its re-render more that 2 times
2.when add more records its re-render more than 4 to 6 times
3.tried with purecomponent but no luck
code deployed in snack
https://snack.expo.dev/#pandianvpsm/cd5737
Internal, React's PureComponent implements the shouldComponentUpdate method and compares previous props values and new props or state values to avoid unnecessary re-renders.
This works well for primitive type values like numbers, strings, and booleans.
For referential types values (objects and arrays), this comparison is not always accurate. This is the behavior you have. this.props.responseRecord is an array of objects (referential types).
We can solve this problem by implementing our own componentShouldUpdate method as below:
/** Trigger component rerender only new elements added */
shouldComponentUpdate(nextProps, nextState) {
return this.props.responseRecord.length !== nextProps.responseRecord.length
}
Full code below
class HomeDetails extends React.Component {
constructor(props) {
super(props);
this.cellRefs = {};
this.flatListRef = React.createRef();
}
/** Trigger component rerender only new elements added */
shouldComponentUpdate(nextProps, nextState) {
return this.props.responseRecord.length !== nextProps.responseRecord;
}
getItem = (data, index) => {
if (index in data) {
return {
key: `${data[index].id} - ${index}`,
id: data[index].id,
accountId: data[index].accountId,
displayName: data[index].displayName,
fullName: data[index].fullName,
};
}
};
keyExtractor(item, index) {
return `${item.id} - ${index}`;
}
getItemCount = (data) => {
return data.length;
};
_renderItem = ({ item, index }) => {
console.log(
"Rerendring",
item.accountId,
moment().format("MM/DD/YY hh:mm:ss a")
);
return (
<View key={index} style={{ height: 50, flexDirection: "row" }}>
<Text>{`${item.accountId} ${moment().format(
"MM/DD/YY hh:mm:ss a"
)}`}</Text>
</View>
);
};
render() {
return (
<VirtualizedList
onScroll={this.onScrollHandler}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
scrollEventThrottle={16}
ref={this.flatListRef}
horizontal={false}
decelerationRate="normal"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
data={this.props.responseRecord}
pagingEnabled={true}
scrollToOverflowEnabled={false}
renderItem={this._renderItem}
keyExtractor={this.keyExtractor}
getItemCount={this.getItemCount}
getItem={this.getItem}
windowSize={21}
progressViewOffset={20}
initialNumToRender={15}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={100}
onEndReached={(val) => {
return this.props.getExtraData(2, 1);
}}
onEndReachedThreshold={0.1}
refreshing={this.props.postLoading}
extraData={this.props.refreshData}
disableIntervalMomentum={false}
removeClippedSubviews={true}
onRefresh={() => {
return this.props.getExtraData(1, 1);
}}
ItemSeparator={this.ItemSeparator}
ListFooterComponent={this.renderFooter}
/>
);
}
}
const mapStateToProps = ({ post, auth, common }) => {
const { responseRecord, postLoading, refreshData } = post;
return {
responseRecord,
postLoading,
refreshData,
};
};
const mapDispatchToProps = {
getExtraData,
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeDetails);

How to create automatic Item Divider for React Native FlatLists?

I have a list of events that I am rendering in a FlatList. I would like there to be a divider whenever the event is on a different date - aka when {item.eventID.eventDate} for a given item is different to the one before it (I already know how to call the sever to return the dates in order).
Is there a way to autogenerate these dividers?
Here is my function for each item of the FlatList:
function Item({ item }) {
return (
<View>
<Text>{item.eventID.eventDate}</Text>
<Text>{item.eventID.artistName}</Text>
<Text>{item.ticketID}</Text>
</View>
);
}
And here is my class component for the page:
export default class MyEventsScreen extends Component {
state = {
tickets: [],
};
componentDidMount = () => {
fetch("http://127.0.0.1:8000/api/fullticket/", {
method: "GET",
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({
tickets: responseJson,
});
})
.catch((error) => {
console.error(error);
});
};
render() {
return (
<View>
<FlatList
style={{ flex: 1 }}
data={this.state.tickets}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={(item) => item.ticketID}
/>
</View>
);
}
}
You can write a custom function to render divider when the date value is changed.
The new Item function and renderDivider:
let prevDate = ""
function renderDivider(date) {
if(prevDate === "" || date !== prevDate) {
prevDate = date //initialize prevDate
return <Text style={styles.divider}>----{date}---</Text>
}
}
function Item({ item }) {
return (
<View>
{renderDivider(item.eventID.eventDate)}
<Text>{item.eventID.artistName}</Text>
</View>
);
}
const styles = StyleSheet.create({
divider: {
marginVertical: 15,
fontWeight: '700',
color: 'rgb(100,100,100)'
}
});

Toggle by index or id react native

I am pulling categories and sub categories from an API which is working as expected. I have them in an accordion using expanded:
constructor(props) {
super(props);
this.state = {
...
expanded : false,
}
}
Then rendering categories and sub categories:
renderFilter=()=> {
const items = [];
for (let item of this.state.docCats) {
if( item.subCats) {
docItems = item.subCats.map(row => {
return<TouchableOpacity key={row.doc_sc_id} onPress={()=>this.FilterRequest(item.doc_cat_id, row.doc_sc_id)}><Text style={styles.filterCat}>{row.doc_sc_title}</Text></TouchableOpacity>
})
}
items.push(
<View key={item.doc_cat_id}>
<TouchableOpacity onPress={()=>this.FilterRequest(item.doc_cat_id, '')}>
<Text style={styles.filterCat}>{item.doc_cat_title}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={()=>this.toggleExpand()}>
<FontAwesome name={this.state.expanded ? 'chevron-up' : 'chevron-down'} size={20} color="#000" />
</TouchableOpacity>
{this.state.expanded &&
docItems
}
</View>
);
}
return items;
}
using a Toggle:
toggleExpand=()=>{
this.setState({expanded : !this.state.expanded})
}
My problem is when i use the toggleExpand it expands all the accordions at the same time. Is there a way i can open them individually by targetting the index or id?
I would prefer not to use a different class/component as i need access to the other functions and fetch requests that i have.
You should add a property to each docCats item that indicates whether its accordion should be expanded or not, then pass an id to the toggleExpand function to update this property for a specific item and the docCats array that's stored in your state. Use the expanded property of each item inside of the renderFilter function to indicate whether the accordion should be expanded or not.
Here's a simplified example, but should give you a good idea how to rewrite your code to make it work:
const items = Array.from({ length: 5 }).map((_, idx) => ({
id: idx,
title: `Accordion ${idx + 1}`
}));
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
items: items.map(item => ({
...item,
expanded: false
}))
};
}
toggleExpand = id => {
const nextItems = [...this.state.items].map(item => {
if (item.id === id) {
return {
...item,
expanded: !item.expanded
};
}
return item;
});
this.setState({
items: nextItems
});
};
renderFilter = () => {
return this.state.items.map(item => (
<div
style={{ border: `1px solid black`, width: `100`, height: `100` }}
onClick={() => this.toggleExpand(item.id)}
key={item.id}
>
<p>
{item.title} {item.expanded ? `is expanded` : `is not expanded`}
</p>
</div>
));
};
render() {
const renderedFilter = this.renderFilter();
return <div>{renderedFilter}</div>;
}
}
If you'd like to see it in action, simply click on each box to change the expanded state, here's the link to a working demo:
CodeSandbox

How i can limit the items in the FlatList and add load more?

My skills is basic, and i'm newbie in React native, what i want to do is limit the posts in 12 and when the user scroll automatically load more posts.
My Code:
export default class Posts extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,};}
componentDidMount() {
return fetch(ConfigApp.URL+'json/data_posts.php')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataPosts: responseJson
}, function() {
});
})
.catch((error) => {
});}
render() {
return (
<FlatList
data={ this.state.dataPosts }
numColumns={2}
renderItem={({item}) =>
<TouchableOpacity activeOpacity={1} style={{flex: 1}}>
<View style={{margin: 5, marginLeft: 4}}>
<ImageBackground source={{uri: ConfigApp.IMAGESFOLDER+item.post_image}}>
<LinearGradient colors={['rgba(0,0,0,0.3)', 'rgba(0,0,0,0.8)']}>
<Text numberOfLines={2}>{item.post_title}</Text>
</LinearGradient>
</ImageBackground>
</View>
</TouchableOpacity>
}
keyExtractor={(item, index) => index}
/>
);}}
If your requirement is to append the existing list from already pulled data in a chunk of 12, then you may consider following strategy which uses onEndReached and onEndThreshold to handle the scroll and add 12 records at a time.
Set current page number to 0 in constructor
constructor(props){
super(props);
this.state = {
... ,
page: 0,
posts: []
}
}
Inside componentDidMount you need to pull all data from the server and store it in the local state (which you are currently doing), then call the function which will read first 12 records.
componentDidMount() {
return fetch(ConfigApp.URL+'json/data_posts.php')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
page: 0,
dataPosts: responseJson
}, function() {
// call the function to pull initial 12 records
this.addRecords(0);
});
})
.catch((error) => {
});
}
Now add the function which will add records from this.state.dataPosts
addRecords = (page) => {
// assuming this.state.dataPosts hold all the records
const newRecords = []
for(var i = page * 12, il = i + 12; i < il && i <
this.state.dataPosts.length; i++){
newRecords.push(this.state.dataPosts[i]);
}
this.setState({
posts: [...this.state.posts, ...newRecords]
});
}
Now add the scroll handler
onScrollHandler = () => {
this.setState({
page: this.state.page + 1
}, () => {
this.addRecords(this.state.page);
});
}
Render function
render() {
return(
...
<FlatList
...
data={this.state.posts}
renderItem={({item}) => ... }
keyExtractor={(item, index) => index}
onEndReached={this.onScrollHandler}
onEndThreshold={0}
/>
...
);
}
Hope this will help!
You Can add the slice(start,end) method while fetching jsondata in datasource. This trick may solve your problem.
dataPosts: responseJson.slice(0,10) replace this line with yours.
In FlatList prop Data you will use slice(start, end)
exp:
data={Data.slice(0,4)}
in above exp flatlist will show only the first 4 object

Object array data source for FlatList empties when used as renderItem

this is an extension of an earlier question now debugged to realize it's a different issue. I have an object array that looks like this when logged to console:
I now want to use this object array to display a list using FlatList component. in my state constructor, I set a variable to itemList which takes in objects generated from my listenForMusic function using this.setState():
class VideoFeed extends React.Component {
constructor(props) {
super(props);
//this.dataRef = database.ref("music");
this.state = {
itemList: null,
}
}
componentWillMount() {
this.listenForMusic();
}
listenForMusic(){
var dataRef = database.ref("music");
let items = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
items.push({
videoURL: snap.val().youtubeURL,
title: snap.val().title,
thumbnail: snap.val().thumbnail
});
});
this.setState({ itemList: items })
}
render() {
console.log(this.state.itemList);
return (
<View>
<FlatList
data={this.state.itemList}
renderItem={({item}) => { console.log(item); return (<Text>{item.videoURL}</Text>) }}
/>
</View>
);
}
}
I have that console.log in my render function and I see the image I posted above, but when I try and console.log the item in the renderItem, it does not show anything in the console (not even an empty array). Where did my data go that I submitted into the data prop?
Should be obvious, but nothing is printed in that <Text> tag.
EDIT: Whole class added
class VideoFeed extends React.Component {
constructor(props) {
super(props);
//this.dataRef = database.ref("music");
this.state = {
itemList: null,
}
//this.listenForMusic = this.listenForMusic.bind(this);
}
componentWillMount() {
this.listenForMusic();
}
listenForMusic(){
var dataRef = database.ref("music");
let items = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
items.push({
videoURL: snap.val().youtubeURL,
title: snap.val().title,
thumbnail: snap.val().thumbnail
});
});
this.setState({ itemList: items })
}
_keyExtractor = (item, index) => item.id;
_renderVideoItem = ({item}) => (
<TouchableWithoutFeedback
onPress={Actions.Submit}
>
<View style={styles.mediaContainer}>
<Image
source={{uri: item.thumbnail }}
style={styles.mediaThumbnail}
/>
<View style={styles.mediaMetaContainer}>
<View style={styles.topMetaContainer}>
<Text style={styles.mediaTitle}>
{item.title}
</Text>
<Text style={styles.sharedByUser}>
UNCVRD
</Text>
</View>
<View style={styles.bottomMetaContainer}>
<Icon
name='youtube-play'
type='material-community'
color='#ff0000'
size={16}
/>
<View style={styles.bottomRightContainer}>
<Icon
name='thumb-up'
size={12}
color='#aaa'
/>
<Text style={styles.metaLikeCounter}>
16
</Text>
</View>
</View>
</View>
</View>
</TouchableWithoutFeedback>
);
render() {
console.log(this.state.itemList);
return (
<View>
<FlatList
data={this.state.itemList}
renderItem={({item}) => { console.log(item); return (<Text>{item.title}</Text>) }}
/>
</View>
);
}
}
EDIT 2: So I did an interesting test, I made two state variables: realList and fakeList:
state = {
realList: [],
fakeList: [],
}
Then when the page will load, the following function is run that populates arrays called real and fake. One with data pulled from Firebase, the other hardcoded with array information:
listenForMusic = () => {
var dataRef = database.ref("music");
let real = [];
let fake = [];
dataRef.orderByChild("date").on('child_added', (snap) => {
var url = snap.val().youtubeURL;
var vidTitle = snap.val().title;
var thumb = snap.val().thumbnail;
real.push({
videoURL: url,
title: vidTitle,
thumbnail: thumb
});
});
fake.push({videoURL: "https://youtu.be/AHukwv_VX9A", title: "MISSIO - Everybody Gets High (Audio)", thumbnail: "https://i.ytimg.com/vi/AHukwv_VX9A/hqdefault.jpg"}, {videoURL: "https://youtu.be/G-yWpz0xkWY", title: "SMNM - Million ft. Compulsive", thumbnail: "https://i.ytimg.com/vi/G-yWpz0xkWY/hqdefault.jpg"});
this.setState({
realList: real,
fakeList: fake
});
}
Then I console.log both of the arrays after the render function and I see this:
And opening both:
So my question is, why does the "real" array look empty but still has data populated inside while the "fake" array displays that it holds two objects inside of it, even before we take a look inside??
I think
renderItem={({item}) => return (<Text>{item.videoURL}</Text>)}
should be:
renderItem={(item) => return (<Text>{item.videoURL}</Text>)}
The way you have it now is trying to deconstruct a property called item, which doesn't exist.

Resources