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
Related
I created a Vue.js app with a central store with vuex and some basic API calls with axios to fetch data into the store.
I create the following store action:
loadConstituencyByAreaCodeAndParliament({commit}, {parliament_id, area_code}) {
axios.get('/cc-api/area-code/' + parliament_id + '/' + area_code)
.then((response) => {
commit('SET_CONSTITUENCY', response.data);
})
.catch(function(error){
commit('SET_CONSTITUENCY', null);
}
)
}
In a single component file I defined a form where the user enters the area code. This form then calls this action to get the constituency fitting the area code:
export default {
name: 'AreaCodeForm',
components: {
PostalCodeInput
},
props: ['parliament_id'],
data: () => ({
postalCode: ''
}),
methods: {
search_area_code(submitEvent) {
let area_code = submitEvent.target.elements.area_code.value;
let payload = {
parliament_id: this.parliament_id,
area_code
}
this.$store.dispatch('loadConstituencyByAreaCodeAndParliament', payload).
then(() => {
let constituency = this.$store.getters.getConstituency();
// do some things with the data received from the API
// but everything depending on constituency does not work the first time.
// Data received from the API is here available only from the second time on
// wehen this code run.
})
}
}
}
As I found out the $store.dispatch method returns a promise but still the constituency variable receives not the data fetched with the loadConstituencyByAreaCodeAndParliament action but remains empty. I thought when I use the promise.then method the data should be already stored in the store but it is not. When I enter the area code a second time everything works well.
As mentioned by blex in a comment returning the axios call is the answer:
loadConstituencyByAreaCodeAndParliament({commit}, {parliament_id, area_code}) {
return axios.get('/cc-api/area-code/' + parliament_id + '/' + area_code)
.then((response) => {
commit('SET_CONSTITUENCY', response.data);
})
.catch(function(error){
commit('SET_CONSTITUENCY', null);
}
)
}
Always remember the return statement when dealing with asyncronous tasks.
You have two options to refactorize your code, keeping promise or async/await.
Option 1: async/await
async loadConstituencyByAreaCodeAndParliament({ commit }, { parliament_id, area_code }) {
try {
const { data } = await axios('/cc-api/area-code/' + parliament_id + '/' + area_code)
commit('SET_CONSTITUENCY', data)
return data
} catch (error) {
commit('SET_CONSTITUENCY', null)
return error
}
}
Notes:
return statement in both blocks of try/catch.
.get in axios is optional, since default is get method.
You can use object Destructuring assignment with { data } by default with axios. If I'm not wrong the default good http responses retrieve data.
Even a more sophisticated way could be const { data: constituencyResponse } = await... then you work with constituencyResponse and you probably save 2 or 3 lines of code each time.
Option 2: Promise
First Path: Make everything in the store.
// actions
loadConstituencyByAreaCodeAndParliament({ commit, dispatch }, { parliament_id, area_code }) {
axios('/cc-api/area-code/' + parliament_id + '/' + area_code)
.then(({data}) => {
commit('SET_CONSTITUENCY', data)
dispatch('actionTwo', constituency)
})
.catch((error) => {
console.log("error", error)
commit('SET_CONSTITUENCY', null)
})
}
actionTwo({commit}, constituency) {
console.log("actionTwo", constituency)
// do something
commit('COMMIT', 'Final value')
}
// Component
// You handle it with a computed property either referencing a getter or the store state.
{
computed: {
getConstituency(){
return this.$store.state.constituency
},
getSomeOtherConstituency(){
return this.$store.state.constituency.something / 3
}
},
// Optionally if you want to listen and react to changes use a `watcher`.
watch: {
// Gets excecuted each time getConstituency updates.
// ! Must have the same name.
getConstituency(update) {
// Do something, `update` is the new value.
}
}
}
Second Path: Handle data inside the component, then update the store.
Vue component.
methods: {
search_area_code(submitEvent) {
const parliament_id = this.parliament_id
const area_code = submitEvent.target.elements.area_code.value
axios('/cc-api/area-code/' + parliament_id + '/' + area_code)
.then(({data: constituency}) => {
this.$store.commit('SET_CONSTITUENCY', constituency)
// Do whatever you want with constituency now inside the component.
})
.catch((error) => {
console.log("error", error)
this.$store.commit('SET_CONSTITUENCY', null)
})
}
},
Notes:
$store.dispatch method returns a promise but still the constituency variable receives not the data fetched with the loadConstituencyByAreaCodeAndParliament action but remains empty.
When I enter the area code a second time everything works well.
I think the problem here is that you either handled bad the asyncronous code or trying to implement a custom pattern to work around.
As I said earlier put store getters in computed properties,
Look at this example in the Vuex-docs.
Code insights:
// Your action doesn't return anything, you must `return axios.get` inside it.
this.$store.dispatch('loadConstituencyByAreaCodeAndParliament', payload).then(() => {
let constituency = this.$store.getters.getConstituency()
})
// without the `return` statement the code above can be translated to
this.$store.dispatch('loadConstituencyByAreaCodeAndParliament', payload)
let constituency = this.$store.getters.getConstituency()
// If you opt for async a valid way would be
async doSomething(){
await this.$store.dispatch('loadConstituencyByAreaCodeAndParliament', payload)
let constituency = this.$store.getters.getConstituency()
}
// IF it still doesnt update anything try `$nextTick` https://vuejs.org/v2/api/
this.$nextTick(() => {
this.data = this.$store.getters.getConstituency()
})
I hope some of this has been helpful.
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});
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);
}
}
I am pulling the data from MongoDB database using axios and set the value to a state value named invoices
I do this in componentDidMount. And again I want to access that state (i.e. invoices) within the componentDidMount. I am trying to set invoices value to another state value called dataa. But always ends up getting empty/null.
Problem is the state has Not been set, value is empty
This is what my code snippet looks like:
componentDidMount() {
axios
.get("http://localhost:4005/purchaseinvoices")
.then(response => {
this.setState({
invoices: response.data //setting value to invoices
});
})
.then(
this.setState({
dataa: this.state.invoices //setting value to dataa
})
)
.catch(err => {
console.log(err);
});
//but this gives 0
alert(this.state.invoices.length)
}
what is the possible cause of problem and how can I fix this?
Update the data directly from response instead of using this.state.invoices.since this.setState is an async call,takes some ms delay to update the state.
componentDidMount() {
axios
.get("http://localhost:4005/purchaseinvoices")
.then(response => {
this.setState({
invoices: response.data //setting value to invoices
data: response.data
});
})
.catch(err => {
console.log(err);
});
}
If you want to see the data on first load of page ,try server side rendering
That's because axios and setState are asynchronous.
But, you can see updated stated in componentDidUpdate or in render function.
Edit: Also you can access just the stated is updated like below;
axios
.get("http://localhost:4005/purchaseinvoices")
.then(response => {
this.setState({
invoices: response.data, //setting value to invoices
dataa: response.data
}, () => {
alert(this.state.invoices.length)
});
})
.catch(err => {
console.log(err);
});
}
currently I'm writing an app in react that works with .NET Core web api and I'm getting:
TypeError: this.setState is not a function
I will explain when I get this error exactly in last paragraph
Now the view is intended to work as follows, on a page I have a table with data seeded from an array of JSON data and couple of inputs next to it. When I click on a table row it will put row data into inputs and I can edit the data from here. Two functions are handling this, first: editData is sending POST request to api and then it modify edited object in database. After this request is done second function: getData shall run and refresh a table with new, edited data. Here is how it looks:
Constructor:
class SomeClass extends React.Component {
constructor(props) {
super(props);
this.props = props;
this.state = {
someArray: [],
tableData1: "",
tableData2: "",
validationSummary: [],
isSubmitting: false
}
this.handleChange = this.handleChange.bind(this); //function to handle change on inputs
this.getData = this.getData.bind(this);
this.editData= this.editData.bind(this);
}
Function for getting data:
async getData() {
return fetch("http://localhost:5000/api/somecontroller/getroute")
.then(response => response.json())
.then(data => {
this.setState({
someArray: data,
tableData1: "",
tableData2: "", //clear inputs
})
})
}
And finally edit object function:
async editData() {
var validationSummary = []
if (this.state.tableData1 !== "" && this.state.tableData2 !== "") {
this.setState = {
validationSummary: [], //clear summary
isSubmitting: true
}
let response = await fetch('http://localhost:5000/api/somecontroller/editroute', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
data1: tableData1,
data2: tableData2
})
});
if (response.status !== 200) {
validationSummary.push("Something went wrong")
this.setState = {
validationSummary: validationSummary,
isSubmitting: false
}
}
else {
await this.getData()
}
}
else {
validationSummary.push("Inputs cannot be empty")
this.setState = {
validationSummary: validationSummary,
isSubmitting: false
}
}
}
The problem is that whenever I edit some data and submit it to send request to API, website stops and I get the before mentioned error on this.setState line in the getData function:
async getData() {
return fetch("http://localhost:5000/api/somecontroller/getroute")
.then(response => response.json())
.then(data => {
this.setState({ //getting error here
I've read couple other questions similiar to this but everytime the solution was to bind function to this in constructor. However as you can see in my code I have binded both getData and editData functions to this. I probably have to mention that when I REMOVE all this.setState references from editData function, page is working properly and I'm no longer getting any error. I'm pretty new to react so I'm curious as to why I'm getting this error as it is and how can I fix this without removing this.setState references from editData function, because I need them for displaying error messages.
UPDATE
amrs-tech's answer fixed my problem, in editData changing this.setState = {...} to this.setState({...}) made it work. Hope it will be useful for someone in future!
if (this.setState.tableData1 !== "" && this.setState.tableData2 !== "")
this is incorrect. It should be like:
if (this.state.tableData1 !== "" && this.state.tableData2 !== "")
Your editData should be like:
async editData() {
var validationSummary = []
if (this.state.tableData1 !== "" && this.state.tableData2 !== "") {
this.setState ({
validationSummary: [], //clear summary
isSubmitting: true
})
let response = await fetch('http://localhost:5000/api/somecontroller/editroute', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
data1: tableData1,
data2: tableData2
})
});
if (response.status !== 200) {
validationSummary.push("Something went wrong")
this.setState ({
validationSummary: validationSummary,
isSubmitting: false
})
}
else {
await this.getData()
}
}
else {
validationSummary.push("Inputs cannot be empty")
this.setState ({
validationSummary: validationSummary,
isSubmitting: false
})
}
}
Hope it works. You can refer for some details about React state and lifecycle - here
setState is a function, also state and props are immutable.
// this.state.tableData1
this.setState.tableData1
// this.setState({...})
// and either way its should considered immutable
this.setState = {
validationSummary: [], //clear summary
isSubmitting: true
};
// also this is redundant because props are immutable.
this.props = props;
another quick way to fix this problem is to keep 'this' as variable
async getData() {
var scope = this;
return fetch("http://localhost:5000/api/somecontroller/getroute")
.then(response => response.json())
.then(data => {
scope.setState({ // scope replace 'this' here