setState not being executed maybe - reactjs

I have a component that fetches a list of items from an API on a button click. name state variable comes from the input box. Code of my handleSubmit method is:
handleSubmit(event) {
event.preventDefault()
this.setState({
fetching: true
})
fetch("https://example.com/v2/sites/list/"+this.state.name, {
method: 'GET',
})
.then(response => async(response) => {
await response.json()
this.setState({
names: response.json()["items"],
fetching: false,
fetched: true
})
toast.success(<NotifySuccess />)
})
.catch(error => toast.error(<NotifyFailure />));
}
On fetched state value being set to true, in my render method, i am trying to render a alert box having names value in it through portals but it seems setState is not working as expected. No alert box appears and on inspection of component through React developer tool, fetched state value is not being updated to true. Why is it not being updated? Plus, when i set it to true myself using developer tool, an alert box appears with empty value. I clicked ok to close it but it opens again. Have to press ok button again. So alert box also appears two times. Any help would be appreciated. Thanks.

#CertainPeformance is absolutely right, just adding a bit of a cleaner approach in case you can make handleSubmit an async function.
async handleSubmit(event) {
...
try {
const response = await fetch("https://example.com/v2/sites/list/" +
this.state.name, {
method: 'GET',
})
const { items } = await response.json()
this.setState({
names: items,
fetching: false,
fetched: true
})
toast.success(<NotifySuccess />)
} catch (error) {
toast.error(<NotifyFailure />)
};

The response is a Promise. Awaiting it in one section of the code doesn't transform that same variable into the resolve value. You need to assign the result of awaiting it into a variable, which will hold the resolve value, then pass that to setState:
fetch("https://example.com/v2/sites/list/" + this.state.name, {
method: 'GET',
})
.then(response => async (response) => {
const result = await response.json();
this.setState({
names: result.items,
fetching: false,
fetched: true
})
toast.success(<NotifySuccess />)
})
.catch(error => toast.error(<NotifyFailure />));

Related

React - state not updating after API call

I am trying to setState after an API call, and I know this is an async task but I can't figure out how to update my state. My code looks like this:
loadUserDetails = () => {
this.setState({
isLoading: true,
status: "Fetching user details..."
}, () => {
fetch('url', { method: 'Get', credentials: 'include' })
.then(res => res.json())
.then((data) => {
console.log("results")
console.log(data.name);
console.log(data.surname);
console.log(data.emailAddress);
this.setState({
userProfile: data
})
if (this.state.userProfile != null)
this.loadRolesData();
})
})
});
.catch(console.log);
}
The console logs are producing the correct values but when I try to update the userProfile to data it doesn't happen. Reading the docs I can see useEffect as a solution but unsure how to implement it.
Edit:
I am initiating this from componentDidMount(). I think this is the correct place but happy to be told otherwise.
I think you did the task in the wrong order.
Do fetch for the api, afterwards do setState. Here's one simple example.
fetch(...).then(res => {
this.setState({...})
})
Please don't get confused about the second parameter of setState, that is to wait till state to finish update. Normally that is designed for some special occasion, 99% of time you don't need that.
setState doesn't update the state immediately after the call, and so that's why there is a second argument (callback). It is fired only when the update is finished. You used that second argument in your first setState call actually. So you can either do the same thing in the second call:
this.setState({
isLoading: true,
status: "Fetching user details..."
}, () => {
fetch('url', { method: 'Get', credentials: 'include' })
.then(res => res.json())
.then((data) => {
console.log("results")
console.log(data.name);
console.log(data.surname);
console.log(data.emailAddress);
this.setState({
userProfile: data
}, () => {
// this code will get fired only after the state updates
if (this.state.userProfile != null) {
this.loadRolesData();
}
})
})
.catch(console.log);
});
Or you can use react hooks which would require you to refactor your component into a function and rewrite your fetch logic like the following:
const [userProfile, setUserProfile] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
// This function will get fired every time the userProfile state updates
React.useEffect(() => {
if (userProfile != null) {
loadRolesData();
}
}, [userProfile]);
const loadUserProfile = () => {
setIsLoading(true);
setStatus("Fetching user details...");
fetch('url', { method: 'Get', credentials: 'include' })
.then(res => res.json())
.then((data) => {
console.log("results")
console.log(data.name);
console.log(data.surname);
console.log(data.emailAddress);
setUserProfile(data);
setIsLoading(false);
})
.catch(console.log);
};

How to send updated state in axios in React?

I am trying to send post request using axios in Reactjs.
I have two component a timer component and App component and in App component i am trying to submit a form and send an axios call when i fetch the time from Timer component and save itinto counter state
I have written a condition if counter is true then update my state and then further send the post request
Working Demo
here is a handle submit code:
const handleSubmit = e => {
console.log("handleSubmit");
e.preventDefault();
if (counter) {
console.log(counter);
const url = `url string`;
setState({
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
});
console.log(state);
axios
.post(url, state)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
}
};
The problem is when counter is true its not update the state which causes error while send axios request.
I have consoled each and every thing but still it fails.
It seems there is lot of rendering.
If you are using class components, you can make the reuqest after the state has been set. Something like this:
this.setState({
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
}, () => {
axios
.post(url, state)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
});
Since you did set the react-hooks tag, I guess that approach is not what you need. In your case, I suggest saving new state in some temporary variable and than passing that variable to axios. Like this:
const newState = {
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
};
setState(newState);
axios
.post(url, newState)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
setState can be executed asynchronously by React, to optimize the rendering process. For cases like this one, it can also take a callback function that is guaranteed to be executed after updating the state.
For example:
this.setState({
name:'value'
},() => {
console.log(this.state.name);
});
in this case console.log will be executed after setting the name variable.
see the docs: https://reactjs.org/docs/react-component.html#setstate

React setstate callback does not work after initial use

I have a function that, when initialized, takes a previously set state and uses it to make an api call with axios:
_onRefresh = () => {
this.setState({ refreshing: true }, () => {
axios.get(this.state.currentPath)
.then(res=>{
console.log(res.data)
this.props.loadCards(res.data)
})
this.setState({refreshing: false})
});
}
I can see that the promise is never completed, and that a response is not given.
However, on the first use after the page loads, the function works correctly; it's only on subsequent usage that it does not work.
When the get request does not work, I've taken the path that's been stored in state, made a request in postman, and received a valid result.
you should cancel the refreshing in the finally block
get(...)
.then(...)
.catch(...)
.finally(() => this.setState({refreshing: false}))
Does this fix the issue?
_onRefresh = () => {
this.setState({ refreshing: true }, () => {
axios.get(this.state.currentPath)
.then(res=>{
console.log(res.data)
this.props.loadCards(res.data)
this.setState({refreshing: false}) // moved this line into the then block
})
});
}
Try this
_onRefresh =async () => {
this.setState({ refreshing: true }, () => {
await axios.get(this.state.currentPath) //you might need to handle promises to get this working.
.then(res=>{
console.log(res.data)
this.props.loadCards(res.data)
})
this.setState({refreshing: false})
});
}

Cannot fetch api due to array react native

I bulid an api using laravel which can run in postman (http://lkcfesnotification.000webhostapp.com/api/notifications). The problem is when i fetch using an example from this (https://www.youtube.com/watch?v=IuYo009yc8w&t=430s) where there is a array in the api then i have to setstate the array which is working well but when i try using the below code it does not render due to it is not using array in the api for example the random user api have "results" :[item], and mine one is "data":[my item]
fetchData = async () => {
const response = await fetch("https://randomuser.me/api?results=500");
const json = await response.json();
this.setState({ data: json.results });
};
if i use this will work but i want to use below code due to some homework i am doing
type Props = {};
export default class IndexScreen extends Component<Props> {
...
this.state = {
data: [],
isFetching: false,
};
_load() {
let url = "http://lkcfesnotification.000webhostapp.com/api/notifications";
this.setState({isFetching: true});
fetch(url)
.then((response) => {
if(!response.ok) {
Alert.alert('Error', response.status.toString());
throw Error('Error ' + response.status);
}
return response.json()
})
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
.catch((error) => {
console.log(error)
});
}
https://imgur.com/a/he5mNXv this is my render
the result i get the code i run is blank is loading
The fetch request is working but you are not saving the right data in the right state property.
The issues is located in the following part:
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
You are assigning the response to a variable members but saving another variable data, which does not exist.
In addition, the response is an object with more information than just the data, so what you are looking for is just the data property of the response.
This should work:
.then(({ data }) => {
this.setState({data});
this.setState({isFetching: false});
})
Here we destructure the response into the variable { data }, solving your issue.
Based on the snippets you don't use the fetched data to set it to your state:
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
membersis the result of your fetched json. So either rename members to data or use data: members. If the code should work like your first function it's probably data: members.result. You can also combine the two setState calls to one single call:
this.setState({
data: members.result,
isFetching: false,
});

React - wait for promise before render

I can't figure this out, if you can point me to right direction. On my NavMenu.js I have LogIn (two inputs and a submit button, on which I call handleSubmit()
In handleSubmit() I am checking for user login credentials which works great, but after I confirm login, i procede with doing next fetch for checking user roles (and returning promise) and visibility in application
Helper.js
function getAllRoles(formName, sessionVarName) {
var roleData = [];
var roleId = sessionStorage.getItem('UserLoggedRoleId');
fetch('api/User/GetUserRole/', {
'method': 'POST',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
'body':
JSON.stringify({
'roleId': roleId,
'formName': formName
})
}).then(roleResponse => roleResponse.json())
.then(data => {
sessionStorage.setItem(sessionVarName, JSON.stringify(data));
roleData = data;
});
var result = Promise.resolve(roleData)
return result;
}
function checkRoleVisibility(userRoles, role) {} <-- return true or false
export {
getAllRoles ,
checkRoleVisibility
};
In NavMenu.js
import { getAllRoles, checkRoleVisibility } from './common/Helper';
handleSubmit() {
fetch('api/User/UserAuthentification/',
....then(response => {
if (response.ok) {
this.setState(prevState => ({ userLogged: !prevState.userLogged }))
response.json().then(value => {
sessionStorage.setItem('UserLogged', true);
sessionStorage.setItem('UserLabelSession', this.state.userLabel);
sessionStorage.setItem('UserLoggedRoleId', value.userRole.roleID);
getAllRoles('NavMenu', 'UserLoggedRoles')
.then(roleData => {
this.setState({
loggedUserRoles: roleData,
loading: false
});
});
this.props.alert.success("Dobro dosli");
})
}
}
But here comes the problem, is that in render() itself, I have a control navBar which calls
checkRoleVisibility(this.state.loggedUserRoles, 'SeeTabRadniNalozi')
from helper, which send this.state.loggedUserRoles as parameter, which suppose to be filled in fetch from handleSubmit() but it's undefined, fetch finish, set loading on true, rendering completes and it doesn't show anything
I have this.state.loading and based on it I show control ...
let navMenu = this.state.loading ? <div className="loaderPosition">
<Loader type="Watch" color="#1082F3" height="200" width="200" />
</div> : !this.state.userLogged ? logInControl : navBar;
If I put a breakpoint on controler I can see my method is being called by getAllRoles function, but also I can see that application keep rendering on going and doesn't wait for promise to return to fill state of loggedUserRoles.
So question is, why rendering doesn't wait for my role fetch nor doesn't wait for promise to resolve before setting loading on true?
I checked this answer Wait for react-promise to resolve before render and I put this.setState({ loading: false }); on componentDidMount() but then loading div is shown full time
From what I see and understand is, that you're calling
this.setState(prevState => ({ userLogged: !prevState.userLogged }))
if the response is okay. This will lead in an asynchronous call from React to set the new State whenever it has time for it.
But you need the resource from
this.setState({
loggedUserRoles: roleData,
loading: false
});
whenever you want to set userLogged to true, right?
So it might be that React calls render before you set the loggedUserRoles but after userLogged is set to true. That would result in an error if I understand you correctly.
You could simply set the userLogged state with the others together like this:
this.setState({
userLogged: true,
loggedUserRoles: roleData,
loading: false
});
EDIT:
Moreover you want to change your Promise creation inside of getAllRoles. You're returning
var result = Promise.resolve(roleData)
return result;
This will directly lead to the empty array since this is the initial value of roleData. Therefore you're always returning an empty array from that method and don't bother about the fetched result.
You've multiple ways to solve this. I would prefer the clean and readable way of using async/await:
async getAllRoles(formName, sessionVarName){
var roleId = sessionStorage.getItem('UserLoggedRoleId');
const response = await fetch(...);
const data = response.json();
sessionStorage.setItem(sessionVarName, JSON.stringify(data));
return data;
Or if you want to stay with Promise:
function getAllRoles(formName, sessionVarName){
return new Promise((resolve, reject) => {
var roleId = sessionStorage.getItem('UserLoggedRoleId');
fetch(...)
.then(roleResponse => roleResponse.json())
.then(data => {
sessionStorage.setItem(sessionVarName, JSON.stringify(data));
resolve(data);
});

Resources