React Native List with Map Method add New Item below selected Item - react-native

React Native List with Map Method
What I want to achieve,
I want when click item then a new Item (I preferer add a new custom View) is added below the Selected item.
Expo Snack code>
https://snack.expo.dev/#stefanosalexandrou/tenacious-french-fries

Since you are changing the background of the selected item, it is necessary that you update the ID's of every item in the list, for otherwise inserting elements will break this functionality. Furthermore, you need to add a state for for otherwise you cannot trigger a UI change
You could implement the desired behaviour as follows.
const [selectedId, setSelectedId] = useState(null);
const [data, setData] = React.useState(persons)
function handleOnPress(idx) {
setSelectedId(idx)
const first = data.slice(0, idx + 1);
const second = data.slice(idx + 1).map(p => ({...p, id: Number(p.id) + 1}));
setData([...first, {id: idx + 2, name: "Whatever new iten"}, ...second])
}
return (
<View style={styles.container}>
<ScrollView>
<View>
{data.map((person, index) => {
const backgroundColor = index === selectedId ? "#6e3b6e" : "#f9c2ff";
return (
<TouchableOpacity
onPress={() => handleOnPress(index)}
style={{
padding:20,
backgroundColor: backgroundColor,
marginBottom:20,
}}
>
<Text>{person.name}</Text>
</TouchableOpacity>
);
})}
</View>
</ScrollView>
</View>
);
Use slice in order to split the array into two parts. Use map for updating the id attribute of the elements in the second array. Finally, combine both parts but insert a new element between them.
Here is an updated snack.

There are some points to consider and I'll list them here, before providing an idea of a solution:
React Native provides performance-optimized components that handle list rendering named <FlatList />/<SectionList />. Use those components instead of .map() for rendering component lists
You'll need to create an internal state for your list to be changed
You need to provide a key prop when rendering a list of components using .map() or other Array methods
With minimal changes to your provided code, you can create a state to store the list and when the item is pressed you can insert a new item inside this list:
import React, { useState } from "react";
import { Text, View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
const people = [/* your list */];
export default function App() {
const [peopleList, setPeopleList] = useState(people)
const [selectedId, setSelectedId] = useState(null);
return (
<View style={styles.container}>
<ScrollView>
<View>
{list.map((person, index) => {
return (
<TouchableOpacity
onPress={() => {
setSelectedId(person.id)
const newPerson = {...person}; // The new item
setPeopleList((prevList) => [...prevList.slice(0,index + 1), newPerson, ...prevList.slice(index + 1)])
}}
style={{
padding:20,
backgroundColor: backgroundColor,
marginBottom:20,
}}
>
<Text>{person.name}</Text>
</TouchableOpacity>
);
})}
</View>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding:20
}
});
Sandbox with working code: https://snack.expo.dev/5rvTbrEvO

Related

How do I get my api data to display as a single item instead of printing vertically letter by letter?

I am starting to grasp fetch and using an API but I am trying to refer to a code source that used currencies and applying it to my own version but using an API that returns a random activity. The project I am using to better grasp my understanding is react-native expo cli
What I want it to do:
Press the search button -> returns random activity to do.
Currently, it is doing this but because my old API returns an array of objects for different currencies and my current random activity API returns just one activity I believe this is why my formatting is off but after reading and trying out different .then actions I can't seem to understand fully how to display my random activity properly instead of the multiple lines of the same activity I am currently getting I want it to display only once. (My prior question had the letters vertical which I have since fixed).
here is the link for the API i want to use https://www.boredapi.com/
here is the link for the old API https://open.er-api.com/v6/latest/USD
Thanks!
import { StyleSheet, Text, View, FlatList, SafeAreaView, Button, ScrollView } from 'react-native';
import { useEffect, useState } from 'react';
const Item = ({ item }) => {
return(
<View>
<Text>{item.value}</Text>
</View>
)
}
export default function App() {
const [data, setData] = useState([]);
var searchForActivity = () => {
fetch('http://www.boredapi.com/api/activity/')
.then((res) => res.json())
.then((json) => {
var array = Object.keys(json.activity).map((key) => ({
value: json.activity,
}));
setData(array);
});
}
useEffect(() => {
searchForActivity();
}, []);
return (
<SafeAreaView>
<ScrollView>
<View style={styles.container}>
<Text>Welcome to Activity Finder</Text>
<FlatList data = {data} renderItem={Item} />
<Button title='Search' onPress={searchForActivity} />
<StatusBar style="auto" />
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Your api call returns a single object like this
{
"activity": "Write a list of things you are grateful for",
"type": "relaxation",
"participants": 1,
"price": 0,
"link": "",
"key": "2062010",
"accessibility": 0
}
Since your data is not going to be an array you should change the data initial state to something like null.
const [data, setData] = useState(null);
You're currently looping over the keys of the activity property, which are just indices. Then map over all the keys and return an object with the property value which just has the json.activity. This does not make any sense. You can set the json directly to the state like this since you don't need an array if you only want to display one activity.
var searchForActivity = () => {
fetch("http://www.boredapi.com/api/activity/")
.then((res) => res.json())
.then((json) => {
setData(json);
});
};
By this point you can't use FlatList anymore since you data in not an array anymore, in your App you should change the FlatList with the Item component you made.
return (
<SafeAreaView>
<ScrollView>
<View style={styles.container}>
<Text>Welcome to Activity Finder</Text>
<Item item={data} />
<Button title="Search" onPress={searchForActivity} />
<StatusBar style="auto" />
</View>
</ScrollView>
</SafeAreaView>
);
Note that in your Item component you use a value property. This property does not exists on the data you get back from the api and should be changed to activity for it to show the name of the activity.
<Text>{item.value}</Text>
// change value with activity
<Text>{item.activity}</Text>

React native Flatlist not re-rendering on state change

I realize there are a lot of questions and answers about this out there but I am fairly new to react native and most of the answers are dealing with React Components and not hooks. In the following example availableInterests is pulled from a firestore database call. Then we loop through the availableInterests so the user can select the their interests from the Flatlist of interests. Everything works great except the FLatlist does not re-render so the button that is used to select currentInterests never shows the change that an interest has been selected. Does anyone see what I am missing here?
const [availableInterests, setAvailableInterests] = useState([]);
const [currentInterests, setCurrentInterests] = useState([]);
const selectThisInterest = (item) => {
let myInterests = currentInterests;
if(myInterests.includes(item.id)) {
myInterests.pop(item.id);
} else {
myInterests.push(item.id);
}
setCurrentInterests(myInterests);
}
return <View>
<Text style={styles.text}>Select Your Interests:</Text>
<FlatList
data={availableInterests}
keyExtractor={(item, index) => index.toString()}
extraData={currentInterests}
renderItem={({ item, index }) =>
<View key={item.id}>
<Text>{item.title}</Text>
<Text>{item.description}</Text>
<Image
source={{ uri: item.icon }}
style={{ width: 100, height: 100}}
/>
<TouchableOpacity onPress={() => selectThisInterest(item)}>
<Text style={styles.buttonText}>{`${currentInterests.includes(item.id) ? 'UnSelect' : 'Select'}`}</Text>
<Text>{item.id}</Text>
</TouchableOpacity>
</View>
}>
</FlatList>
</View>
put this state below
const [currentInterests, setCurrentInterests] = useState([]);
const [extra, setExtra] = useState(0);
at the end of your function just put this
const selectThisInterest = (item) => {
....
setExtra(extra + 1)
}
I think the mistake is in your selectThisInterest function. When you are updating the currentInterests based on previous value, React doesn't recognises such a change because you are simply assigning myInterests with your currentInterests.
What you want to do is to copy that array and assign it to myInteresets and then update your values to the new copied array. Once the calculation are completed on the new myInteresets array, the setCurrentInterests() will re-render the app because now React recognises there is a change in the state.
To copy the array, you can use,
let myInterests = [...currentInterests];
change your selectThisInterest function to reflect this change,
const selectThisInterest = (item) => {
let myInterests = [...currentInterests];
if(myInterests.includes(item.id)) {
myInterests.pop(item.id);
} else {
myInterests.push(item.id);
}
setCurrentInterests(myInterests);
}

react native flat list how to force list items to be the same height?

I have a React-Native application where I am using FlatList to display a list of items obtained from the server. The list has 2 columns and I need my list items to be the same height. I put a border around the code rendering my list items but the list items are not the same height. I have tried using flexbox settings to make the view fill the container, but everything I try makes no difference.
I have created a simplified version of my app to illustrate the issue:
See that the red bordered areas are NOT the same height. I need to get these to be the same height.
The grey border is added in the view wrapping the component responsible for a list item and the red border is the root view of the component responsible for a list item. See the code below for clarity.
I can not use the grey border in my application because my application shows empty boxes whilst the component responsible for a list item is getting additional information from the server before it renders itself
Furthermore I can not used fixed sizes for heights.
Application Project structure and code
My code is split up in a manner where the files ending in "container.js" get the data from the server and pass it to its matching rendering component. For example, "MainListContainer" would be getting the list from the server and then pass the list data to "MainList", and "ListItemContainer" would get additional information about the single list item from the server and pass it to "ListItem" to render the actual item. I have kept this model in my simplified application so its as close to my real application as possible.
index.js
import {AppRegistry} from 'react-native';
import MainListContainer from './app/components/MainListContainer';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => MainListContainer);
MainListContainer.js
import React from 'react';
import MainList from './MainList';
const data = [
{id: '1', title: 'Item 1', subtitle: 'A', description: 'This is the first item.'},
{id: '2', title: 'Item 2', subtitle: 'B', description: 'The Big Brown Fox Jumped over the lazy dogs. The Big Brown Fox Jumped over the lazy dogs.',},
];
const MainListContainer = () => {
return ( <MainList items={data} /> );
};
export default MainListContainer;
MainList.js
import React from 'react';
import {StyleSheet, FlatList, View} from 'react-native';
import ListItemContainer from './ListItemContainer';
export default class MainList extends React.Component {
constructor(props) {
super(props);
this.state = { numColumns: 2};
this.renderItem = this.renderItem.bind(this);
}
renderItem({item, index}) {
return (
<View style={styles.flatListItemContainer}> <!-- THIS IS WHERE THE GREY BORDER IS ADDED -->
<ListItemContainer key={index} item={item} />
</View>
);
}
render() {
const {items} = this.props;
const {numColumns} = this.state;
return (
<View>
<FlatList
data={items}
renderItem={this.renderItem}
numColumns={numColumns}
key={numColumns}
keyExtractor={(item) => item.id}
/>
</View>
);
}
};
const styles = StyleSheet.create({
flatListItemContainer: {
flex: 1,
margin: 10,
borderColor: '#ccc',
borderWidth: 1,
},
});
ListItemContainer.js
import React from 'react';
import ListItem from './ListItem';
const ListItemContainer = (props) => {
const { item } = props;
return (
<ListItem item={item} />
);
};
export default ListItemContainer;
ListItem.js
import React from 'react';
import {TouchableHighlight, View, StyleSheet, Image, Text} from 'react-native';
const ListItem = (props) => {
const { item } = props;
return (
<TouchableHighlight
underlayColor="white"
>
<View style={styles.containerView}> <!-- THIS IS WHERE THE RED BORDER IS ADDED -->
<View style={styles.top_row}>
<Image style={styles.image} source={require('../images/placeholder.png')} />
<View style={styles.title_texts}>
<Text style={{fontWeight:'bold'}}>{item.title}</Text>
<Text style={{color: 'rgb(115, 115, 115)'}}>{item.subtitle}</Text>
</View>
</View>
<Text>{item.description}</Text>
</View>
</TouchableHighlight>
);
};
export default ListItem;
const styles = StyleSheet.create({
containerView: {
padding: 14,
borderColor: 'red',
borderWidth: 1,
},
top_row: {
flex: 1,
flexDirection: 'row',
marginBottom: 10,
},
title_texts: {
flex: 1,
flexDirection: 'column',
},
image: {
alignSelf: 'flex-end',
resizeMode: 'cover',
height: 40,
width: 40,
marginRight: 20
},
});
What I have tried
ListItem.js : move the style onto the "TouchableHighlight" view
ListItem.js : add a view wrapping "TouchableHighlight" view and adding style there
ListItem.js : added "alignItems:'stretch' on the "TouchableHighlight, added it to the "containerView" style, tried it on the description field too
same as "alignItems" but used "alignedSelf" instead
same as "alignItems" but used "alignedContent" instead
tried using "flexGrow" on different views (container, description)
You can measure the height of every element in the list and when you determine the maximum height, you can use that height for every element in the list.
const Parent = ({ ...props }) => {
const [maxHeight, setMaxHeight] = useState<number>(0);
const computeMaxHeight = (h: number) => {
if (h > maxHeight) setMaxHeight(h);
}
return (
<FlatList
data={props.data}
renderItem={({ item }) => (
<RenderItem
item={item}
computeHeight={(h) => computeMaxHeight(h)}
height={maxHeight}
/>
)}
....
/>
)
}
The Items:
const RenderItem = ({...props }) => {
return (
<View
style={{ height: props.height }}
onLayout={(event) => props.computeHeight(event.nativeEvent.layout.height)}
>
<Stuffs />
</View>
)
}
This is a very non-performant way of achieving this. I would avoid this if I have a long list or any list of more than a few items. You however can put certain checks in place to limit rerendering etc. Or alternatively if it is only text that will affect the height, then you can only measure the height of the element with the most text and use that element's height for the rest.
Instead of set fixed width height, you can use flex box to achieve it. I just solved the issue by removing alignSelf at the FlatList and add alignItems center on it.
Wrap the flatList in flex box with align item center, you can add the code in your MainList.js file, the first <View>, i.e:
render() {
const {items} = this.props;
const {numColumns} = this.state;
return (
<View style={{flex: 1, alignItems: 'center'>
<FlatList
data={items}
renderItem={this.renderItem}
numColumns={numColumns}
key={numColumns}
keyExtractor={(item) => item.id}
/>
</View>
);
If still not reflected, you may try to add flex:1, alignItems center in FlatList style props.
You are missing a very basic concept of giving fixed height to the flatlist items, in your ListItem.js, try to set height:200 in containerView. Let me know if that works for you

what react native component for creating list with sub item

what react native component for creating list with sub items? I checked the docs, it could be flatlist, but flatlist doesn't say anything about sliding in sub item.
You can use <FlatList> for efficient displaying of a large list. This <FlatList> is recommended if you have a large list. Then you can contain each content of a list in <TouchableWithoutFeedback> and provide onPress event handler.
for eg.
<FlatList>
<TouchableWithoutFeedback onPress={/*---include your selection logic here*/}>
/* your content come here */
</TouchableWithoutFeedback>
</FlatList>
Also, if you want to apply animation to drop down list I would recommend you to use <LayoutAnimation>
You can use react-native-collapsible.
it will help you to achieve same things and you can design your own styling by creating customizable view.
Installation
npm install --save react-native-collapsible
Example
import React, { Component } from 'react-native';
import Accordion from 'react-native-collapsible/Accordion';
const SECTIONS = [
{
title: 'First',
content: 'Lorem ipsum...'
},
{
title: 'Second',
content: 'Lorem ipsum...'
}
];
class AccordionView extends Component {
state = {
activeSections: []
};
_renderSectionTitle = section => {
return (
<View style={styles.content}>
<Text>{section.content}</Text>
</View>
);
};
_renderHeader = section => {
return (
<View style={styles.header}>
<Text style={styles.headerText}>{section.title}</Text>
</View>
);
};
_renderContent = section => {
return (
<View style={styles.content}>
<Text>{section.content}</Text>
</View>
);
};
_updateSections = activeSections => {
this.setState({ activeSections });
};
render() {
return (
<Accordion
sections={SECTIONS}
activeSections={this.state.activeSections}
renderSectionTitle={this._renderSectionTitle}
renderHeader={this._renderHeader}
renderContent={this._renderContent}
onChange={this._updateSections}
/>
);
}
}
You can customize the view by using Properties

FlatList rendered row but not displaying items

I am trying to make a FlatList with items that can expand and collapse onPress
However, when I add a new item from another screen then go back to SearchListScreen, it will only display 2 items, but the FlatList does render the correct number of rows.
example:
Before adding new item
After adding new item
The same thing happens when I remove an item or expand a item.
Here's my code:
SearchList.js
import React, { Component } from 'react'
import { Text, View, FlatList, StyleSheet } from 'react-native'
import SearchCard from './SearchCard'
export default class SearchList extends Component {
wrapperStyle (index) {
return index > 0 ? styles.listItemWrapper : [styles.listItemWrapper, styles.wrapperFirst]
}
_renderItem = ({item, index}) => (
<View style={this.wrapperStyle(index)}>
<SearchCard
search={item}
id={item.id}
filterAttributes={this.props.filterAttributes}
onSearch={this.props.onSearch}
onFavorite={this.props.onFavorite}
favorites={this.props.favorites}
/>
</View>
)
render () {
const { searches, filterAttributes, onSearch, onFavorite, favorites } = this.props
return (
<FlatList
data={searches}
extraData={{ filterAttributes: filterAttributes, onSearch: onSearch, onFavorite: onFavorite, favorites: favorites, searches: searches }}
keyExtractor={item => item.id}
renderItem={this._renderItem}
enableEmptySections
style={{backgroundColor: 'red'}}
/>
)
}
}
const styles = StyleSheet.create({
wrapperFirst: {
marginTop: 20
},
listItemWrapper: {
marginLeft: 20,
marginRight: 20,
marginBottom: 20
}
})
After hours of struggling, I find that adding a height to the item solved the problem.
It could be an issue related to the styles of StyleSheet applied to either the row or the FlatList itself. In my case I applied a wrong style property to the FlatList which in return did not display the list.