This is probably a super simple question but I seem to be finding various answers to. Essentially I have an app that makes an axios call to get a list of items via JSON API.
I need to set the initial state on the component load my state looks like this:
this.state = {
sensors: [],
filterName: "",
sortBy: "id",
};
my component loading code looks like so:
componentDidMount =() => {
axios
.get("http://localhost:3000/api/v1/devices.json")
.then((response) => {
// handle success
console.log(response);
this.setState({ sensors: [...response.data]});
})
.catch((error) => {
console.log(error);
})
}
This works...but I feel like this isn't the right way. Isn't it not supposed to change the initial array because I remember reading that we shouldn't ever be changing the initial state only returning a new version of it?
Is there a better/safer way?
(Also what about updating a single one/adding an item to array of objects? Is there a "preferred way")
If you are looking to add elements to the sensors array without directly mutating the state, this is what you can do.
const { sensors } = this.state;
this.setState({
sensors: [...sensors, response.data]
});
What this does is that we are using the spread syntax to destructure the array, and copy it to a new array with the response.data from the API.
Related
So I've started to learn ReactJS recently, and been struggling with handling a component's state.
While fetching an URL, I receive a JSON file and set the state of my component to have values based on this JSON file. I have no problem doing that if my state's key only receives one value, but I can't figure out how to loop through my setState so I can return multiple values to one of my state keys.
For example, if the JSON has an array of ingredients, I can only pass one ingredient to my state ingredients array, and not all of them.
In sum, I want to do something like this:
for(let i = 0; i < json.ingredients.length; i++) {
this.setState({
ingredients: [...this.state.ingredients, json.ingredients[i].name]
})
}
This is the code I used to create my component so far:
class SingleMeal extends Component {
state = {
meal: null,
diets: [],
ingredients: []
}
componentDidMount() {
const { id } = this.props.match.params;
const url = `https://api.spoonacular.com/recipes/${id}/information?apiKey='apiKey`;
fetch(url)
.then( response => response.json())
.then ( json => (
this.setState({
meal: json,
diets: json.diets[0], // ADD ALL DIETS TO THIS STATE OBJECT
ingredients: [json.extendedIngredients[0].name] // ADD ALL INGREDIENTS TO THIS STATE OBJECT
})
))
}
I appreciate your help!!
it you want to add all the items in json.diets, and select only the names from the json.extendedIngredients, try this:
this.setState({
meal: json,
diets: json.diets,
ingredients: json.extendedIngredients.map(ingredient=>ingredient.name)
})
Also, I hope that isn't your actual api key in the url
I am kinda new into react and web dev overall, I want to ask, where is the issue in my proble?
I created a database with firebase, posting into it went fine, but now I am trying to GET the date I posted before and store it Into a variable, so then I can iterate through the data and map different components for each data. I am using axios, here is the code:
function CreateBlog(props) {
const [fetchedData, setFetchedData] = useState();
useEffect(() => {
axios.get("https://diary-page-default-rtdb.firebaseio.com/diaryPages.json")
.then((response) => {
console.log(response.data);
setFetchedData(response.data);
console.log(fetchedData)
})
.catch(error => console.log("error occured:", error))
}, []);
so as I console.log the response.data I get the object with the data stored in the database, but when I setFetchData and console.log the fechedData I get undefined. Is there any simple way to store the data inside "fetchedData" as an array where every different object represents a part of the array so that later on I can map through the array and display the data in separate components?
You are storing the data correctly, but you are not able to console.log them straight away because the useState operates asynchronously.
If you want to console.log your fetchedData, have a useEffect listening to changes on that state (for demonstration purposes):
useEffect(() => {
console.log(fetchedData)
}, [fetchedData]);
A further suggestion I would give (not essential though), is to set your initial state as an empty array since that's the type of data you are storing:
const [fetchedData, setFetchedData] = useState([]);
From here, you can map through your data as such:
{fetchedData?.map((data, index) => <div key={index}>{data}</div>}
Just make sure data is not an object if you copy my example, or it will return you an error since you can't display objects inside jsx
I am using a fetch request to get some fx rates from an API. However, I only need to store the rate for a currency that's been previously selected by the user and saved as state (state.currency).
The example below works well for predefined values (eg. json.rates.GBP) but I can't find a way to link it with this.state.currency.
Here is my current code:
var fxRates;
class FxRateCheck extends Component {
constructor() {
super();
this.state = {
currency: "",
isLoading: true
};
}
handleSubmit = () => {
fetch("https://api.exchangeratesapi.io/latest?base=USD")
.then((response) => response.json())
.then((json) => {
fxRates = json.rates.GBP;
// I need to replace GBP with the value of this.state.currency
})
.catch((error) => console.error(error))
.finally(() => {
this.setState({
isLoading: false
});
});
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The user sets the currency base in their state, you then need to use the same value as the key when you access a property in a different object.
fxRates = json.rates[this.state.currency];
There are a few other things you need to change though. the global variable fxRates is generally a bad practice, if you need to use the result in other places in your component consider putting it on the component state.
You should also let the user choose what to compare with. ?base=USD is hardcoded currently. Maybe consider letting them change that in a future change :)
Heres an example of what I was describing :)
You can use dot and bracket notation to access properties in an object
const data = {
id: 1,
value: "one"
}
can be accessed like
data.value
or
data["value"]
or
var property = "value";
data[property]
So, in your case you can do it like
fxRates = json.rates[this.state.currency];
I originally coded this to get the first 10 albums from a rest api of albums.
constructor(props){
super(props)
this.state = { albums: [] }; // an array of objects
}
// set the first ten albums to state prop when fetched from fetch()
tenAlbums(jsonResponse) {
let tenAlbums = [];
// get just the first 10 albums from the response
for(let i = 0; i < 10; i++){
tenAlbums.push({ id: jsonResponse[i].id, title: jsonResponse[i].title })
}
this.setState({ albums: tenAlbums }); // Save as state property
}
This worked fine, until I realised I had to append to each object in the albums array, some properties of images and thumbnails from another api call. I was going to make another method like tenImages() and append them to this.state.albums. In order to do this, it looks like I'm going to have to individually inject the properties into the objects instead of my example above. I'm having trouble figuring out how to do this in React's setState. Some solutions say to make a copy of the state, make change and update it. I've tried for example
this.setState({ albums: { ...this.state.albums[i].title, title: jsonResponse[i].title} });
but this doesn't work. I guess it's something to do with the objects not being set in the first place. Any advice? Thanks.
use a map function in your function tenAlbums like this:
const albums = jsonResponse.map((item) => ({
id: item.id,
title: item.title
})
map function would not mutate your original object, instead, it returns a new object. then you can use the returned object(albums in your case) to set your react state.
furthermore, if you want to set other properties like thumbnails, images etc afer some other api calls, you can write another function and use react setState function like this:
this.setState(prevState => {
// you can set your other propeties here
const newAlbums = prevState.albums.map((album) => ({
...album,
thumbnail: 'some url',
image: 'some url'
}));
return newAlbums
})
I am trying to make a 'facebook kind of newsfeed' in React with Firebase Firestore. In the componentDidMount I first get the friendslist and per friend I will get their activities which I push to an empty array and sort() + reverse() the id's which are timestamps. This way the newest activity will be first in the array. Once ALL the items are pushed to the array, I want to set the state with the array. This is the code that I have:
import React, { Component } from 'react'
import { db, firebaseAuth } from '../../helpers/base'
import Activities from './Activities'
export default class ActivityList extends Component {
state = {
activityKeys: [],
}
componentDidMount(){
const uid = firebaseAuth().currentUser.uid
var activityKeys = []
db.doc(`users/${uid}/social/friends`).get().then( (doc) => {
//GET ALL THE FRIENDS
const friends = doc.data() //OBJECT OF {friendOneId: "friendOneId", friendTwoId: "friendTwoId"}
//LOOP THROUGH FRIENDS AND GET ACTIVITY
Object.keys(friends).forEach( friend => {
db.collection("activity").where("user", "==", friend).get().then((querySnapshot) => {
querySnapshot.forEach( (doc) => {
const activity = doc.id
activityKeys.push(activity)
activityKeys.sort().reverse()
})
})
})
})
console.log('activityKeys: ', activityKeys)
this.setState({ activityKeys })
}
render () {
return (
<div>
<h5>Activity List</h5>
<Activities activityKeys={this.state.activityKeys} />
</div>
)
}
}
The problem is that the array isn't set correctly or that it maybe is set before all the items are pushed. This is the log that I get:
It looks like it is loaded but it is empty between the brackets. If I console.log This.state.activity I get the same result. Can someone tell me how to fix this? And how can I setState once all the activities are pushed to the empty array?
You get a Promise when you make a call to API, but you set this.setState({ activityKeys }) before the call has completed. In other words, you must chain another .then() after the data has been received, in which you will call this.setState({ activityKeys }). What makes it a little difficult is that you're creating many Promises when iterating with forEach, and you need to wait for each of them to complete. You could save them all to list, and use Promise.all to wait for their completion and return it from the previous .then. Read more on the promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise