I would like to map a more dimensional array that looks like this:
const array = [
{
name: "Anna",
items: ["Bread", "Cake", "Wine"]
},
{
name: "John",
items: ["Cucumber", "Pizza", "Jam"]
}
]
I've tried this:
class Example extends Component {
render() {
return (
<View>
{
array.map((data) => {
return(
<Text>{data.name}</Text>
{
data.items.map((item) => {
return (
<Text>{item}</Text>
);
}
);
}
}
</View>
);
}
}
I have also tried to put this into a function that I'm rendering but neither is working for me
can you help ?
Maybe this one can help you. Also you should use key otherwise you got warn during rendering.
class Example extends Component {
_renderYYY(item) {
console.log(item);
return item.map((data) => {
return (
<View>
<Text>{data}</Text>
</View>
);
});
}
_renderXXX(array) {
return array.map((data) => {
return (
<View>
<Text key={data.name}>{data.name}</Text>
{
this._renderYYY(data.items)
}
</View>
);
});
}
render() {
return (
<View>
{
this._renderXXX(array)
}
</View>
);
}
}
A limitation of JSX is that a JSX object must always have one single root.
That is,
return (
<Text>One</Text>
<Text>Two</Text>
);
is not valid.
You should wrap the return value of the outer map (including the outer <Text> and the inner result of .map()) with a root element (probably <View>).
Additionally, you should always use the key={} prop, and give it a a globally unique value when rendering an array into JSX.
All in all, I'd have something like this:
class Example extends Component {
render() {
return (
<View>
{
array.map((data) => (
<View key={data.name}>
<Text>{data.name}</Text>
{
data.items.map((item) => (
<Text key={data.name + item}>{item}</Text>
))
}
</View>
))
}
</View>
);
}
}
I'm assuming that there cannot be duplicate names, and that there cannot be duplicate items inside of a single named object.
Here this might help.
class Example extends Component {
renderData = (array) => (
array.map((data, index) => ([
<Text key={index}>{data.name}</Text>,
this.renderItems(data.items)
])
))
renderItems = (items) => (
items.map((item, index) => (
<Text key={`${item}${index}`}>{item}</Text>
)
))
render() {
return (
<View>
{this.renderData(array)}
</View>
);
}
}
Related
Using the Open Library API I am trying to get the first lccn from the array which is 2002044748 which looks something like this.
"first_publish_year": 1937,
"lccn": [
"2002044748",
"67029221",
"2013497341",
"38005859",
"2012545250",
"73008769",
"88156046",
"67000312",
"37038859",
"2007028554",
"84009023",
"2012278060",
"77078707",
"2002075940",
"88009061",
"98102207",
"90156122",
"2001276594",
"2012039220"
],
The lccn - 2002044748 is equal to the book title The Hobbit.
If you will take a closer look on it, you'll be convince that it's an array but when I tried to get the type of it by using typeof lccn it returns an object. Even trying to get the first value like lccn[0] did not work since it's saying it's an object.
What I did is I tried to iterate on the object and see if value will be string since it looks like a string.
Object.entries(lccn).map(item => {
console.log(typeof item);
});
But this returned an object again. I am really confuse on this. How do i get the first item from the lccn array that's actually equates to the title.
Any idea how? I am a newbie when it comes to array.
UPDATE:
I have the following codes on my component:
class BookListingScreen extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
books: [],
errorMessage: '',
isFetching: true,
};
}
async fetchRandomBooks() {
try {
let response = await fetch(
'https://openlibrary.org/search.json?author=tolkien',
);
let json = await response.json();
this.setState({books: json.docs, isFetching: false});
} catch (error) {
this.setState({errorMessage: error});
}
}
componentDidMount() {
this._isMounted = true;
this.fetchRandomBooks();
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
let content = <BookList books={this.state.books} />;
if (this.state.isFetching) {
content = <ActivityIndicator size="large" />;
}
return <View style={styles.container}>{content}</View>;
}
}
Here's the booklist component:
export default class BookList extends Component {
_keyExtractor = item => item.title;
render() {
return (
<FlatList
style={{flex: 1}}
data={this.props.books}
keyExtractor={this._keyExtractor}
renderItem={({item} = this.props.books) => {
return <BookItem item={item} />;
}}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
/>
);
}
}
On my item list props:
export default class BookItem extends Component {
render() {
const {title, lccn} = this.props.item;
console.log(lccn);
return (
<View>
<View style={styles.cardContainerStyle}>
<View style={{paddingRight: 5}}>
{lccn.map(firstLccn => {
return (
<Image
source={{
uri: `https://covers.openlibrary.org/b/lccn/${firstLccn[0]}-M.jpg`,
}}
style={{width: 100, height: 100}}
/>
);
})}
<Text style={styles.cardTextStyle}>{title}</Text>
</View>
</View>
</View>
);
}
}
Basically, I am trying to get the equivalent lccn for each title so I can get the first book cover image as stated on this doc.
It would be data.docs[0].lccn[0]. The JSON you linked to has a docs array where the lccn is in the first element object.
const data = {
numFound: 296,
start: 0,
numFoundExact: true,
docs: [
{
lccn: [
"2002044748",
"67029221",
"2013497341",
"38005859",
"2012545250",
"73008769",
"88156046",
"67000312",
"37038859",
"2007028554",
"84009023",
"2012278060",
"77078707",
"2002075940",
"88009061",
"98102207",
"90156122",
"2001276594",
"2012039220"
]
}
],
num_found: 296,
q: "",
offset: null
};
console.log(data.docs[0].lccn[0]);
Update
TypeError: Cannot read properties of undefined (reading 'map')
at BookItem.render (App.js.js:23:19
The issue in your code is that there are some docs (books) elements that don't have a lccn property. This isn't a problem until in BookItem you attempt to map the lccn array, but you can't since it's undefined.
class BookItem extends React.Component {
render() {
const { title, lccn } = this.props.item;
console.log(lccn); // <-- some undefined values logged
return (
<View>
<View style={styles.cardContainerStyle}>
<View style={{ paddingRight: 5 }}>
{lccn.map((firstLccn) => { // <-- blows up here
return (
...
);
})}
...
</View>
</View>
</View>
);
}
}
See this running Expo Snack for your code and check the console log.
To resolve you can either provide a valid, mappable fallback value when destructuring:
const { title, lccn = [] } = this.props.item;
Or you can use the Optional Chaining operator to guard against the potentially null/undefined value.
{lccn?.map((firstLccn) => {
return (
...
);
})}
Code
class BookItem extends React.Component {
render() {
const { title, lccn } = this.props.item;
console.log(lccn);
return (
<View>
<View style={styles.cardContainerStyle}>
<View style={{ paddingRight: 5 }}>
{lccn?.map((firstLccn) => (
<Image
source={{
uri: `https://covers.openlibrary.org/b/lccn/${firstLccn[0]}-M.jpg`,
}}
style={{ width: 100, height: 100 }}
/>
))}
<Text style={styles.cardTextStyle}>{title}</Text>
</View>
</View>
</View>
);
}
}
If you want to access the first element of an array, you do this:
lccn[0]
The number in the braces is the index of the item. In arrays the first index is 0.
If you want to console.log all elements in an array after eachother, you can do this with the map function.
lccn.map((item)=>{
console.log(item)
})
I saw the lccn you show is an object with key lccn.
SO you can get value by: lccn.lccn[0]
If you want the first element then simply use arr_name[0]. [0] is the index of the very first item.
I managed to fetch data and show to UI with this code:
export default class BoxGarage extends Component {
render() {
let garage = this.props.garage;
garage.name = garage.name.replace('strtoreplace', 'My Garage');
let cars = garage.cars.length ?
garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
)
}) : (
<View style={styles.boxEmpty}>
<Text style={styles.textEmpty}>(No Cars)</Text>
</View>
);
return (
<View style={styles.boxGarage}>
<Text>{ garage.name }</Text>
{ cars }
</View>
)
}
}
Then I tried to change with a function, but no cars shown. What is missing?
export default class BoxGarage extends Component {
render() {
let garage = this.props.garage;
garage.name = garage.name.replace('strtoreplace', 'My Garage');
cars = function(garage) {
if (garage.cars.length) {
garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
);
});
}
else {
return (
<View style={styles.boxEmpty}>
<Text style={styles.textEmpty}>(No Cars)</Text>
</View>
);
}
}
return (
<View style={styles.boxGarage}>
<Text>{ garage.name }</Text>
{ cars(this.props.garage) }
</View>
)
}
}
And I think I should refactor for best practice either using constructor or just move the function outside render, but I don't know what it is. Please advice.
The reason your second code doesn't work is that you're not returning anything from your function if garage.cars.length > 0.
if (garage.cars.length) {
// Added a return statement on the next line
return garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
);
});
}
That said, i think your first version of the code was much cleaner. If a piece of code got complicated enough that i was tempted to make an inline function to do calculations, i'd either pull that out to another class method, or to another component. In your case though, just doing a ternary or an if/else will be much better.
I'm using Flatlist to render a list of items. In Componentwillmount, I set the state to result of a fetch.
constructor() {
super();
this.state = {
listOfCameras: [],
};
}
componentWillMount() {
apiService.listCameras().then((res) => {
this.setState({ listOfCameras: res });
});
}
My Flatlist looks like this:
createListOfCams() {
return (
<FlatList
data={this.state.listOfCameras}
renderItem={({ item, index }) => {
return (
this.createSingleCamera(item, index)
);
}}
keyExtractor={(item, index) => index.toString()}
// extraData={}
/>
);
}
and finally my createSingleCamera looks like this:
createSingleCamera(item, index) {
const modelCam = item.model;
return (
<View style={styles.singleCamLineView}>
<View style={styles.modelCamNameView}>
<Text style={styles.singleCameraText}>{modelCam}</Text>
</View>
<View style={styles.deleteIconView}>
<TouchableOpacity
onPress={() => {
this.deleteCamFromList(item, index);
}}
>
<Text style={styles.singleCameraText}> </Text>
</TouchableOpacity>
</View>
</View>
);
}
What I'd like to do is pass on the index of the item I want to delete to the deleteCamFromList method and then use that to re-render the Flatlist.
So far my deletefromCamList looks like this:
deleteCamFromList(item, index) {
let allCamerasBeforeDelete = [...this.state.listOfCameras];
let newArryOfCams = allCamerasBeforeDelete.filter(index);
}
How would I use filter to return a new array minus the index that I provided it?
The second argument given to Array.prototype.filter is the item index, so:
deleteCamFromList(item, index) {
let newArrayOfCams = this.state.listOfCameras.filter((_, i) => i !== index);
this.setState({listOfCameras: newArrayOfCams})
}
I'm trying to integrate opening app intro sliders to my app, but failing to connect the points to get from the intro to my main app body.
The library i'm using says use 'react-native-app-intro-slider' as such, where an _onDone() function is called to finish the intro and show the 'real' app:
export default class App extends React.Component {
_onDone = () => {
// User finished the introduction. Show "real" app
}
render() {
return (
<AppIntroSlider
slides={slides}
onDone={this._onDone}
/>
);
}
}
With the main body of my app (it works when I run it without the intro-slider addition) being this:
render() {
const contents = collection.map((item, index) => {
return (
<Card key={index}>
[[there's stuff here omitted]]
</Card>
)
});
return (
<View style={{flex:1}}>
<CardStack>
{contents}
</CardStack>
</View>
);
How can I get this to render after the introslider? Do I put all that inside the _onDone() function? (this doesn't work). Or is there a way write _onDone so that after the component, the regular part of the main app would proceed to go as before?
export default class App extends React.Component {
_onDone = () => {
// Something that lets me pass onto the main part of my app
}
render() {
return (
<AppIntroSlider
slides={slides}
onDone={this._onDone}
/>
// The main body of the app that I want React to get to after the <AppIntroSlider> component.
const contents = collection.map((item, index) => {
return (
<Card key={index}>
[[there's stuff here omitted]]
</Card>
)
});
return (
<View style={{flex:1}}>
<CardStack>
{contents}
</CardStack>
</View>
);
);
}
}
If you're not using a navigation library, I would suggest to simply use state:
constructor(props) {
super(props);
this.state = {
showRealApp: false
}
}
_onDone = () => {
this.setState({ showRealApp: true });
}
render() {
if (this.state.showRealApp) {
const contents = collection.map((item, index) => (
<Card key={index}>
{...}
</Card>
));
return (
<View style={{flex:1}}>
<CardStack>
{contents}
</CardStack>
</View>
);
} else {
return <AppIntroSlider slides={slides} onDone={this._onDone}/>;
}
}
You can also consult issue #18 on the react-native-app-intro-slider repo.
I'm mapping an array to be rendered in React Native. On an event (button press) I want to add and object to the array and it be rendered on the screen. I am getting the lifecycle functions to trigger, but not sure if they are needed for this or how to utilize them effectively. Any help would be greatly appreciated!
class Home extends Component {
constructor(props) {
super(props)
this.state = {
data: '',
text: '',
submitted: false,
count: 1,
arr: [
{
key: 1,
text: "text1"
},
],
}
buttonsListArr = this.state.arr.map(info => {
return (
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({count: this.state.count + 1})
}}/>
</View> )
})
}
shouldComponentUpdate = () => {
return true;
}
componentWillUpdate = () => {
this.state.arr.push({key: this.state.count, text: 'text2'})
}
render() {
return (
<View style={styles.container}>
{buttonsListArr}
</View>
)
}
}
What you've written is not typical. A quick fix is to move the logic to a render function like
constructor(props) {
.
.
this.renderText = this.renderText.bind(this)
}
renderText() {
return this.state.arr.map(info => {
return (
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({count: this.state.count + 1})
}}/>
</View> )
})
}
then call the function within the render()
render() {
return (
<View style={styles.container}>
{this.renderText()}
</View>
)
}
You shouldn't be using a lifecycle call to add an element to an array. Simply call setState and it will rerender itself!
Try this.
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({
count: this.state.count + 1
arr: this.state.arr.push({key: this.state.count, text: 'text2'})
})
}}/>
</View> )
return this.arrayholder.map(image => {
return (
<View style={styles.ImageContainer}>
<Image style={styles.ImageContainer} source={image} />
</View>
)
})