React Native displaying nested array items - reactjs

The following code is for a single post, axios fetches the current post ID and stores the result in post array. Keep in mind, only ONE post is stored since this is fetching a single post. The ID and date display properly but if I try to display nested items like rendered content it doesn't work.
Here is what the API json result looks like:
constructor(props) {
super(props);
this.state = {
post: []
};
}
getPost() {
axios
.get('single_post_api')
.then(response => {
this.setState({
post: response.data
});
});
}
componentDidMount() {
this.getPost();
}
render() {
const data = this.state.post;
return (
<View>
<Text>{data.date}</Text>
<Text>{data.slug}</Text>
///Neither of these work///
<Text>{data.content.rendered}</Text>
<Text>{data.content[0]}</Text>
////////////////////////////
</View>
);
}

Try this.
render() {
// If we run into a null, just render it as if we had an empty array.
const posts = (this.state ?? this.state.posts) ? this.state.posts : [];
return (
<View>
(posts.map((post, index) => (
<View key={index}>
<Text>{post.date}</Text>
<Text>{post.slug}</Text>
<Text>{post.content ? post.content.rendered : ""}</Text>
</View>
));
</View>
);
}

Related

onClick like button likes all objects in array React JS

I'm super new to web development but I'm trying to implement a like button to an API array of the mars rover images in react js. The problem I'm having is when I click the like button for one of my images all of the like buttons click. I've tried using id's, classNames, and keys for my button but nothing seems to be working. Any help would be appreciated :)
class Rover extends React.Component {
state = {
loading: true,
images: [],
};
async componentDidMount() {
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key='
const response = await fetch(url);
const data= await response.json();
this.setState({ images: data.photos, loading: false });
console.log(this.state);
}
render() {
if (this.state.loading) {
return <div>loading...</div>;
}
if (!this.state.images) {
return <div>didn't find image</div>
}
return (
<IconContext.Provider value={{ color: '#a9b3c1', size: 64 }}>
<RoverSection>
<RoverWrapper>
<RoverHeading>Mars Rover</RoverHeading>
<div>
{this.state.images.map((image, idx) => (
<div className={`some-image-${idx}`} key={`some-image${idx}`}>
<RoverContainer>
<RoverCard to='/'>
<RoverCardInfo>
<RoverCardID>Photo ID: {image.id}</RoverCardID>
<RoverCardFeatures>
<Img src={image.img_src} />
<RoverCardFeature>{image.rover.name} Rover</RoverCardFeature>
<RoverCardFeature>{image.camera.full_name}</RoverCardFeature>
<RoverCardFeature>{image.earth_date}</RoverCardFeature>
</RoverCardFeatures>
<Button primary id={image.id} className={`some-button-${idx}`} onClick={() => {
this.setState({
isLiked: !this.state.isLiked
})
}}>{this.state.isLiked ? <FaHeart/> : <FaRegHeart/>}</Button>
</RoverCardInfo>
</RoverCard>
</RoverContainer>
</div>
))}
</div>
</RoverWrapper>
</RoverSection>
</IconContext.Provider>
);
}
}
export default Rover
You are mapping through all of the images (you have an array of images); However, you do not have an array for your likes you just have a single boolean value. You need to create an array for your likes of equal size to your image array and then use the same index (you can get the current index in your map (you already did an named it idx))
Your initial like state should be similar to:
async componentDidMount() {
const url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&page=2&api_key='
const response = await fetch(url);
const data= await response.json();
this.setState({ images: data.photos, isLiked:new Array(data.photos.length).fill(false), loading: false });
console.log(this.state);
}
and then in you component
this.setState((current)=>{
const newState={...current};
const isLiked = newState.isLiked;
isLiked[idx] = !isLiked[idx];
return {
...newState,
isLiked:[...isLiked]
}
})
and
{this.state.isLiked[idx] ? <FaHeart/> : <FaRegHeart/>}
lets say this is your image array:
this.state.images = [{id : 0, img : "img1"} ,{id : 1 , img : "img2"} ,{id : 2, img : "img3"} ]
now say you want to like img1 , when you click like on img1, dont you think that you need to only save img1 as liked image. you can do it by modifying the images array object. if you dont want to change data in your original image object,you can keep another array/object map with id and their like status ( you can keep all or you can only push the liked ones.
now when you click on any like button on image , call the function ( also bind it in constructor)
onClick = {(e)=> {this.handleLikeClick(image)}}
and in function handleLikeClick
handleLikeClick(image){
let copyImages = [...this.state.images]
this.state.images.forEach((img) => {
if(img.id === image.id) {
img.liked= !img.liked;
}
});
this.setState({
images :copyImages
})
}
now whenever you are in render , instead of just checking isLIked, you can check the liked property in image object.

setState not returned from render when using Axios

I'm using axios to get data from an endpoint. I'm trying to store this data inside the state of my React component, but I keep getting this error:
Error: Results(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I've struggled with many approaches: arrow functions etc., but without luck.
export default class Map extends Component {
constructor() {
super();
this.state = {
fillColor: {},
selectedCounty: "",
dbResponse: null,
};
}
getCounty(e) {
axios.get("/getWeatherData?county=" + e.target.id)
.then((response) => {
this.setState(prevState => {
let fillColor = {...prevState.fillColor};
fillColor[prevState.selectedCounty] = '#81AC8B';
fillColor[e.target.id] = '#425957';
const selectedCounty = e.target.id;
const dbResponse = response.data;
return { dbResponse, selectedCounty, fillColor };
})
}).catch((error) => {
console.log('Could not connect to the backend');
console.log(error)
});
}
render() {
return (
<div id="map">
<svg>big svg file</svg>
{this.state.selectedCounty ? <Results/> : null}
</div>
)
}
I need to set the state using prevState in order to update the fillColor dictionary.
Should this be expected? Is there a workaround?

How to prevent the component from being repeated using array map() in react-native

I'm implementing a calendar in react native with expo, the problem is that when I want to bring a date from the firebase database to paint it in calendar, it repeats several times, here a picture
View picture
The code is this:
constructor(props) {
super(props);
this.state = {
selected: "",
usuarios: [],
};
}
componentDidMount() {
firebase
.database()
.ref("DatosCli/")
.on("child_added", (data) => {
var datos = data.val();
var usuariosTemp = this.state.usuarios;
datos.key = data.key;
usuariosTemp.push(datos);
this.setState({ usuarios: usuariosTemp });
});
}
cargarDatos = async () => {
var userTemp = new Array();
var data = await firebase.database().ref("/DatosCli").once("value");
data.forEach((child) => {
var user = child.val();
user.key = child.key;
userTemp.push(user);
});
this.setState({ usuarios: userTemp });
};
render() {
return (
<View style={styles.container}>
{this.state.usuarios.map((usuarioTemp) => (
<CalendarList
markedDates={{
[usuarioTemp.date]: {
selected: true,
disableTouchEvent: true,
selectedColor: "orange",
selectedTextColor: "red",
},
}}
/>
))}
</View>
);
}
}
I know that having the map() outside of CalendarList is the reason why it repeats itself several times, how would this be solved then?
The calendar library that i use is: https://github.com/wix/react-native-calendars
You want to have a list of marked dates and not a list of CalendarList components. Right now you are looping through the dates and create a new CalendarList for each of your dates. Instead you want to create an object of dates. Map is part of the array protoype. The prop markedDate seems to require an object though. For that, we need to refactor your code a bit and replace map by forEach for example to construct the object.
Let's split this problem up into two steps. First we create the object of markedDates by using the forEach function and after that we pass it as a prop to CalendarList.
render() {
const markedDates = {};
this.state.usuarios.forEach((usuarioTemp) => {
markedDates[usuarioTemp.date] = {
selected: true,
disableTouchEvent: true,
selectedColor: "orange",
selectedTextColor: "red",
};
};
return (
<View style={styles.container}>
<CalendarList markedDates={markedDates} />
</View>
);
}

React-Name Array from Json with variable as key

I'm not 100% sure how to ask this question, and I'm fairly new to react-native. I have a list of categories with a count in them. In PHP I may do something like this:
$purchases['kayaks'] = 0;
$purchases['kayaks']++;
so it increments every time a kayak is sold for this example. or more specifically something like this:
$purchases[$categoryName]++;
I need to take the name of the category based on the user pressing which category they want and add a count to it, then store it in json format in AsyncStorage.
So I know I can do this:
{
"categories": [
{
"kayaks":"0"
}
]
}
And if I import that into "products" I can do products.categories.kayaks to retrieve "0" (or whatever the purchase count is), but I need kayaks to be able to be a variable based on a selection the user makes. so something more like products.categories[categoryName], or however the more optimal way to do that would be in react-native. What is the way (or if there is a more ideal way other than this) to accomplish having different category counts like this?
I hope that makes sense. Thanks in advance!
Here is a basic example written in react-native that uses AsyncStorage to read/write data and manipulates an object. Check out https://facebook.github.io/react-native/docs/asyncstorage for more info about AsyncStorage.
import React from 'react';
import { ActivityIndicator, AsyncStorage, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
input: {
minWidth: 100,
backgroundColor: 'red'
}
});
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: undefined,
categoryName: 'kayak' // default input value
};
}
componentDidMount() {
AsyncStorage.getItem('data').then(dataAsString => {
// if the data exists lets convert it into an Object we can easily edit
// else default to an empty object
this.setState({ data: dataAsString !== null ? JSON.parse(dataAsString) : {} });
});
}
handleIncrement = () => {
const { data: prevData, categoryName } = this.state;
const prevValue = prevData[categoryName];
// if the category doesn't exist the data lets default to 0
const newValue = prevValue !== undefined ? prevValue + 1 : 0;
this.setState({
data: {
...prevData, // keep all the previous data
[categoryName]: newValue // but set the selected category to the new value
}
});
};
handleSave = () => {
const { data } = this.state;
const dataAsString = JSON.stringify(data); // convert data into a string so AsyncStorage can save it properly
AsyncStorage.setItem('data', dataAsString); // save the data string
};
handleOnChangeText = categoryName => this.setState({ categoryName });
render() {
const { data, categoryName } = this.state;
return (
<View style={styles.container}>
{data === undefined ? (
<ActivityIndicator /> // While data is undefined (AsyncStorage is still doing getItem('data)) show a loading indicator
) : (
<React.Fragment>
<Text>{JSON.stringify(data, null, 2)}</Text>
<TextInput style={styles.input} value={categoryName} onChangeText={this.handleOnChangeText} />
<TouchableOpacity onPress={this.handleIncrement}>
<Text>Add/Increment</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleSave}>
<Text>Save</Text>
</TouchableOpacity>
</React.Fragment>
)}
</View>
);
}
}
export default App;
Note : Its best to use an object to store your data (as Cuong Tran Duc mentions) as you are indexing with category.
Hope this example was helpful.
assuse that your data get from json look like this
const data = {
"categories": [
{
"kayaks":"0"
},
{
"foo":"1"
},
{
"bar":"1"
}
]
}
const categories = data["categories"].reduce((acc, category) => (acc[Object.keys(category)[0]] = Object.values(category)[0], acc), {});
console.log(categories);
you could convert it into an object then access data by categories[categoryName] like you want
change Object.values(category)[0] to +Object.values(category)[0]
if you want to convert string to number

React native data not rendered after setstate

So i have been working with firebase as a backend in my react native application, i have tried to fetch data this way but i have nothing rendered, i have the activity indicator that went off, but i get that the data array is empty in the application screen, and when i do a console.log, i can see the data in the console, but nothing shows off in the application screen, please help me it's been days that i'm struggling.
export default class Leaderboard extends React.Component{
constructor(props){
super(props)
this.state = {
loading : true,
data : []
}
}
componentDidMount(){
firebase.firestore().collection('rankings').get()
.then(res => {
let rankArray = []
res.forEach(document => {
rankArray.push(document.data())
})
return rankArray;
}).then(res =>{
let data = []
res.forEach(item =>{
firebase.firestore().doc(item.idUser.path)
.get()
.then(doc =>{
let dataItem = {}
dataItem.id = doc.ref.path
dataItem.name = doc.data().fullname
dataItem.points = doc.data().points
dataItem.lc = 'Oran'
data.push(dataItem)
dataItem = {}
})
})
return data;
}).then(res =>this.setState({
loading : false,
data : res
}) ).catch(err => console.log(err))
}
render(){
if(this.state.loading){
return(
<View style = {styles.container}>
<ActivityIndicator size= 'large'></ActivityIndicator>
</View>
)
}else{
console.log(this.state.data)
return(
<View>
<Text>{this.state.data.length}</Text>
<FlatList
data={this.state.data}
renderItem={({item}) => <Text>{item.fullname}</Text>}
/>
</View>
)
}
}
}
The reason for this not working as expected is that you're trying to perform an asynchronous function call, per iteration of your res array inside of your forEach() callback:
// This is asynchronous
firebase.firestore().doc(item.idUser.path).get().then(doc =>{ ... })
Consider revising your code to use the Promise.all() method instead. This will ensure that each asynchronous for individual documents per-item in res array is completed, before setState() in the susequent .then() handler is invoked:
.then(res => {
let rankArray = []
res.forEach(document => {
rankArray.push(document.data())
})
return rankArray;
})
.then(res => {
// Use promise all to resolve each aync request, per item in the
// res array
return Promise.all(res.map(item => {
// Return promise from .get().then(..) for this item of res array.
return firebase.firestore()
.doc(item.idUser.path)
.get()
.then(doc => {
let dataItem = {}
dataItem.id = doc.ref.path
dataItem.name = doc.data().fullname
dataItem.points = doc.data().points
dataItem.lc = 'Oran'
// Return resolve dataItem to array that is relayed to next .then()
// handler (ie where you call this.setState())
return dataItem
})
}));
})
.then(res =>this.setState({
loading : false,
data : res
}))

Resources