SectionList from Multiple Sets of Data - react-native

I am attempting to create a SectionList with Sections as "Inventory" followed by "Ingredients" for each Inventory. I have created an array from two Realm Object Arrays based on whether the inventoryID in Realm Object "Ingredients" matches the recordID in Realm Object "Inventories." I tried my hand at a FlatList, but am leaning towards SectionList for purposes of creating sections. Basically, I want to be able to have corresponding ingredient items for each inventory. The array currently has 1 ingredient item for each inventory. I want to be able to add more ingredient items and have them display underneath each inventory, as in:
Inventory #1 (Section Heading)
ID,
Name,
Date
Ingredient (Section Heading)
Ingredient ID,
Ingredient Type
Ingredient Name
Inventory #2 (Section Heading)
ID,
Name,
Date
Ingredient (Section Heading)
Ingredient ID,
Ingredient Type
Ingredient Name
I basically need assistance on how to loop through the array to create such sections. Any help would be greatly appreciated. Thank you.
My initial code is as follows:
import * as React from 'react';
import {View, Text, SectionList} from "react-native";
import realm from '../schemas/InventoryDatabase';
let inventoryArray = [];
const inventories = Object.values(realm.objects('Inventories'));
inventories.map((inv) => {
inventoryArray.push({recordID: inv.recordID, inventoryName: inv.inventoryName, date: inv.date});
const ingredients = realm.objects('Ingredients').filtered('inventoryID == $0', inv.recordID);
ingredients.map((ing) => {
inventoryArray.push({ingredientID: ing.ingredientID, ingredientType: ing.ingredientType, ingredient: ing.ingredient});
});
});
This is a console.log printout of inventoryArray:
[{"date": "Tue Feb 14 2023", "inventoryName": "Test 1", "recordID": 1}, {"ingredient": "Baking Powder", "ingredientID": 1, "ingredientType": "Misc"}, {"date": "Tue Feb 14 2023", "inventoryName": "Test 2", "recordID": 2}, {"ingredient": "White", "ingredientID": 2, "ingredientType": "Bread"}]
Edit after trying answer below: Getting Type error on SecitonList.js file:
render(): React.Node {
const {
stickySectionHeadersEnabled: _stickySectionHeadersEnabled,
...restProps
} = this.props;
const stickySectionHeadersEnabled =
_stickySectionHeadersEnabled ?? Platform.OS === 'ios';
return (
<VirtualizedSectionList
{...restProps}
stickySectionHeadersEnabled={stickySectionHeadersEnabled}
ref={this._captureRef}
getItemCount={items => items.length}
getItem={(items, index) => items[index]}
/>
);
}
The error is "Cannot read property length of undefined" It points to the line above "getItemCount={items => items.length}. I will work on a resolution.

import * as React from 'react';
import {View, Text, SectionList} from "react-native";
import realm from '../schemas/InventoryDatabase';
let inventoryArray = [];
const inventories = Object.values(realm.objects('Inventories'));
inventories.forEach((inv) => {
const ingredients = realm.objects('Ingredients').filtered('inventoryID == $0', inv.recordID);
const ingredientArray = ingredients.map((ing) => ({ingredientID: ing.ingredientID, ingredientType: ing.ingredientType, ingredient: ing.ingredient}));
inventoryArray.push({title: inv.inventoryName, recordID: inv.recordID, date: inv.date, data: ingredientArray});
});
const renderItem = ({item}) => {
return (
<View>
<Text>{item.ingredientID}</Text>
<Text>{item.ingredientType}</Text>
<Text>{item.ingredient}</Text>
</View>
);
};
const renderSectionHeader = ({section: {title}}) => {
return (
<View>
<Text>{title}</Text>
</View>
);
};
const InventoryList = () => {
return (
<SectionList
sections={inventoryArray}
keyExtractor={(item, index) => item + index}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
/>
);
};
export default InventoryList;

I truly appreciate the assistance. However, I was able to solve by using a FlatList of inventory Realm object array and map of Ingredient Realm object array instead. I filtered the Ingredient Realm object array with a condition to find which inventoryID matched the inventory recordID. See code below.
import * as React from 'react';
import {View, Text, FlatList} from "react-native";
import realm from '../schemas/InventoryDatabase';
const inventories = Object.values(realm.objects('Inventories'));
const ingredients = Object.values(realm.objects('Ingredients'));
export default class ViewInventory extends React.Component {
constructor(props) {
super(props);
}
ListViewItemSeparator = () => {
return (
<View style={{ height: 0.5, width: '100%', backgroundColor: '#000' }} />
);
};
render() {
return (
<View>
<FlatList
data={inventories}
ItemSeparatorComponent={this.ListViewItemSeparator}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<View style={{ backgroundColor: 'white', padding: 20 }}>
<Text>Inventory ID: {item.recordID}</Text>
<Text>Name: {item.inventoryName}</Text>
<Text>Date: {item.date}</Text>
<View>
{
ingredients.filter(ing => ing.inventoryID == item.recordID).map(
(ingredient) => {
return (
<Text>IngredientID: {ingredient.ingredientID}</Text>,
<Text>IngredientType: {ingredient.ingredientType}</Text>,
<Text>Ingredient: {ingredient.ingredient}</Text>
)})
}
</View>
</View>
)}
/>
</View>
);
}
}

Related

Objects are not valid as a React child found: object with keys {cca2, currency, callingCode, region, subregion, flag, name}

I am using component with AppPicker that selects a country then passes the selected country to another component through navigation.navigate('component', { parameter: parameter}), my problem is, I am getting Objects are not valid as a React child. the AppPicker's onselect method returns an array of objects as shown below and I have a function that extracts only the country name, the country name is stored in country state and I pass that to another component for display. Can anyone help with this problem. my components are shown below:
Covid.js
import React, { useState } from 'react';
import { StyleSheet, TouchableOpacity, Text, View } from 'react-native';
import CountryPicker from 'react-native-country-picker-modal'
export default function App(props) {
const [country, setCountry] = useState(null) ;
const getCountry = (country) => {
return country.name;
}
return (
<View style={styles.container}>
{/* <TouchableOpacity onPress={ () => props.navigation.navigate('CovidCountryData', { selectedCountry: country})}> */}
<Text>
Welcome to Country Picker !
</Text>
<CountryPicker
withFilter
onSelect={(country) => {
const countrySelect= getCountry(country)
setCountry(countrySelect);
console.log(countrySelect);
props.navigation.navigate('CovidCountryData', { selectedCountry: country})
}}
/>
{/* </TouchableOpacity> */}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
the onselect returns the array shown below
Object {
"callingCode": Array [
"213",
],
"cca2": "DZ",
"currency": Array [
"DZD",
],
"flag": "flag-dz",
"name": "Algeria", =================> this is the country name I am extracting..
"region": "Africa",
"subregion": "Northern Africa",
}
CovidCountryData.js
import React from 'react'
import {Text, StyleSheet} from 'react-native';
export default function CovidCountryData(props) {
//console.log(props.navigation)
let Country = props.navigation.getParam('selectedCountry'); //selectedCountry
console.log(Country) ;
return (
<>
<Text>You have selected the country shown below</Text>
<Text>{Country}</Text>
</>
)
}
Here, you are sending through the navigation the whole country object received from the onSelect function inside your CountryPicker, because the variable shares the same name as the one you're keeping in state. Refactor it this way to understand the changes:
<CountryPicker
withFilter
onSelect={(selectedCountry) => {
const countrySelect= getCountry(selectedCountry)
setCountry(countrySelect);
console.log(countrySelect);
props.navigation.navigate('CovidCountryData', { selectedCountry: country}) // before even if you sent the country as param here, it wasn't the country from your state, but the one which is now called selectedCountry
}}
/>

onChangeText returns the previous state value

I am trying to make a dynamic form. The user must enter the required count of fields in the first text field. After that, I add the entered count to the state and render all the fields. But in "onChangeText" the previous state value is returned. At the same time, if I click on the button, the current state value is returned?
import React, {useState} from "react";
import {Button, TextInput, View, StyleSheet} from "react-native";
export const TestScreen = () => {
const initialState: any = {
num: null,
inputs: null
};
const [state, setState] = useState(initialState);
const changeNum = (num = null) => {
const inputs = [];
if (num) {
for (let i = 0; i < num; i++) {
inputs.push(
<TextInput
key={i.toString()}
style={styles.input}
onChangeText={changeNewInput}
/>
)
}
}
setState({num, inputs})
};
const changeNewInput = (e) => {
console.log(state.num)
};
return (
<View>
<TextInput onChangeText={changeNum} style={{...styles.input, borderColor: 'red'}}/>
{state.inputs}
<Button title={'see count'} onPress={() => console.log(state.num)}/>
</View>
)
};
const styles = StyleSheet.create({
input: {
borderWidth: 1,
borderColor: 'black',
marginBottom: 5
}
});
the state update using the updater provided by useState hook is asynchronous. That's why you see previous value of your state when you call changeNewInput method after setState. If you want to see your state is updated you can use, useEffect.
useEffect(() => {
console.log(state.num)
}, [state]);
I'm using my code example
Create a new useEffect and set a new State inside
OLD STATE const [comment, setComment] = useState('')
NEW STATE const [newData, setNewData] = useState(null)
onChangeText={text => setComment(text)}
useEffect(() => {
setNewData(comment)
},[comment])

Why isn't flatlist not able to map through and display the data?

I am using Zomato API to get the list of restaurants, data is in the form of the array which has object restaurants in which there are different restaurant objects and then their name and other details.
It's my first time using a flat List and I am not able to display this data.
Goal: Use the search bar to display the list of restaurants in the city using flatlist.
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { SearchBar } from 'react-native-elements';
import { FlatList } from 'react-native-gesture-handler';
class Main extends Component {
constructor(props) {
super(props);
this.state = { search: '', data: [] };
}
componentDidMount() {
fetch('https://developers.zomato.com/api/v2.1/search', {
method: 'GET',
headers: {
'user-key': '999999999999999999999999999'
},
params: JSON.stringify({
entity_type: 'city',
q: {this.state.search}
}),
}).then((response) => response.json())
.then((responseJson) => { return this.setState({ data: responseJson.restaurants }) })
.catch((error) => { console.warn(error) }
);
}
render() {
let data = this.state.data;
let name = data.map(p => p.restaurant.name)
console.warn("Check Data", name)
return (
<View>
<SearchBar
round
searchIcon={{ size: 24 }}
placeholder='Search'
onChangeText={search => { this.setState({ search }) }}
value={this.state.search}
/>
//Using this method to display the data if any
{name.length != 0 ?
<FlatList
data={name}
keyExtractor={(x, i) => x + i}
renderItem={({ name }) => <Text>{name}-DATA</Text>}
/>
: <View style={{height:"100%", width:"100%", alignItems:"center",
justifyContent:"center"}}>
<Text>No data found</Text>
</View>
}
</View>
);
}
}
export default Main;
Maybe the way I declared state is wrong, or maybe the way I'm storing the data in the state is wrong.
I got the names of the restaurant in the console.warn successfully.
Without your users-key I can't surely understand what is your api results.
Here
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { SearchBar } from 'react-native-elements';
import { FlatList } from 'react-native-gesture-handler';
class Main extends Component {
constructor(props) {
super(props);
this.state = { search: '', data: [] };
}
componentDidMount() {
fetch('http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop/', {
method: 'GET',
}).then((response) => response.json())
.then((responseJson) => { return this.setState({ data: responseJson }) })
.catch((error) => { alert(error) }
);
}
render() {
let data = this.state.data;
return (
<View>
<SearchBar
round
searchIcon={{ size: 24 }}
placeholder='Search'
onChangeText={search => { this.setState({ search }) }}
value={this.state.search}
/>
{data.length != 0 ?
<FlatList
data={data}
keyExtractor={(x, i) => x + i}
renderItem={({ item }) => <Text>{item.name}-DATA</Text>}
/>
: <View style={{height:"100%", width:"100%", alignItems:"center",
justifyContent:"center"}}>
<Text>No data found</Text>
</View>
}
</View>
);
}
}
export default Main;
This is a working code with another api call just add your api call instead on mine.. This is working properly. I guess you are just messing with your data.
Try replacing the renderItem from FlatList to
renderItem={({ item }) => <Text>{item}-DATA</Text>}
Also, replace the condition to use double equals like name.length !== 0
Check the url link properly.
https://developers.zomato.com/api/v2.1/search/999999999999999999999999999
Just check it on web browser it is showing message : The requested url was not found
It means we are not getting any data from this URL.
That why we are not able to map any data.

React Native State is Undefined Vasern

I am actually starting with Mobile Development and React Native and I thought an interesting Database called Vasern But now I am trying to load things from my database with the componentDidMount() method but i actually just get this Error everytime.
I am sure its just a trivial Error but i just cant find it...
Thanks in Advance
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
Button,
SectionList
} from 'react-native';
import Vasern from 'vasern';
import styles from './styles';
const TestSchema = {
name: "Tests",
props: {
name: "string"
}
}
const VasernDB = new Vasern({
schemas: [TestSchema],
version: 1
})
const { Tests } = VasernDB;
var testList = Tests.data();
class Main extends Component {
state = {
tests: [],
};
constructor(props){
super(props);
this._onPressButton = this._onPressButton.bind(this);
this._onPressPush = this._onPressPush.bind(this);
this._onPressUpdate = this._onPressUpdate.bind(this);
this._onPressDeleteAll = this._onPressDeleteAll.bind(this);
}
componentDidMount() {
Tests.onLoaded(() => this._onPressButton());
Tests.onChange(() => this._onPressButton());
}
_onPressButton = () => {
let tests = Tests.data();
console.log(tests);
//if( tests !== undefined){
this.setState({tests}); // here is the error
//alert(tests);
//}
}
_onPressPush = () => {
Tests.insert({
name: "test"
});
console.log(this.state.tests + "state tests"); //here the console only shows the text
}
_onPressUpdate = () => {
var item1 = Tests.get();
Tests.update(item1.id, {name: "test2"});
}
_onPressDelete = () => {
}
_onPressDeleteAll = () => {
let tests = Tests.data();
tests.forEach((item, i) => {
Tests.remove(item.id)
});
}
_renderHeaderItem({ section }) {
return this._renderItem({ item: section});
}
_renderItem({ item }){
return <View><Text>{item.name}</Text></View>
}
render() {
//const { tests = [] } = this.props;
return (
<View style={styles.container}>
<View>
<TextInput> Placeholder </TextInput>
<Button title="Press me for Show" onPress={this._onPressButton}/>
<Button title="Press me for Push" onPress={this._onPressPush}/>
<Button title="Press me for Update" onPress={this._onPressUpdate}/>
<Button title="Press me for Delete" onPress={this._onPressDelete}/>
<Button title="Press me for Delete All" onPress={this._onPressDeleteAll}/>
</View>
<View style={styles.Input}>
<SectionList
style={styles.list}
sections={this.state.tests}
keyExtractor={item => item.id}
renderSectionHeader={this._renderHeaderItem}
contentInset={{ bottom: 30 }}
ListEmptyComponent={<Text style={styles.note}>List Empty</Text>}
/>
</View>
</View>
);
}
}
export default Main;
Since Tests.data() will return an array (as list of records).
In React Native, you might use FlatList to display an array of records.
import { ..., FlatList } from 'react-native';
...
// replace with SectionList
<FlatList
renderItem={this._renderItem} // item view
data={this.state.tests} // data array
style={styles.list}
keyExtractor={item => item.id}
contentInset={{ bottom: 30 }}
ListEmptyComponent={<Text style={styles.note}>List Empty</Text>}
/>
In case you want to use SectionList, you will need to reform data into sections. Something like:
var sections = [
{title: 'Title1', data: ['item1', 'item2']},
{title: 'Title2', data: ['item3', 'item4']},
{title: 'Title3', data: ['item5', 'item6']},
]
Besides, React Native is in a really good state. I think you will like it.

why are colors being updated multiple times

Why are my random colors being updated multiple times? Is the right way to control this behavior through a lifecycle event?
import React from 'react';
import { StyleSheet, Text, ScrollView, FlatList, SectionList, View, Button, SegmentedControlIOS } from 'react-native';
import contacts, {compareNames} from './contacts';
import {Constants} from 'expo';
import PropTypes from 'prop-types'
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
const Row=(props)=>(
<View style={styles.row}>
<Text style={{color:props.color}} >{props.name}</Text>
<Text >{props.phone}</Text>
</View>
)
const renderItem=(obj)=> {
return(<Row {...(obj.item)} color={getRandomColor()} />)
}
const ContactsList = props => {
const renderSectionHeader=(obj) =><Text>{obj.section.title}</Text>
const contactsByLetter = props.contacts.reduce((obj, contact) =>{
const firstLetter = contact.name[0].toUpperCase()
return{
...obj,
[firstLetter]: [...(obj[firstLetter] || []),contact],
}
},{})
const sections = Object.keys(contactsByLetter).sort().map(letter=>({
title: letter,
data: contactsByLetter[letter],
}))
return(
<SectionList
keyExtractor = { (item, key) => key.toString() }
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
sections={sections}
/>
)}
ContactsList.propTypes ={
renderItem: PropTypes.func,
renderSectionHeader: PropTypes.func,
contacts: PropTypes.array,
sections: PropTypes.func
}
export default class App extends React.Component {
state={show: false, selectedIndex: 0, contacts: contacts}
toggleContacts=()=>{
this.setState({show:!this.state.show})
}
sort=()=>{
this.setState({contacts: [...this.state.contacts].sort(compareNames)})
}
render() {
return (
<View style={styles.container}>
<Button title="toggle names" onPress={this.toggleContacts} />
<Button title="sort" onPress={this.sort} />
<SegmentedControlIOS
values={['ScrollView', 'FlatList','SectionList']}
selectedIndex={this.state.selectedIndex}
onChange={(event) => {
this.setState({selectedIndex: event.nativeEvent.selectedSegmentIndex});
}} />
{this.state.show && this.state.selectedIndex === 0 &&
<ScrollView >
{this.state.contacts.map(contact=>(
<Row {...contact}/> ))}
</ScrollView>}
{this.state.show && this.state.selectedIndex === 1 &&
<FlatList
data={this.state.contacts}
keyExtractor = { (item, index) => index.toString() }
renderItem={renderItem}>
</FlatList>}
{this.state.show && this.state.selectedIndex === 2 &&
<ContactsList
contacts={this.state.contacts}>
</ContactsList>}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
// alignItems: 'flex-start',
paddingTop: Constants.statusBarHeight + 25,
},
row: {
padding:20,
},
});
const NUM_CONTACTS = 10
const firstNames = ['Emma','Noah','Olivia','Liam','Ava','William','Sophia','Mason','Isabella','James','Mia','Benjamin','Charlotte','Jacob','Abigail','Michael','Emily','Elijah','Harper','Ethan','Amelia','Alexander','Evelyn','Oliver','Elizabeth','Daniel','Sofia','Lucas','Madison','Matthew','Avery','Aiden','Ella','Jackson','Scarlett','Logan','Grace','David','Chloe','Joseph','Victoria','Samuel','Riley','Henry','Aria','Owen','Lily','Sebastian','Aubrey','Gabriel','Zoey','Carter','Penelope','Jayden','Lillian','John','Addison','Luke','Layla','Anthony','Natalie','Isaac','Camila','Dylan','Hannah','Wyatt','Brooklyn','Andrew','Zoe','Joshua','Nora','Christopher','Leah','Grayson','Savannah','Jack','Audrey','Julian','Claire','Ryan','Eleanor','Jaxon','Skylar','Levi','Ellie','Nathan','Samantha','Caleb','Stella','Hunter','Paisley','Christian','Violet','Isaiah','Mila','Thomas','Allison','Aaron','Alexa','Lincoln']
const lastNames = ['Smith','Jones','Brown','Johnson','Williams','Miller','Taylor','Wilson','Davis','White','Clark','Hall','Thomas','Thompson','Moore','Hill','Walker','Anderson','Wright','Martin','Wood','Allen','Robinson','Lewis','Scott','Young','Jackson','Adams','Tryniski','Green','Evans','King','Baker','John','Harris','Roberts','Campbell','James','Stewart','Lee','County','Turner','Parker','Cook','Mc','Edwards','Morris','Mitchell','Bell','Ward','Watson','Morgan','Davies','Cooper','Phillips','Rogers','Gray','Hughes','Harrison','Carter','Murphy']
// generate a random number between min and max
const rand = (max, min = 0) => Math.floor(Math.random() * (max - min + 1)) + min
// generate a name
const generateName = () => `${firstNames[rand(firstNames.length - 1)]} ${lastNames[rand(lastNames.length - 1)]}`
// generate a phone number
const generatePhoneNumber = () => `${rand(999, 100)}-${rand(999, 100)}-${rand(9999, 1000)}`
// create a person
const createContact = () => ({name: generateName(), phone: generatePhoneNumber()})
// compare two contacts for alphabetizing
export const compareNames = (contact1, contact2) => contact1.name > contact2.name
// add keys to based on index
const addKeys = (val, key) => ({key, ...val})
// create an array of length NUM_CONTACTS and alphabetize by name
export default Array.from({length: NUM_CONTACTS}, createContact).map(addKeys)
It looks like your component is triggering the render method multiple times. This happens mostly when you use setState method, since everytime your state changes the render method is triggered.
You may have 2 options to handle this:
1) Identify where your component is being rerendered and treat it if this behavior is unnecessary. You can use console.log to debug it.
2) You can avoid the random method to be called multiple times if you call it only in your componentDidMount method. Since componentDidMount is called only once in the component lifecycle, all functions inside of it will not be triggered when the component rerenders, only when it mount again. (I think this is the solution)
Let me know if it helps.