How can I save in state the response of an Api Call? - javascript

I'm trying to save in the state of my component the data an Api call retrieves, but the data have no time to come cause of the async function so when I check the state its value is an empty array. Here is the code.
async getValuesData() {
let id = "dataid";
let getValuesCall = urlCallToDatabase + "valuesById/" + id;
const response = await fetch(getValuesCall, { headers: {'Content-Type': 'application/json'}})
const res = await response.json()
this.setState = ({
values: res
})
console.log("Data Values: ", res);
console.log("Data Values from state: ", this.state.values);
}
I'm calling the function in the contructor.

First, you've to call the function inside ComponentDidMount lifecycle if you want the component to appear as soon as the data is mounted
Second,I'd do the following:
I declare, either in the same file or in a different one, for example, x.business.js the function that calls the backend and returns the result:
const getValuesData = async () => {
const id = "dataid";
const getValuesCall = urlCallToDatabase + "valuesById/" + id;
const response = await fetch(getValuesCall, { headers: {'Content-Type': 'application/json'}})
return await response.json();
}
Then in the component, if I want it to be called as soon as it is assembled, this is when I make the assignment to its state (and if you want to check that it has been set, you use the callback that setState has after the assignment):
class SampleComponent extends React.Component {
state = {
values: {}
}
componentDidMount() {
getValuesData().then(response =>
this.setState(prevState => ({
...prevState,
values: response
}), () => {
console.log(this.state.values);
}))
}
...
}
As the community says, it's all in the documentation:
componentDidMount: if you need to load data from a remote endpoint and update your state
setState(): to update state of the component
Here's an example of how it would work

You're calling setState incorrectly. It should be:
this.setState({ values: res });
The console.log() calls, even if you adjust the above, won't show accurately what you expect. If that's what you want try this too:
this.setState({ values, res },
() => {
console.log("Data Values: ", res);
console.log("Data Values from state: ", this.state.values);
}
);
I.e., make the console.log()'s the second argument to setState which will then accurately show the value of state.

You should do this :
this.setState({values: res})
this.setState should be a function : https://reactjs.org/docs/react-component.html#setstate

You have to use setState instead of this.state = {}
this.setState({values: res})

Use like this
this.setState({values: res})
use this.setState() to schedule updates to the component local state
Do Not Modify State Directly
// Wrong
this.state.values = res
Instead, use setState()
// Correct
this.setState({values: res});

Related

Struggling With JS Promises in React

I'm struggling with the concept of Promise in JavaScript. I'm writing a React app that makes a GET call out to a separate API service in Java, and I want to store its state in a useState() hook. So here's my fetch code:
const ratingsUrl = "%URL%";
const base64 = require("base-64");
const login = "login";
const password = "password";
function fetchRatings() {
return fetch(ratingsUrl, {
method: "GET",
headers: new Headers({
Authorization: "Basic " + base64.encode(login + ":" + password),
}),
})
.then((response) => response.json())
.catch(handleError);
}
And now I'm trying to store its state in a hook in my page component:
function DisplayPage(){
const [ratings, setRatings] = useState(fetchRatings());
.
.
.
}
Now, the data returns but it's in a Promise, hence causing errors down the line:
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Array(20)
What I need to do is to initialise the data in a hook and return it in a Table so I can map through it. However, whenever I try to do something like
ratings.map()
I get a TypeError in the console saying ratings.Map is not a function.
I'm aware that the fetch library returns data asynchronously, but all I really want is for the PromiseResult to be stored in a useState() hook so I can perform operations on it further down the line.
async methods return promises. If you directly set the result of a promise in your setRatings state variable, you will get a promise.
Typically this would be rewritten something like this:
function DisplayPage(){
const [ratings, setRatings] = useState(null);
useEffect(() => {
fetchRatings
.then(result => setRatings(result))
.catch(err => console.error(err));
}, []);
if (ratings === null) return <div>loading...</div>;
/* .. do your thing .. */
}
How About this ,
const [ratings, setRatings] = useState();
useEffect(()=>{
fetch(ratingsUrl, {
method: "GET",
headers: new Headers({
Authorization: "Basic " + base64.encode(login + ":" + password),
})}).then((response) => {let res = response.json();
setRatings(res)
})
.catch(handleError);
},[])
I would advice using the useEffect hook to set initial state.(similar to componentDidMount)
So if the response you expect is for example an array.
const [ratings, setRatings] = useState([]);
Then in the useEffect hook, update state when you get a response from your fetch request.
That way you can prevent errors if you for example map over ratings in your DOM somewhere before the request is finished.
useEffect(){
fetch(ratingsUrl, {
method: "GET",
headers: new Headers({
Authorization: "Basic " + base64.encode(login + ":" + password),
}),
})
.then((response) => {
response.json()
})
.then(res => setRatings(res))
.catch(handleError);
Because the fetch runs asynchronously, you're not going to be able to initialize your state using the immediate result of invoking fetchRatings.
There are, fortunately, a couple of fairly straightforward ways to handle this. You can initialize your state with an empty value and then update it once fetchResults resolves:
function DisplayPage() {
// initially ratings will be undefined
const [ratings, setRatings] = useState();
// update state when fetchResults comes back
fetchResults().then(response => setRatings(response));
// ...
The above example omits this in the interest of readability, but you'd generally do this via useEffect so it runs when your component mounts or when relevant inputs (usually props, known as dependencies for the effect) change:
function DisplayPage() {
const [ratings, setRatings] = useState();
useEffect(() => {
fetchResults().then(response => setRatings(response));
}, []) // empty dependencies array will cause the effect to run only once, on mount
// ...

State of array is not updated in ComponentDidMount

I have a weird situation, where I have an array as a state:
this.state = { ActivityItem: []} and I am pushing values to it from library that calls an API like this:
getDataFromKit(dateFrom) {
Kit.getSamples(stepCountSample, (err, results) => { //api call
if (err) {
return;
}
const newData = results.map(item => { return { ...item, name: 'itemAmount' } });
this.setState({ d: [...this.state.ActivityItem, ...newData] })
})
Then, I call this method from ComponentDidMount() for array to be loaded
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
console.log("values are", this.state.ActivityItem)
}
Now, the weirdest part: somehow the array is empty in ComponentDidMount, but when I display elements of Array in return of render() function it displays all the values that were added correctly. How is that possible and how I might fix that?
setState is asynchronous in nature. Therefore, logging the state just immediately after setting it can give this behaviour but if set properly, it will display the required content which is happening in your case. Also componentDidMount is called only once in the beginning so you can check for logs in componentDidUpdate method.
State updates are async in nature. If you want to print the state soon after setting your state in class component, then pass a function to the 2nd argument of setState.
Like this
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
// remove console.log
}
...
getDataFromKit(dateFrom) {
...
this.setState({ ActivityItem: [...this.state.ActivityItem, ...newData] }), () => {
console.log("values are", this.state.ActivityItem) //<----
}
})
...
}
use prevstate while updating the state value. React setState is an asynchronous update and does batch updating. Using prevState makes sure that the state value is updated before calculating new state value.
getDataFromKit(dateFrom) {
let stepCountSample = {
startDate: dateFrom.toISOString(),
type: "Type1"
};
Kit.getSamples(stepCountSample, (err, results) => {
//api call
if (err) {
return;
}
const newData = results.map(item => {
return { ...item, name: "itemAmount" };
});
this.setState(prevState => {
ActivityItem: [...prevState.ActivityItem, ...newData];
});
});
}
DOCUMENTATION would help understand the concept
Also, console.log would directly not give the updated state, since state updates are batched. You can use a callback method to setState function. The callback will run only after successfull updation of state value

How to change fetch call and rerender screen?

I have a React Native App that is communicating with a PostgresDB threw a ExpressJS REST API.
I have a fetchcall that gives back some data that I want to display inside my app. It calls the route with two params. One being the userid that I stored inside AsyncStorage and the other one being the selected Date that is stored in my state.
My problem is that the fetch url should be updated when the date state changes and then give back the data for the given day. Currently it is not rerendering the screen but the date changes. Is there a specific way how to tell my fetch query that it should rerender?
For example: `http://myip/api/hours/daydata/${realDate}/${userid}`
is my fetchurl and realDate is this.state.date and userid is the AsyncStorage stored userid.
`http://myip/api/hours/daydata/2019-11-06/138` this url gives me back the data I need for the given day and for the user.
On a Button click my date state changes for example to 2019-11-07.
`http://myip/api/hours/daydata/2019-11-07/138` this would be the new url that should be fetched next and I expect to rerender the screen to see the changes. But it doesnt rerender!
Here you have some code:
async fetchData() {
let realDate = this.state.date;
await getData("userid") // asyncStorage gets userid correctly
.then(data => data)
.then(value => this.setState({ userid: value }))
.catch(err => console.log("AsyncS_Error: " + err));
const userid = this.state.userid;
console.log("cpmDM id_ " + userid);
await fetch(
`http://myip/api/hours/daydata/${realDate}/${userid}`
)
.then(console.log("realD8_: " + realDate))
.then(res => res.json())
// .then(res => console.log(res))
.then(res => {
console.log(res[0].remark);
return this.setState({
remark: res[0].remark
});
})
.catch(err => console.log(err));
}
componentDidMount() {
this.fetchData();
}
I expect to get a rerender because I am calling setState on remark.
In the first render the remark from the first selected date gets diplayed but as I change the day the remark stays and doesnt update.
Ideas:
Is there a way to log the current fetchurl? Do I need something like componentWillUpdate maybe?
EDIT: This is the way that my state gets updated:
dateForwardHandler = () => {
console.log("Current Date:", this.state.date);
const newDate = Moment(this.state.date).add(1, "d").format("YYYY-MM-DD");
this.setState({ date: newDate.toString() });
console.log("Current Date:", this.state.date);
};
EDIT 2.0:
calling fetchData() inside the dateBackHandler and dateForwardHandler is actually showing me the different remarks where I need them but somehow they are 1 day off. If I go back in date for ex to the 25.10 I get the remark from the 26.10 and when I then go to 24.10 I get the one from 25.10
Seems like the component does not get updated on the first press but on the second so its one day off.
EDITS MERGED: Soo thanks for all the help that you guys gave me.
I want to tell you how the app is behaving currently.
dateForwardHandler = () => {
console.log("Current Date:", this.state.date);
const newDate = Moment(this.state.date).add(1, "d").format("YYYY-MM-DD");
this.setState({ date: newDate.toString() });
console.log("Current Date:", this.state.date);
this.fetchData(); // THIS CALL IS NEW
};
By calling this.fetchData(); after the state changes and on Button Press I get some different remarks shown but a Problem I have is that it is always one day off. So somehow the frontend and the fetchcall are not updating at the same time.
But I think it is right to first set the state.date to the new date and then to call fetchData...
Thanks for all the help so far ;)
You don't have to return this.setState, so instead of
return this.setState({
remark: res[0].remark
});
use
this.setState({
remark: res[0].remark
});
Why do you need the current date in your state and update it everytime when needed? cannot you just do:
let currentDate = Moment().add(1, "d").format("YYYY-MM-DD").toString()
This way you don't force a re-render everytime you update the date, while still fetching with the date in the moment you are fetching
First of all I would not put two awaits into one single async function. It could be the your userId will not be retrieved in time for your fetch method.
Regarding the rerendering, try to first return the result of fetchData (res.json) and then setState outside of the async function.
EDIT:
As you mentioned that you wanted to do on button click, it could look somewhat like this:
fetchData = (url, method) => {
let error;
return fetch(url, {
method: method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
}).then((response) => {
if (response.ok) {
return response.json().then(response => ({ response })).catch((error) => { console.log("JSON cannot be parsed") });
}
error = JSON.stringify(response);
return ({ error });
}).catch((error) => {
console.log("Promise rejected");
});
}
onButtonPress = async () => {
const userid = this.state.userid;
const realDate = this.state.realDate;
const { response, error } = await this.fetchData('http://myip/api/hours/daydata/${realDate}/${userid}', 'GET');
if (response) {
console.log(response);
this.setState({
remark: response[0].remark
});
} else {
console.error("Error: " + error);
}
}

This.state property is considered "undefined"

I'm fetching data from my backend to my frontend. After I invoke
let data = response.json(), I then invoke const bartData = Object.entries(data). So, I'm creating an array that holds the key/value pairs of my original object. I then set the state of my component this.setState({allStations: bartData}), where the property allStations: []. This is where the problem comes up- I want visual confirmation that I'm geting the right data and manipulate it the way I want to so I invoke console.log(this.state.allStations[0]) and it gives me the correct contents but when I go further console.log(this.state.allStations[0][0], I get an error that states
this.state.allStations[0] is undefined
Why?
Also, I get that I'm putting an array inside of an array, which is why I was surprised that console.log(this.state.allStations[0])gave me the contents of the original array. Picture of console.log(this.state.allStations) this.state.allStations
constructor(){
super(props);
this.state = {
allStations: []
}
}
async getAllStations(){
try{
const response = await fetch(`/base-station-routes`);
let data = await response.json();
// console.log(response);
// let test = JSON.parse(bartData);
// console.log(test)
const bartData = Object.entries(data);
// console.log(bartData[0][0]) works
this.setState({
allStations: bartData
})
}catch(e){
console.log(`Error: ${e}`)
}
}
render(){
console.log(this.state.allStations[0]);
return( more stuff )
}
[1]: https://i.stack.imgur.com/hQFeo.png
In render function before console.log(this.state.allStations[0]) you should check the state value.
Render function executes before fetching data from backend, my suggestion to do this
if(this.state.allStations) && console.log(this.state.allStations[0])
Do a conditional render to prevent it from showing the array before response has been sent.
Add something like:
constructor(){
super(props);
this.state = {
isLoading: true,
allStations: []
}
}
You need to use React Lifecycles and stick Fetch inside:
componentWillMount(){
fetch('/base-station-routes') // Already Async
.then(res => res.json()) // Convert response to JSON
.then(res => this.setState({ isLoading: false, allStations: res})) // you can call it bartData, but I like to stick with res
.catch(err => { //.catch() handles errors
console.error(err)
})
}
and then in your Render:
render(){
return(
<div>
{this.state.isLoading ? <span>Still Loading</span> : // do a map here over your data}
</div>
)
}
This prevents doing anything with the data before your response is there.

setState not working inside AsyncStorage in react native?

setState not working inside AsyncStorage in React Native.
constructor(props) {
super(props);
this.state = {userId: ''};
}
componentDidMount() {
AsyncStorage.getItem('USER_ID', (err, result) => {
if (!err && result != null) {
this.setState({
userId: result
});
}
else {
this.setState({
userId: null
});
}
});
alert(this.state.userId);
let userId = this.state.userId;
fetch('http://localhost/JsonApi/myprofile.php', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: userId,
}),
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({userDetails: responseJson});
})
.catch((error) => {
console.error(error);
});
}
Setting the userId value using setState and alert returns no value at all. Tried other solutions from Stackoverflow but not as per my expectation.
Note: Code updated. After getting userId from AsyncStorage, it will be passed to fetch. Here, userId value is missing.
2 ways to do this. One is Simple but other is correct way according to react recommendation
One is here- pass value to state directly.
.then((responseJson) => {
// this.setState({userDetails: responseJson});
this.state.userDetails=responseJson;
this.setState({}); //for update render
})
Second Way is here
in the render function Check state Value like this .if UserDetails state is null it will be not give you error whenever userDetails state get data render execute again and provide perfect result.
render() {
return (
<div>
{this.state.userDetails ?
this.state.userDetails.map((data, index) =>
<tr key={index}>
<td>{data.userName}</td>
<td>{data.userEmail}</td>
</tr>
)
: null
}
</div>)}
Let me know gain. if facing issue
Try to alert after updating state. You will get callback once state is updated.
this.setState({
userId: result
},function(){
console.log("userId in async = ", this.state.userId);
alert(this.state.userId);
});
I don't know why you wrote so much code.
First way
AsyncStorage.getItem("USER_ID").then((value) => {
console.log("userId in async = " + value);
this.setState({
userId: value
});
});
You don't need to check error & result both because if that is null, you are setting userId null in state. so you can directly set value to state userId.
Also set a log to see what is output of your async storage userId.
Please also verify that you are setting value in "USER_ID" somewhere.
Second way
There can different ways also like using async method.
const getUserId = async () => {
try {
const userId = await AsyncStorage.getItem('USER_ID') || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return userId;
}
and you can use
this.setState ({
userId : getUserId()
});
I don't like this way because I need to create another method with async & await keywords.
I use the first way so.
Update
Do your work related to userId inside getIten();, because you alert userId immediately after calling AsyncStorage. And AsyncStorage returns value after you call alert.
AsyncStorage.getItem("USER_ID").then((value) => {
console.log("userId in async = " + value);
this.setState({
userId: value
});
alert(this.state.userId); // move this line here
});
// removed from here

Categories