React-Native returning objects from nested array - javascript

I am new to React-Native and struggling to return objects from a nested array (Hopefully I am using the correct terminology).
I am grabbing my data from the tfl tube status api JSON is below:
[
{
"$type": "Tfl.Api.Presentation.Entities.Line,
Tfl.Api.Presentation.Entities",
"id": "bakerloo",
"name": "Bakerloo",
"modeName": "tube",
"disruptions": [],
"created": "2018-03-13T13:40:58.76Z",
"modified": "2018-03-13T13:40:58.76Z",
"lineStatuses": [
{
"$type": "Tfl.Api.Presentation.Entities.LineStatus,
Tfl.Api.Presentation.Entities",
"id": 0,
"statusSeverity": 10,
"statusSeverityDescription": "Good Service",
"created": "0001-01-01T00:00:00",
"validityPeriods": []
}
],
"routeSections": [],
"serviceTypes": [],
"crowding": {}
},
I am fetching the data using Axios.
state = { lineDetails: [] };
componentDidMount() {
axios.get('https://api.tfl.gov.uk/line/mode/tube/status')
.then(response => this.setState({ lineDetails: response.data }));
};
I am returning the data like this.
renderLineDetails() {
return this.state.lineDetails.map((details) =>
<TubeList
key={details.id}
details={details} />
)};
render() {
return (
<ScrollView>
{this.renderLineDetails()}
</ScrollView>
);
}
My TubeList component looks like:
const TubeList = ({ details }) => {
const { name, statusSeverityDescription } = details;
const { nameStyle, statusStyle } = styles;
return (
<TubeCard>
<CardSectionTitle>
<Text style={nameStyle}>{name}</Text>
</CardSectionTitle>
<CardSectionStatus>
<Text style={statusStyle}>{statusSeverityDescription}</Text>
</CardSectionStatus>
</TubeCard>
);
};
Is someone able to explain why statusSeverityDescription is not displaying in my list below.
Iphone Simulator image
Thank you.

Instead of statusSeverityDescription you have to use lineStatuses and map it for getting statuses.
TubeList:
const TubeList = ({ details }) => {
const { name, lineStatuses } = details;
const { nameStyle, statusStyle } = styles;
return (
<TubeCard>
<CardSectionTitle>
<Text style={nameStyle}>{name}</Text>
</CardSectionTitle>
{lineStatuses.map((status) =>
<CardSectionStatus>
<Text style={statusStyle}>{status.statusSeverityDescription}</Text>
</CardSectionStatus>
}
</TubeCard>
);
};

Thanks for all your comments. I have fixed the issue following Prasun Pal's comments. Below is my new code and screenshot of the working app.
renderLineDetails() {
return this.state.lineDetails.map((details) =>
<TubeList
key={details.id}
lineStatus={details.lineStatuses[0]}
lineName={details}
/>
)};
const TubeList = ({ lineName, lineStatus }) => {
const { statusSeverityDescription } = lineStatus;
const { name } = lineName;
const { nameStyle, statusStyle } = styles;
return (
<TubeCard>
<CardSectionTitle>
<Text style={nameStyle}>{name}</Text>
</CardSectionTitle>
<CardSectionStatus>
<Text style={statusStyle}>{statusSeverityDescription}</Text>
</CardSectionStatus>
</TubeCard>
);
};
iPhone screenshot of working app

Related

Translate gendre_ids from TMDB API in react-native application

I'm wondering how to show a genre to each movie in the list. So I have already other details like title, poster_path or description.
The problem comes when I'm trying to show a genre's becouse they are a numbers and I don't know how to translate them to string like 'Horror'
Here is code for fetch data:
fetch(
`https://api.themoviedb.org/3/search/movie?&api_key=${
this.apiKey
}&query=${searchTerm}`,
)
.then(data => data.json())
.then(data => {
const results = data.results;
const movieRows = [];
const movieGen = [];
results.forEach(movie => {
movie.poster_path =
'https://image.tmdb.org/t/p/w500' + movie.poster_path;
const movies = <MovieRow key={movie.id} movie={movie} />;
movieRows.push(movies);
});
this.setState({rows: movieRows});
});
}
and also diplay it in custom component like movie card:
viewMore = () => {
Alert.alert(
`PRODUCTION : ${this.props.movie.original_language}`,
`DESCRIPTION : ${this.props.movie.overview}\n \n GENRE : ${
this.props.movie.genre_ids
}`,
);
};
render() {
return (
<View
style={{
width: '100%',
alignItems: 'center',
justifyContent: 'center',
}}>
<CardCustom
title={this.props.movie.title}
popularity={this.props.movie.popularity}
vote_count={this.props.movie.vote_count}
poster_path={this.props.movie.poster_path}
onPress={this.viewMore}
/>
</View>
);
}
}
export default MovieRow;
This how this looks like in the application:
and the response from api for genre_ids looks like that
I noticed that I have to use separate API for genre's. Now I want to match them to current movie and I dont know how to do it.
Here is a code
class MovieRow extends Component {
constructor() {
super();
this.apiKey = '1bd87bc8f44f05134b3cff209a473d2e';
this.state = {};
}
viewMore = () => {
Alert.alert(
`PRODUCTION : ${this.props.movie.original_language}`,
`DESCRIPTION : ${this.props.movie.overview}\n \n
GENRE : ${this.props.movie.genre_ids}`, // < ------ NUMBER DISPLAYS. HOW TO MATCH GENRE WITH CURRENT MOVIE?
);
this.fetchGenre();
};
fetchGenre() {
fetch(
`https://api.themoviedb.org/3/genre/movie/list?&api_key=${this.apiKey}`,
)
.then(data => data.json())
.then(data => {
const resultGenres = data.genres;
const genreRow = [];
console.log(resultGenres);
resultGenres.map(genre => {
console.log('name', genre.name, 'id', genre.id);
const genres = <Text>genre: {genre.name}</Text>;
genreRow.push(genres);
});
this.setState({gen: genreRow});
});
}
render() {
return (
<View
style={{
width: '100%',
alignItems: 'center',
justifyContent: 'center',
}}>
<CardCustom
title={this.props.movie.title}
popularity={this.props.movie.popularity}
vote_count={this.props.movie.vote_count}
poster_path={this.props.movie.poster_path}
onPress={this.viewMore}
/>
{this.state.gen}
</View>
);
}
}
also this is how response looks like
Regards
Just get an array with all ids of genders and store it into your state, then when you want to display it you will just use a map. like so :
this.state.gender_ids = [
1: "Action",
2: "Horror",
3: "Other gender"
]
this.props.movie.genre_ids.map(id => <Text key={this.state.gender_ids[id]}>{this.state.gender_ids[id]}</Text>)
Just run the following code into your browser's console, I'm pretty sure from now on you'll get the job done.
Example for pairing :
let gendersFromServer = [
{
id: 28,
name: "Action"
},
{
id: 12,
name: "Adventure"
},
{
id: 16,
name: "Animation"
},
// other genders here
]
let gender_ids = [] // intialize with an empty array
gendersFromServer.map(el => gender_ids[el.id] = el.name) // here you transform the data
// here you can setState({gender_ids})
const movie = {
gender_ids: [
28,
12,
16
]
// rest of data
}
// how to get text gender, notice that gender_ids from console log is the one you use in state, not the one from the movie
movie.gender_ids.map(id => console.log(gender_ids[id]))
EDIT 2:
Hope this will solve your problem finally
import React from 'react'
import { SafeAreaView, ScrollView, View, Text } from 'react-native'
const API_KEY = '1bd87bc8f44f05134b3cff209a473d2e'
export default props => {
const [genres, setGenres] = React.useState([])
const [movies, setMovies] = React.useState([])
React.useEffect(() => {
fetch('https://api.themoviedb.org/3/search/movie?&query=Now+You+See+Me&api_key=' + API_KEY)
.then(res => res.json())
.then(result => {
setMovies(result.results)
})
fetch('https://api.themoviedb.org/3/genre/movie/list?&api_key=' + API_KEY)
.then(genre => genre.json())
.then(result => {
const genres = result.genres.reduce((genres,gen) => {
const { id, name } = gen
genres[id] = name
return genres
},[])
setGenres(genres)
})
},[])
const Movies = () => movies.map(movie => {
return (
<View>
<Text>{movie.title}</Text>
<View>
<Text>Genres :</Text>
{
movie.genre_ids.map(id => {
return <Text>{genres[id]}</Text>
})
}
</View>
</View>
)
})
return (
<SafeAreaView style={{flex: 1}}>
<ScrollView style={{flex: 1}}>
<Text>Movies here</Text>
<Movies />
</ScrollView>
</SafeAreaView>
)
}

undifined is not an object in react native

I'm making an autocomplete with react native. I pulled the data from the API
The following is the API list:
{
"status": 200,
"error": false,
"kecamatan": [
{
"id_kecamatan": "1",
"kecamatan": "Gambir",
"kode": "CGK12020",
"id_kota": "1",
"kota": "Jakarta Pusat",
"id_provinsi": "1",
"provinsi": "DKI Jakarta"
},
{
"id_kecamatan": "2",
"kecamatan": "Tanah Abang",
"kode": "CGK12080",
"id_kota": "1",
"kota": "Jakarta Pusat",
"id_provinsi": "1",
"provinsi": "DKI Jakarta"
},
]
}
when i get with fetch, i get into trouble ie,
undifined is not an object in (evaluating kecamatan.filter)
here is my code
constructor(props) {
super(props);
this.state = {
kecamatan: [],
query: '',
};
}
componentDidMount() {
fetch(`${API}/kecamatan/`)
.then(res => res.json())
.then((json) => {
console.log(json)
const { results: kecamatan } = json;
this.setState({ kecamatan });
});
}
find(query) {
if (query === '') {
return [];
}
const { kecamatan } = this.state;
const regex = new RegExp(`${query.trim()}`, 'i');
return kecamatan.filter(kecamatan => kecamatan.kecamatan.search(regex) >= 0);
}
render() {
const { query } = this.state;
const kecamatan = this.find(query);
const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim();
return (
<View style={styles.container}>
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={kecamatan.length === 1 && comp(query, kecamatan[0].kecamatan) ? [] : kecamatan}
defaultValue={query}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter Star Wars film title"
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => this.setState({ query: item.kecamatan })}
>
<Text style={styles.itemText}>{item.kecamatan}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
please correct, this condition I want to find the name of the district using autocomplete, where I am wrong
I see 2 problems in your filter function:
You're extracting kecamatan from state incorrectly, it should be:
const { kecamatan } = this.state;
You're using the same name for the array and the item, you should use a different name inside your filter like this:
return kecamatan.filter(item => item.kecamatan.search(regex) >= 0);
componentDidMount() {
fetch(`${API}/kecamatan/`)
.then(res => res.json())
.then((json) => {
console.log(json)
const { results: kecamatan } = json;
this.setState({ kecamatan });
});
}
Change const { results: kecamatan } to const { kecamatan } and then it will properly assign it to the state.
results is not defined on the API response.
So when you do const { results: kecamatan } = json;, kecamatan is undefined, which you then set to state. So replace this with const { kecamatan } = json;
You have kecamatan.kecamatan.search(regex) >= 0)
I do not think you have 2 layers of kecamatan as you already pulled it out of the state above?
const { kecamatan } = this.state;
Your kecamatan array is undefined because of the way you have set your fetch results. result is not a key on the returned JSON, so you are trying to filter on undefined. You want const { kecamatan } = json;
If your fetch does ever come back as undefined, you can guard your filter operation (i.e. instead of return kecamatan.filter(result => ..., you could do return (kecamatan || []).filter(result => ... (or in recent Typescript return kecamatan?.filter(result => ....
Also, don't name your iterable the same as its member, you may get unexpected effects. This is why I changed kecamatan.filter(kecamatan => to kecamatan.filter(result =>.

Appending fetched data

I'm trying to build a treeview component in react where data for the tree is fetched based on the nodes expanded by the user.
Problem
I want to replace the code inside handleChange with data from my server, so that I append the data i fetch to the tree state. How can I achieve this with react?
The data i get can look like this:
{
"children": [
{
"id": "2212",
"parentId": "3321",
"name": "R&D",
"address": "homestreet"
},
{
"id": "4212",
"parentId": "3321",
"name": "Testing",
"address": "homestreet"
}
]
}
My Code
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import TreeView from "#material-ui/lab/TreeView";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
import TreeItem from "#material-ui/lab/TreeItem";
const useStyles = makeStyles({
root: {
height: 216,
flexGrow: 1,
maxWidth: 400
}
});
export default function FileSystemNavigator() {
const classes = useStyles();
const initialData = {
root: [
{
id: "1",
label: "Applications"
}
],
};
const [tree, setTree] = useState(initialData);
const handleChange = (event, nodeId) => {
setTimeout(() => {
const newTree = {
...tree,
[nodeId]: [
{
id: "2",
label: "Calendar"
},
{
id: "3",
label: "Settings"
},
{
id: "4",
label: "Music"
}
]
};
setTree(newTree);
}, 1000); // simulate xhr
};
const renderTree = children => {
return children.map(child => {
const childrenNodes =
tree[child.id] && tree[child.id].length > 0
? renderTree(tree[child.id])
: [<div />];
return (
<TreeItem key={child.id} nodeId={child.id} label={child.label}>
{childrenNodes}
</TreeItem>
);
});
};
return (
<TreeView
className={classes.root}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
onNodeToggle={handleChange}
>
{renderTree(tree.root)}
</TreeView>
);
}
If I am understanding correctly, you want to replace your "fake" setTimeout implementation of an API call with a real call using fetch.
In this case, it's as simple as calling fetch inside of the handleChange handler and updating your state with new items that you get back as a result.
function FileSystemNavigator() {
const initialData = {...}
const [tree, setTree] = React.useState(initialData)
const handleChange = (event, nodeId) => {
const handleResult = (data) => {
const items = data.children.map(item => {
return { id: item.id, label: item.name }
})
setTree({
root: [...tree.root, ...items]
})
}
const handleError = (error) => {
// handle errors appropriately
console.error(error.message)
}
fetch("https://api.myjson.com/bins/1aqhsc")
.then(res => res.json())
.then(handleResult)
.catch(handleError)
}
// ...
return (...)
}
This should do the trick.
Note that I've used your sample API endpoint that you've provided in the comments, so you will have to change the handleResult callback inside of the handleChange handler to make sure you're parsing out your new data appropriately.
If you'd like to see a quick example, I created a CodeSandbox with a button that can be clicked to fetch more data and display it in a list:
Demo
Let me know if you have any questions.

How to filter JSON data from API and setState in React

Upon clicking a filter button, the first filter returns the correct data, but the array in state is mutated (or destroyed?) when a second filter method is called. I'm using setState for each function and cannot figure out why the array's initial state is not restored.
This app is working when I import a local copy of the JSON data. The blocks of commented code do indeed show that the filter functions work with the local data, but not with the same JSON data returned by the fetch method from my server.
import React from "react";
import { Button, Row, Col } from "react-bootstrap";
import AutoList from './AutoList';
// import autoData from '../autoData.json';
const API = "https://MY_SERVER/autoData.json";
class GetAutos extends React.Component {
constructor(props) {
super(props);
this.state = {
autos: [],
};
}
fetchAutoData() {
fetch(API)
.then(autoData => autoData.json())
.then(autoData =>
this.setState({
autos: autoData,
isLoading: false,
})
)
.catch(error => this.setState({ error, isLoading: false }));
}
componentDidMount() {
this.fetchAutoData();
// this.setState(() => ({ autos: autoData }));
}
render() {
const { autos } = this.state;
let onResetArray = () => {
this.setState({ autoData: this.state.autos })
//console.log(autos)
}
let filterFord = () => {
const fordAutos = autos.filter( (auto) => auto.title.includes("Ford"));
// const fordAutos = autoData.filter( (auto) => auto.title.includes("Ford"));
this.setState({ autos: fordAutos });
}
let filterChevy = () => {
const chevyAutos = autos.filter( (auto) => auto.title.includes("Chevrolet"));
// const chevyAutos = autoData.filter( (auto) => auto.title.includes("Chevrolet"));
this.setState({ autos: chevyAutos });
}
let filterJeep = () => {
const jeepAutos = autos.filter( (auto) => auto.title.includes("Jeep"));
// const jeepAutos = autoData.filter( (auto) => auto.title.includes("Jeep"));
this.setState({ autos: jeepAutos });
}
return (
<div className="container">
<Row className="mb-4">
<Col>
<Button event-key="reset-autos" onClick={onResetArray}>All Cars</Button>
</Col>
<Col>
<Button event-key="ford-autos" onClick={filterFord}>Filter Ford</Button>
</Col>
<Col>
<Button event-key="chevy-autos" onClick={filterChevy}>Filter Chevy</Button>
</Col>
<Col>
<Button event-key="jeep-autos" onClick={filterJeep}>Filter Jeep</Button>
</Col>
</Row>
<AutoList autos={ this.state.autos } />
</div>
);
}
}
export default GetAutos;
I expect a subsequent button click to filter the data set in state. Instead, the click filters the array created by the .filter() method. My code works with a local import of my JSON data, but not with the fetched JSON.
When you set the state with the filtered result you are removing entries from the source. You should instead store the filtered result in a different key.
For instance something like this.
const jeepAutos = autos.filter( (auto) => auto.title.includes("Jeep"))
this.setState({
filteredAutos: jeepAutos
})
Then when rendering you would use it like so
<AutoList autos={ this.state.filteredAutos } />
Remember that initially filteredAutos would be empty, so to view all you would need to check for filtered items before passing
<AutoList autos={ filteredAutos.length > 0 ? filteredAutos : autos } />
Simple example to play with
JSON File:
[
{
"id": 1,
"name": "Maruti Ritz \\Hatchback Manual",
"details": "5 seater, petrol",
"picture": "./static/images/Ritz.jpg",
"overcharge": "PKR 130 per excess km",
"weekday": "PKR 120/hr",
"weekend": "PKR 157/hr",
"car_type": "Hatchback"
},
{
"id": 2,
"name": "Toyota Corolla XLI \\Hatchback Automatic",
"details": "4 seater, diesel",
"picture": "./static/images/XLI.jpg",
"overcharge": "PKR 179 per excess km",
"weekday": "PKR 190/hr",
"weekend": "PKR 287/hr",
"car_type": "SUV"
},
constructor(props) {
super(props);
this.state = {
lists: [],
}
}
componentDidMount() {
axios.get('http://localhost:3001/your directory/yourfilename.json')
.then(response => {
// console.log(response.data)
this.setState({
lists: response.data,
isLoading: false
})
})
}
render() {
const {lists} = this.state;
console.log(lists)
let filterHatchback = () => {
// alert("event clicked")
const hatchbackCar = lists.filter( (list) => list.car_type === 'Hatchback');
this.setState({ lists: hatchbackCar });
};
return(
<Card
className="carder"
body
style={{ fontSize: "14px", height: "" }}
>
<p>Car Type</p>
<Button outline color="secondary"
onClick={filterHatchback}
>
Hatchback
</Button> and so on
I think you would like to display autos, and keep the unfiltered API results in autoData. So:
create a state for both
this.state = {
autos: [],
audoData: [],
};
Store the API result in autoData:
this.setState({
autoData,
isLoading: false,
})
Filter the autoData instead of autos:
const { autoData } = this.state;
const fordAutos = autoData.filter( (auto) => auto.title.includes("Ford"));

React Native display FlatList with Firebase data

Trying to get FlatList to display data from Firebase.
Setup is correct and I can see the date in my console, but don't know how to visualise it.
I'd like to see 'recipeOne' 'recipeTwo' 'recipeThree' in the list.
I am sure I am missing something basic.
Here is the code
...
import {DataConfig} from '../data/DataConfig';
const firebaseApp = firebase.initializeApp(DataConfig);
globalTexts = require('../styles/Texts.js');
globalColors = require('../styles/Colors.js');
export default class ListSort extends Component {
constructor(props) {
super(props);
this.dataItem = firebaseApp.database().ref('recipes');
this.state = {
item: []
}
};
componentWillMount() {
this._createItemList();
};
_createItemList = (dataItem) => {
this.dataItem.on('value', (snapshot) => {
var itemList = [];
snapshot.forEach((doc) => {
itemList.push({
key:doc.key,
itemType: doc.toJSON()
});
this.setState({item: itemList});
console.log(this.state.item);
})
})
};
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.item}
renderItem={({item, index}) => (
<View style={styles.cell}>
<Text style={globalText.btnFlatPrimary}>{item.recipes}</Text>
</View>
)}
/>
</View>
)
}
}
and here is the data. The rules in Firebase are setup as read:true only.
{
"recipes": {
"recipeOne": {...
"recipeTwo": {...
"recipeThree": {...
}
}

Categories