How can I display a spinner in a React App? - reactjs

I have a React App and I want to show a spinner when processing will take some time to complete, for example an API request, or even lengthy setting of state. I have tried a callback such as:
this.setState({
showSpinner: true,
}, () =>
this.APIgiveMeTheWorld().then(
this.setState({
showSpinner: false,
})
));
and async function with:
await this.setState({
showSpinner: true,
});
this.APIgiveMeTheWorld().then(
await this.setState({
showSpinner: false,
})
);
But at no discernible point does state show showSpinner as being true (therefore my spinner never appears). What is the best way to implement this kind of functionality.

What you can do is to show the Spinner as long the showSpinner state is false and hide it when It became false
{ !showSpinner? <SpinnerComponent /> : <ResultShower /> }
And in your component you can load your data from an API in the componentWillMount lifeCycle and when the loading completed and you have data back from the API's Call you can update the showSpinner state to false, which will automatically hide the SpinnerComponent and show whatever else you want to show.
componentWillMount() {
loadData().then((data) => {
// Here I consider get data from from you API request
this.setState({
data,
showSpinner: false
});
})
}

I see you have passed result of setState in call back then. Did you try:
this.setState({
showSpinner: true,
}, () =>
//should pass a call back function to call right after api return.
this.APIgiveMeTheWorld().then(r => {
this.setState({
showSpinner: false,
})
}));

The problem is with my API function resolving instantaneously.

Related

Cann't set the state data from response data

I'm doing in the axios to get data from webAPI . The problem is state is empty
componentDidMount() {
uploadFilesService.getFiles().then((response) => {
this.setState({
fileInfos: response.data,
});
console.log('check 1', response.data)
console.log('check 2', this.state.fileInfos)
});
}
The other project with the same method is OK .
You can the use second argument of setState
this.setState({
fileInfos: response.data,
}, () => {
// do something with the new state
});
From React doc:
The second parameter to setState() is an optional callback function
that will be executed once setState is completed and the component is
re-rendered. Generally we recommend using componentDidUpdate() for
such logic instead.

Why won't my changes to state show in my componentDidMount lifecycle?

I am building an app using React and for my homepage, I set state in the componentDidMount lifecycle:
export default class HomePage extends Component {
state = {
posts: [],
token: '',
};
//Display posts when homepage renders
componentDidMount() {
//If token exists, run lifecycle event
if (this.props.location.state.token) {
this.setState({ token: this.props.location.state.token });
}
Axios.get('http://localhost:3000/api/posts/all')
.then((req) => {
this.setState({ posts: req.data });
})
.catch((err) => {
console.log(err.message);
throw err;
});
console.log(this.state);
}
However when I run the console log at the end of the lifecycle method, it shows posts and token as still being empty. I know they are being populated because the posts from the req.data show up in my JSX. Why does it show state being empty when I console log inside the method?
React setState is asynchronous!
React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component.
Think of setState() as a request rather than an immediate command to update the component.
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});

Waiting for request to finish before performing operation ReactJS

I have an axios call inside my react application
}).then(res =>
this.setState({
name: res.data.fname,
sName: res.data.sname,
percentage: res.data.percentage,
result: res.data.result,
showResult: true,
type: this.typeHandler(res.data.percentage)
}));
}
after the request is done AND the state is set, I want to set input forms to blank. So i tried this
}).then(res =>
this.setState({
name: res.data.fname,
sName: res.data.sname,
percentage: res.data.percentage,
result: res.data.result,
showResult: true,
type: this.typeHandler(res.data.percentage)
})
).then(
this.setState(prevState =>{
return {inputName: '', inputSname: ''}
}))
}
using another promise, being resolved after the first, but the forms are still set to blank before the request state is being set. I have tried using a callback as well, but the first .then() method, does not accept a second argument. How can I solve this?
setState can get a call back
https://reactjs.org/docs/react-component.html#setstate
setState(updater[, callback])
instead of another then pass this callback to setState

axios.get function in ComponentDidMount not setting state as expected

In my header component, I have conditional rendering set to display different links depending on if the user is logged in or not. The conditional rendering works fine EXCEPT the fact that it blinks one set of links before displaying the proper set of links.
It dawned on me this is because of the axios.get call I have that retrieves the user's status. The state variable is initially set to false. If the user is logged in, the get call will indicate that and the state variable will get set to true. The short delay makes it so the "not logged in" links are shown momentarily before the "is logged in" links get displayed.
I've tried quite a few recommended approaches in both componentDidMount and componentWillMount - nothing seems to be working:
componentDidMount() {
axios.get('http://localhost:8000/api/login', {
withCredentials: true
}).then(res => {
if (res.data.loggedIn) {
this.setState({isLogged: true});
} else {
this.setState({isLogged: false});
}
}).catch(error => {
this.setState({isLogged: false});
});
}
Next approach:
async componentDidMount() {
const res = await axios.get('http://localhost:8000/api/login', {
withCredentials: true
});
const logged = await res.data.loggedIn;
if (logged) {
this.setState({isLogged: true});
} else {
this.setState({isLogged: false});
}
}
I've tried call back functions with the same results.
The conditional rendering is the typical:
{this.state.isLogged && <LoggedIn />}
I'm clearly failing to see something basic in how the async functions work - I'm just not sure what that is!
Thanks in advance!!
The best that you can do in this case is to show nothing or a spinner until the request has finished. It doesn't matter what you do (async/await, promise, callback) that request is going to take some time.
Here’s a method I’ve found useful. Add a loading variable to state:
async componentDidMount() {
this.setState({ loading: true });
const res = await axios.get('http://localhost:8000/api/login', {
withCredentials: true
});
const logged = await res.data.loggedIn;
if (logged) {
this.setState({isLogged: true, loading: false });
} else {
this.setState({isLogged: false, loading: false });
}
}
Then, you can add:
{!this.state.loading && this.state.isLogged && ...}
You can always set loading to true in your component’s constructor, if you still see a flash, but I would try to keep loading in the same method as the process that’s actually loading.

Set loading state before and after an action in a React class component

I have function which dispatched an action. I would like to display a loader before and after the action. I know that react composing the object passed to setState. the question is how can I update the property in async way:
handleChange(input) {
this.setState({ load: true })
this.props.actions.getItemsFromThirtParty(input)
this.setState({ load: false })
}
Basically, it all worked great if I put this property as part of the application state (using Redux), but I really prefer to bring this property to the component-state only.
you can wrap the setState in a Promise and use async/await as below
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}
async handleChange(input) {
await this.setStateAsync({ load: true });
this.props.actions.getItemsFromThirtParty(input);
await this.setStateAsync({ load: false })
}
Source: ASYNC AWAIT With REACT
Wrap the rest of your code in the callback of the first setState:
handleChange(input) {
this.setState({
load: true
}, () => {
this.props.actions.getItemsFromThirtParty(input)
this.setState({ load: false })
})
}
With this, your load is guaranteed to be set to true before getItemsFromThirtParty is called and the load is set back to false.
This assumes your getItemsFromThirtParty function is synchronous. If it isn't, turn it into a promise and then call the final setState within a chained then() method:
handleChange(input) {
this.setState({
load: true
}, () => {
this.props.actions.getItemsFromThirtParty(input)
.then(() => {
this.setState({ load: false })
})
})
}
Here's a typescript implementation of an "async-await" setState:
async function setStateAsync<P, S, K extends keyof S>(
component: Component<P, S>,
state:
((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) |
Pick<S, K> |
S |
null
) {
return new Promise(resolve => component.setState(state, resolve));
}
The previous answers don't work for Hooks. In this case you get the following error when passing a second argument to setState
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
As the error message says, you need to use useEffect instead in this case (see also this discussion for more detailed information)
Here's what you can do...
Change your action to take in a onFetchComplete callback, along with the input.
Change your handleChange to -
handleChange(input) {
this.setState({ load: true }, ()=>
this.props.actions.getItemsFromThirtParty(input,
()=>this.setState({ load: false }))
);
}
This will ensure the action processor code can invoke back your state change callback even if it's not written in a promise based fashion.
A small update- using promises for the action creators and async/await works great, and it makes the code even cleaner, compared to the "then" chaining:
(async () => {
try {
await this.props.actions.async1(this.state.data1);
await this.props.actions.async2(this.state.data2)
this.setState({ load: false );
} catch (e) {
this.setState({load: false, notify: "error"});
}
})();
Of course it is a matter of taste.
EDIT : Added missing bracket
I know this is about class components... But in functional components I do this to synchronously set the state:
const handleUpdateCountry(newCountry) {
setLoad(() => true);
setCompanyLocation(() => newCountry);
setLoad(() => false);
}
Just worked for my automatic country detection that should set the form to dirty.
One approach not mentioned in any of the other answer is wrapping the operation in a setTimeout. This will also work if you use hooks.
Eg.:
handleChange(input) {
this.setState({ load: true })
setTimeout(() => {
this.props.actions.getItemsFromThirtParty(input).finally(() => {
this.setState({ load: false })
});
});
}

Resources