async/await for fetching data with spinner - reactjs

Goal
To have a spinner running till data loads.
What I did
I followed this article
I also tried regular promise and then but no success.
What happens
the console.log is displaying "boom" right off the bat, so not waiting for data fetching. No errors.
EventPage.js
constructor (props) {
super(props);
this.state = {
panelView: true,
loading: true
}
}
async componentDidMount () {
try {
await this.props.fetchEvents();
this.setState({loading: false});
console.log("BOOM")
} catch {
}
}
render() {
const {loading} = this.state;
const {panelView} = this.state;
if (loading) {
return <Loader />
}
return (
(Actual page)
)
}
eventActionCreator fetchEvents
export const fetchEvents = () => {
return async dispatch => {
try {
const response = await axios.get('http://localhost:3090/events');
dispatch(setEvent(response.data));
return response.data;
} catch (e) {
return e;
}
}
}
The console is only to show code is waiting for fetch to execute, it doesn't.

Try this approach
state = {
products: [],
loading: true,
}
async componentDidMount() {
// try catch just to make sure we had no errors
try {
const res = await fetch('http://api');
// wait for json to be ready
const data = await res.json();
this.setState({
loading: false,
data,
});
// console.log(product.data.attributes)
} catch (e) {
console.log(e);
}
}
render() {
const { data, loading } = this.state;
return (
<Container>
<Contents>
{loading ? 'loading..' : <Products data={data} />}
</Contents>
</Container>
);
}

I figured out the issue. The problem was that I returned data from action creator and not a promise based action.
so, instead of
const response = await axios.get('http://localhost:3090/events');
dispatch(setEvent(response.data));
return response.data;
it should have been
return axios.get('http://localhost:3090/events')
.then((response) => {
dispatch(setEvent(response.data));
});
Issue that helped me resolve it

Seems you have it backward... You're initializing your state to true and then setting it to false in cdm... Then you are checking if loading is true if so render the Loader... Of course, it's not true, you set it to false...
Change to this:
constructor (props) {
super(props);
this.state = {
panelView: true,
loading: false <- here
}
}
async componentDidMount () {
this.setState({loading: true}); <- here
try {
await this.props.fetchEvents();
this.setState({loading: false}); <- and here...
console.log("BOOM")
} catch {
}
}
render() {
const {loading} = this.state;
const {panelView} = this.state;
if (loading) {
return <Loader />
}
return (
(Actual page)
)
}
Here is a live Demo: https://codesandbox.io/s/r0w381qw3p

Related

Using state with componentDidMount

I want to fetch data that returns successfully after componentDidMount, but before there is an error that singer.data is undefined:
// imports
export default class LookSinger extends Component {
state = {
singer: {}
}
componentDidMount () {
let { singer } = this.props.location.state;
singer = singer.replace(/ /g,"+");
const fetchData = async () => {
try {
const response = await fetch(
`http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=${singer}&api_key=a3c9fd095f275f4139c33345e78741ed&format=json`
);
const data = await response.json();
this.setState({
singer: data
})
} catch (error) {
console.log(error.message);
}
}
fetchData();
}
render() {
let singer = this.state.singer
return(
<div>
{console.log(singer.artist.name)} // gives undefined but after fetching artist.name absolutely exists
</div>
)
}
}
Url is:http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=Ariana+Grande&api_key=a3c9fd095f275f4139c33345e78741ed&format=json
The problem is here:
{console.log(singer.artist.name)}
In the initial render, singer.artist is undefined and if you call singer.artist.name it will throw error. name of undefined.... You just need to wait for data to fetch and update the state.
Try like this
export default class LookSinger extends Component {
state = {
singer: {}
}
componentDidMount () {
let { singer } = this.props.location.state;
singer = singer.replace(/ /g,"+");
const fetchData = async () => {
try {
const response = await fetch(`http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=${singer}&api_key=a3c9fd095f275f4139c33345e78741ed&format=json`);
const data = await response.json();
this.setState({ singer: data })
} catch (error) {
console.log(error.message);
}
}
fetchData();
}
render() {
const { singer } = this.state
if (!singer.artist) { // <-- Check if the data is present or not.
return <div>Loding singer info...</div>
}
return(
<div>
<h1>{singer.artist.name}</h1>
</div>
)
}
}
You do let singer = this.state but there's no this.setState({ singer: ... }) in your code. Instead of this.setState({ data }), try this.setState({ singer: data })
Set you state as below and,
const data = await response.json();
this.setState({
singer: data
})
and you can log it out likes this,
console.log(this.state.singer)

Warning: Can't perform a React state update on an unmounted component....componentWillUnmount method

I'm very new to React and React Native and I am getting this warning when I switch screens. Also, the console.log keeps repeating infinitely, how do I fix it?
class DecodeScreen extends Component {
state = {
data: this.props.navigation.getParam("data", "NO-QR"),
bookData: '',
bookFound: false
}
bookSearch = () => {
query = `https://librarydb-19b20.firebaseio.com/books/${this.state.data}.json`,
axios.get(query)
.then((response) => {
const data = response.data ? response.data : false
console.log(data)
if (data) {
this.setState({
bookData: data,
bookFound: true
})
}
}).catch((error) => {
this.setState({
bookFound: false
})
})
}
renderContent = () => {
if (this.state.bookFound) {
return(
<View>
<TextH5>{this.state.bookData.title}</TextH5>
<TextH5>{this.state.bookData.author}</TextH5>
<TextH5>{this.state.bookData.publisher}</TextH5>
<TextH5>{this.state.bookData.isbn}</TextH5>
</View>
)
}
else {
return <TextH5>beer not found</TextH5>
}
}
componentDidMount() {
this.bookSearch()
}
render() {
{this.bookSearch()}
return (
<Container>
<TextH5>{this.state.data}</TextH5>
{this.renderContent()}
</Container>
);
}}
export default DecodeScreen;
the console.log outputthe warning
You can try this approach to see if it fixes the problem.
isMounted = false;
class DecodeScreen extends Component {
state = {
data: this.props.navigation.getParam("data", "NO-QR"),
bookData: "",
bookFound: false,
};
bookSearch = () => {
this.isMounted = true;
(query = `https://librarydb-19b20.firebaseio.com/books/${this.state.data}.json`),
axios
.get(query)
.then((response) => {
const data = response.data ? response.data : false;
console.log(data);
if (data) {
if (this.isMounted) {
this.setState({
bookData: data,
bookFound: true,
});
}
}
})
.catch((error) => {
this.setState({
bookFound: false,
});
});
};
renderContent = () => {
if (this.state.bookFound) {
return (
<View>
<TextH5>{this.state.bookData.title}</TextH5>
<TextH5>{this.state.bookData.author}</TextH5>
<TextH5>{this.state.bookData.publisher}</TextH5>
<TextH5>{this.state.bookData.isbn}</TextH5>
</View>
);
} else {
return <TextH5>beer not found</TextH5>;
}
};
componentDidMount() {
this.isMounted = true;
this.bookSearch();
}
componentWillUnmount() {
this.isMounted = false;
}
render() {
{
this.bookSearch();
}
return (
<Container>
<TextH5>{this.state.data}</TextH5>
{this.renderContent()}
</Container>
);
}
}
export default DecodeScreen;
You have to use componentDidMount method to do api call
componentDidMount() {
this.bookSearch()
}
Read about react life cycle method

context in componentDidMount appears as null

I currently have a context provider.
componentDidMount() {
if (this.state.memberID === null) {
try {
this.checkAuthUser();
} catch (e) {
console.error(e);
}
}
}
checkAuthUser = () => {
new Promise((resolve, reject) => {
this.props.firebase.auth.onAuthStateChanged(authUser => {
if(authUser) {
resolve(authUser);
} else {
reject(new Error("Not authorized"));
}
})
})
.then( authDetails => {
this.props.firebase.getOrgID(authDetails.uid).on('value', snapshot => {
const setSnapshot = snapshot.val();
const getOrganizationID = Object.keys(setSnapshot)[0];
this.setState({ memberID: authDetails.uid, orgID: getOrganizationID })
})
})
.catch(err => console.log(err))
}
When I try to use this in another component:
static contextType = AuthDetailsContext;
componentDidMount() {
console.log('here is context: ' + this.context.orgID);
if(this.context.orgID) {
this.setState({currentOrganization: this.context.orgID, loading: true}, () => {
this.getMembersInDB('1');
})
}
}
My console.log is null. Means the context isn't registering yet. Any idea what I'm doing wrong?
Your design here seems flawed i.e. when your provider is mounted you send the API request and then when your descendant component is mounted you try to use it - these operations will happen in quick succession, far quicker than it would take for an API call to return from a server.
In your provider, if you must have a user before the component mounts then you need to delay rendering the child components until your API response completes i.e.
const AuthDetailsContext = React.createContext(null);
class AuthDetailsProvider extends PureComponent {
...
componentDidMount() {
const { firebase } = this.props;
firebase.auth.onAuthStateChanged(authUser => {
if (!authUser) {
// Maybe set some other state state to inform the user?
this.setState({ authError: new Error('Not Authorised') });
return;
}
firebase.getOrgID(authUser.uid)
.on('value', snapshot => {
const setSnapshot = snapshot.val();
const getOrganizationID = Object.keys(setSnapshot)[0];
this.setState({
authError: null,
memberID: authUsermemberID.uid,
orgID: getOrganizationID
});
});
})
}
render() {
if (this.state.authError) return <b style={{ color: red }}>{this.state.error.message}</b>;
if (!this.state.memberID) return <b>Authenticating...</b>
return (
<AuthDetailsContext.Provider value={this.state}>
{this.props.children}
</AuthDetailsContext.Provider>
);
}
}

How to fix 'Can't perform a React state...' error in React

I making mutation in LyricCreate
` onSubmit = (e) => {
e.preventDefault();
const { content } = this.state;
const { songId, addLyric } = this.props;
addLyric({
variables: {
content,
songId
},
}).then( () => this.setState({ content: '' }) )
}`
it's going well, and adds to database.
But in parent component appears error with
after refresh page created Lyric appears in lyricList, and parent component songDetails doesn't has errors till I make mutation again.
Help please..
you can check if your component is mounted like this
componentDidMount() {
this._ismounted = true;
}
componentWillUnmount() {
this._ismounted = false;
}
onSubmit = (e) => {
e.preventDefault();
const { content } = this.state;
const { songId, addLyric } = this.props;
addLyric({
variables: {
content,
songId
},
}).then(() => {
if(this._ismounted {
this.setState({ content: '' })
}
})
}

React error with not fetched data. Ugly Code and null pointer. What to do?

I have an React code that needs to fetch some data from an API, put it on a redux-store, and then render a List with this data. This is what Im doing
constructor(props) {
super(props);
this.state = {
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
this.loadData();
}
loadData = async () => {
try {
API.getList()
.then(data => {
this.updateState(data);
})
.then(data => this.setState({ isLoading: false }))
.catch(function(error) {
console.log(error.message);
});
} catch (e) {}
};
updateState = async (data) => {
if (data != null) {
await this.props.mainActions.receiveData(data);
}
};
render() {
const { isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
let items = [];
if (this.props.items.data !== undefined) {
items = this.props.items.data.stadiums;
}
return <MyList items={items} />;
}
}
The problem is, the first time it renders, when I try to get "this.props.items" it is undefined yet.
So I need to put this ugly IF to dont break my code.
What will be a more elegant solution for this problem?
I am assuming the use of ES6 here:
I would set a defaultProp for items in the MyList component
export class MyList extends Component {
...
static defaultProps = {
items: []
}
...
}
This way, if you pass items as undefined and mapping over items in your render method it will produce an empty array which is valid jsx
Ok. Just change the "componentDidMount" with "componentWillMount".
Jsx doesn't render undefined or null so you can include your condition in your return statement.
Instead of writing an if statement, do this:
return (
{
this.props.items.data &&
this.props.items.data.stadiums &&
<Mylist
items={this.props.items.data.stadiums}
/>
}
);

Resources