Using state with componentDidMount - reactjs

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)

Related

React not rendering array after fetching data

class MyAttendees extends React.Component {
static contextType = AuthContext;
constructor(props){
super(props);
this.state = {
barcodesData: []
}
}
componentDidMount() {
this.fetchBarcodeData()
}
fetchBarcodeData() {
const { currentUser, GetBarcodesByUser } = this.context; // getting current user logged in and a function
const uid = currentUser.uid; // uid of the user
GetBarcodesByUser(uid) // this will return a array of string containing barcodes id
.then( data => this.setState({ barcodesData: data }))
}
// force rerender
forceUpdater() {
this.forceUpdate();
}
render() {
return (
<div>
{
// trying to render the array list
// this is not rerendering even after the barcodes is updated
this.state.barcodesData.map((item) => {
console.log("item: ", item)
return <h1 key={item}>{item}</h1>
})
}
</div>
)
}
}
export default MyAttendees;
const GetBarcodesByUser = async ( uid: string ): string[] => {
const data = await getBarcodesByUser(uid);
return data;
}
export const getBarcodesByUser = async ( uid: string ): string[] => {
const result = [];
const q = query(collection(firestore, "qrcode"), where("uid", "==", uid));
onSnapshot(q, (querySnapshot): string[] => {
querySnapshot.forEach( document => {
result.push( document.id )
})
})
return result;
}
Things that I have tried
at first I was using function approach with useEffect.
I tried to use function to render array.
I tried to use the fetching function inside componentDidMount() function
Tried forceUpdate
Any of the above method is not working
Not 100% that was the entire console.log so my guess is you are mapping the wrong bit. Try changing your state to set like this
.then( response => this.setState({ barcodesData: response.data }))

How do I return only part of an array from an API call?

I've got a massive list of about 50 dog photos that I'm pulling in from an API into a react component, and I only want to display the first 10.
I wrote the following function to attempt to filter out only the first 10 photos url's in the array
setData = async () => {
const x = await fetch('https://dog.ceo/api/breed/hound/images')
const y = await x.json()
const z = await y.message
let newArr =[]
for (let i=0; i<z.length; i++){
if (i<=10){
newArr.push(z[i])
}
}
return newArr
}
then used the result of that to set the state
componentDidMount(){
const dogList = this.setData()
this.setState({
dog: dogList
})
}
....which then was supposed to render just the first 10 dog photos:
render() {
return (
<div>
<h1>Rate My Dogue</h1>
{
this.state.dog.map(doggie => {
return <img className = 'img' src = {doggie}></img>
})
}
</div>
);
}
}
and unsurprisingly, it didn't work. Does anyone have suggestions on how I can prune my API call?
Here's the full component:
import React from 'react';
import './styles.css'
class App extends React.Component {
constructor(){
super()
this.state = {
dog: []
}
}
setData = async () => {
const x = await fetch('https://dog.ceo/api/breed/hound/images')
const y = await x.json()
const z = await y.message
let newArr =[]
for (let i=0; i<z.length; i++){
if (i<=10){
newArr.push(z[i])
}
}
return newArr
}
componentDidMount(){
const dogList = this.setData()
this.setState({
dog: dogList
})
}
render() {
return (
this.state.loading ? <h1> Dogues Loading.....</h1>
:
<div>
<h1>Rate My Dogue</h1>
{
this.state.dog.map(doggie => {
return <img className = 'img' src = {doggie}></img>
})
}
</div>
);
}
}
export default App;
You have an async function (setData) which returns a promise and to get the value of that async function you need to do a .then() method. So something like this in your componentDidMount
componentDidMount() {
this.setData()
.then((res) => {
this.setState({
dog: res,
});
})
.catch((error) => console.log(error));
}
Or, make your componentDidMount an async function and await the results of setData.
async componentDidMount() {
try {
const dogList = await this.setData();
this.setState({
dog: dogList,
});
} catch (error) {
console.log(error);
}
}
In your question, you stated you wanted the first 10 photos so your setData should have a check like this since your loop is starting at the index of 0.
setData = async () => {
const x = await fetch("https://dog.ceo/api/breed/hound/images");
const y = await x.json();
const z = await y.message;
let newArr = [];
for (let i = 0; i < z.length; i++) {
if (i <= 9) {
newArr.push(z[i]);
}
}
return newArr;
};
Please don't forget to add a key prop to your map method in the render function.

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: '' })
}
})
}

async/await for fetching data with spinner

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

Resources