Issue with fetching data - reactjs

iam new to React and trying to show data from API,
It works at first but after reload i got error " Cannot read properties of undefined (reading 'length')",
any ideas what could it cause ?
thanks
code looks like this:
import React from "react";
import { useEffect, useState } from "react";
const options = {
//options for API call
};
const Ticket = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch(
"https://daily-betting-tips.p.rapidapi.com/daily-betting-tip-api/items/daily_betting_coupons?sort=-id",
options
)
.then((res) => res.json())
.then((data) => {
setData(data);
})
.catch((err) => {
console.log(err);
})
.finally(() => {
setLoading(false);
});
}, []);
if (loading) {
return <p>data is loading...</p>;
}
return (
<div>
<h1>length: {data.data.length}</h1>
<h2></h2>
</div>
);
};
export default Ticket;

You are getting this error because you have data state which is an array but in return you are trying to access data key from the state's data array, which is not there hence it returns the undefined and then you are trying to access the length from undefined.
Instead of data.data.length just use data.length

Use this code. I edited your code. Add a condition when set your data variable
if(data.data) {
setData(data.data)
}
And also change this line
<h1>length: {data.data.length}</h1>
To
<h1>length: {data.length}</h1>
Here is the full code
import React from "react";
import { useEffect, useState } from "react";
const options = {
//options for API call
};
const Ticket = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch(
"https://daily-betting-tips.p.rapidapi.com/daily-betting-tip-api/items/daily_betting_coupons?sort=-id",
options
)
.then((res) => res.json())
.then((data) => {
if (data.data) {
setData(data.data);
}
})
.catch((err) => {
console.log(err);
})
.finally(() => {
setLoading(false);
});
}, []);
if (loading) {
return <p>data is loading...</p>;
}
return (
<div>
<h1>length: {data.length}</h1>
<h2>Hello world</h2>
</div>
);
};
export default Ticket;

Related

Why I have error when use Api from DummyJSON

Run code and I get "Consider adding an error boundary to your tree to customize error handling behavior.: How can I fix it
import React, { useState, useEffect } from "react";
const BASE_URL = "https://dummyjson.com/products";
export default function App() {
const [product, setProduct] = useState([]);
useEffect(() => {
fetch(`${BASE_URL}`)
.then((res) => res.json())
.then((res) => {
setProduct(res);
})
// .then(console.log)
}, []);
return (
<ul>
{product.map((item) => (
<li key={item.id}>
{item.products.brand}
</li>
))}
</ul>
);
}
I tried change another API and it dose not have error
The problem is what type of data in file json. In this case data' type is object so I can not use map to render product.
first I change useState hook from empty array to object
const [product, setProduct] = useState({ products: [] });
then fetch data from api
const fetchData = () => {
fetch(`${BASE_URL}?limit=5`)
.then((res) => res.json())
.then((data) => {
setProduct(data);
});
};
useEffect(() => {
fetchData();
});
after fecth data and set to state I render it

How do I show my get request data in frontend?

This is my code to show the get request data in frontend
import React, { useEffect, useState } from "react";
import axios from "axios";
const Users = () => {
const [users, setusers] = useState({ collection: [] });
const [Error, setError] = useState();
useEffect(() => {
axios
.get("http://127.0.0.1:5000/users/users-list")
.then((response) => {
console.log(response.data);
// console.log(response.status);
// console.log(response.statusText);
// console.log(response.headers);
// console.log(response.config);
setusers({ collection: response.data });
return response.data;
})
.catch((error) => {
console.log({ Error: error });
setError(error);
// return error;
});
}, []);
return (
<div>
{users.collection.length > 0 &&
users.collection.map((element, i) => {
return (
<div key={i}>
{element.Name}‑{element.Email}
‑{element.Message}
</div>
);
})}
{Error && <h2>{Error}</h2>}
</div>
);
};
export default Users;
As you can see in the following code I am trying to display my get data in the browser web page .
but its is not displaying in the browser but showing in console.log()
First of all dont make variable starts with capital letter as you have used Error (which refers to Error class in JavaScript) in useState.
You can show component with different state as follows:
const [isLoading, setIsLoading] = useState(false);
const [users, setUsers] = useState([]);
const [error, setError] = useState("");
useEffect(() => {
setIsLoading(true);
axios.get("http://127.0.0.1:5000/users/users-list")
.then((res => {
setUsers(res.data);
setIsLoading(false);
})
.catch(err => {
setError(err.response.data);
setIsLoading(false);
}
},[]);
if (isLoading) {
return <LoadingComponent />
}
if (error !== "") {
return <h1>{error}</h1>
}
if (users.length < 1) {
return <h1>There is no user.</h1>
}
return <div>
{users.collection.map((element, i) => {
return (
<div key={i}>
{element.Name}‑{element.Email}
‑{element.Message}
</div>
);
})}
</div>
You implementation ok it's work with [{"Name":"T","Email":"t#email.com","Message":"T user"}] API response format. Just check what is API response in your end, It should render the results.
I have notice catch block you have to set error message instead of Err object
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const Users = () => {
const [users, setusers] = useState({ collection: [] });
const [Error, setError] = useState('');
useEffect(() => {
axios
.get('https://63a0075424d74f9fe82c476c.mockapi.io/api/collection/Test')
.then((response) => {
console.log(response.data);
// console.log(response.status);
// console.log(response.statusText);
// console.log(response.headers);
// console.log(response.config);
setusers({ collection: response.data });
})
.catch((error) => {
console.log({ Error: error });
setError('Something went wrong');
// return error;
});
}, []);
return (
<div>
{users.collection.length > 0 &&
users.collection.map((element, i) => {
return (
<div key={i}>
{element.Name}‑{element.Email}
‑{element.Message}
</div>
);
})}
{Error && <h2>{Error}</h2>}
</div>
);
};
export default Users;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I tested your code (with a different API) and could not find any issues. As you can see in the codesandbox, the values appear on the screen:
https://codesandbox.io/s/wonderful-ganguly-5ecbid?file=/src/App.js
I noticed that you capitalised the object properties, Name, Email and Message. Perhaps this caused you the issue. You will need to check the console logged object to see whether the properties are capitalised or not. Usually, they will not be. So you would call them like this: element.name, element.email and element.message.
I guess your response data is maybe your problem. I don't know what is your response but it must be array.
I have replace the axios url with some other fake urls and it worked. but remember that the user.collection must be array. Therefor, you need to make sure that response.data is array. Otherwise, you need to set response.data as array in user.collection.
import React, { useEffect, useState } from "react";
import axios from "axios";
const Users = () => {
const [users, setusers] = useState({ collection: [] });
const [Error, setError] = useState();
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => {
console.log(response.data);
setusers({ collection: [response.data] });
return response.data;
})
.catch((error) => {
console.log({ Error: error });
setError(error);
// return error;
});
}, []);
return (
<div>
{users.collection.length > 0 &&
users.collection.map((element, i) => {
return <div key={i}>{element.title}</div>;
})}
{Error && <h2>{Error}</h2>}
</div>
);
};
export default Users;

How can I use response data from an API to call another different API request in React UseEffect?

I was wondering how can I call an API that requires data from other API. In my case I want to get the coordinates; lattitude and longitute and make another API call with these to retrieve the information I need.
Here is App.js file
import React from 'react';
import './App.css';
import CityInput from "./components/CityInput";
import {Container} from 'react-bootstrap';
import UseFetch from "./hooks/useFetch";
import {API_BASE_URL, API_KEY} from "./apis/config";
import WeatherList from "./components/WeatherList";
const App = () => {
const {data, error, isLoading, setUrl} = UseFetch();
const getContent = () => {
if(error) return <h2>Error when fetching: {error}</h2>
if(!data && isLoading) return <h2>LOADING...</h2>
if(!data) return null;
return <WeatherList weathers={data.list}/>
};
return (
<Container className="App">
{/* user types a city and clicks search*/}
<CityInput onSearch={(city) => setUrl(`${API_BASE_URL}/data/2.5/forecast?q=${city}&appid=${API_KEY}&units=metric`)} />
{getContent()}
</Container>
);
}
export default App;
and here is my UseFetch.js file
import {useState, useEffect} from 'react';
import {API_BASE_URL, API_KEY} from "../apis/config";
const UseFetch = (initialUrl) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(null);
const [url, setUrl] = useState(initialUrl);
useEffect(() => {
if(!url) return;
setIsLoading(true);
setData(null);
setError(null);
fetch(url)
.then((response) => response.json())
.then((data) => {
setIsLoading(false);
if(data.cod >= 400) {
setError(data.message);
return;
}
setData(data);
console.log(data);
console.log(data.city.coord.lat);
console.log(data.city.coord.lon);
})
.catch((error) => {
setIsLoading(false);
setError(error);
});
//
// console.log('HIIIIII'+ data);
// fetch(`${API_BASE_URL}/data/2.5/onecall?lat=${data.city.coord.lat}&lon=${data.city.coord.lon}&exclude=minutely&appid=${API_KEY}`)
// .then((response) => response.json())
// .then((data2) => {
// setIsLoading2(false);
// setData2(data2);
// console.log(data2);
// })
// .catch((error2) => {
// setIsLoading2(false);
// setError2(error);
// });
}, [url]);
return { data, error, isLoading, setUrl};
};
export default UseFetch;
I want to retrieve lantitude and lontitude so i can make another fetch
fetch(`${API_BASE_URL}/data/2.5/onecall?lat=${data.city.coord.lat}&lon=${data.city.coord.lon}&exclude=minutely&appid=${API_KEY}`)
But this doesnt seem to work.
I'm using these API for reference:
https://openweathermap.org/forecast5
https://openweathermap.org/api/one-call-api
Call it right after the Initial Api sends back the response for example:
fetch(APIURL)
.then(response => {
/** do any operations using received response data **/
/** Calling second api **/
fetch(API_URL_ + response.data.url)
.then(data => {
setData(data)
})
.catch(error)
}).catch(error)
UseFetch.js
import {useState, useEffect} from 'react';
import {API_BASE_URL, API_KEY} from "../apis/config";
const UseFetch = (initialUrl) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(null);
const [url, setUrl] = useState(initialUrl);
useEffect(() => {
if(!url) return;
setIsLoading(true);
setData(null);
setError(null);
fetch(url)
.then((response) => response.json())
.then((data) => {
setIsLoading(false);
if(data.cod >= 400) {
setError(data.message);
return;
}
setData(data);
console.log(data);
console.log(data.city.coord.lat);
console.log(data.city.coord.lon);
console.log('HIIIIII'+ data);
fetch(`${API_BASE_URL}/data/2.5/onecall?lat=${data.city.coord.lat}&lon=${data.city.coord.lon}&exclude=minutely&appid=${API_KEY}`)
.then((response) => response.json())
.then((data2) => {
setIsLoading2(false);
setData2(data2);
console.log(data2);
})
.catch((error2) => {
setIsLoading2(false);
setError2(error);
});
})
.catch((error) => {
setIsLoading(false);
setError(error);
});
}, [url]);
return { data, error, isLoading, setUrl};
};
export default UseFetch;
You can try to chain another .then() after save the first response in a variable??Something like:
let anyVariable;
fetch(url)
.then((response) => response.json())
.then((data) => {
setIsLoading(false);
if(data.cod >= 400) {
setError(data.message);
return;
}
anyVariable = data.city.coord
})
.then(() => {
fetch(`${API_BASE_URL}/data/2.5/onecall?lat=${anyVariable.lat}&lon=${anyVariable.lon}&exclude=minutely&appid=${API_KEY}`)
.then((response) => response.json())
.then((data2) => {
setIsLoading2(false);
setData2(data2);
console.log(data2);
})
})
Any way i think it will be cleaner and better performing to use axios and async await. Also notice that useState is asynchronous.

How to make React Hooks return an array

I have some hard-coded data in my React app which I would now like to fetch from an API. I would like to preserve the array-like format because that is how the data is used down the road.
My original component:
import moment from 'moment';
const INITIAL_STATE = {
articles:
[
{
title: 'Guerrilla Public Service Redux (2017)',
tag0: 'story',
points: 421,
created_at: moment('2020-05-27T16:05:32.000Z'),
author: 'DerWOK',
},
{
title: 'Build Yourself a Redux',
tag0: 'story',
points: 395,
created_at: moment('2020-05-27T16:05:32.000Z'),
author: 'jdeal',
},
],
};
function Articles (state = INITIAL_STATE) {
return state;
}
export default Articles;
This is how I imagine it to work but what should be in the return() below is a mystery to me:
import moment from 'moment';
function Articles() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch("https://hn.algolia.com/api/v1/search?query=redux")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.items);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
{items.map(item => (
???)
);
}
}
export default Articles;
Edit: I do have the list component to render the data. I just need to return the data in acceptable form.
This looks good to me except for not returning JSX from the component In the last use case and in the other cases you are. Try this
return (
<ul>
{items.map((item, index) => <li key={index}>{item.title} </li> )}
</ul>
);
Note if your items have unique ids it is better practice to use those as the key instead of the index.
EDIT :
Here is an example of how you could make a hook to use in articles
function useGetArticles() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch('https://hn.algolia.com/api/v1/search?query=redux')
.then((res) => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.items);
},
(error) => {
setIsLoaded(true);
setError(error);
}
);
}, []);
return [items, isLoaded, error];
}
function Articles() {
const [items, isLoaded, error] = useGetArticles();
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item.title} </li>
))}
</ul>
);
}
}
If all you are trying to do is maintain Articles as a function, you cannot without making it a promise(which is redundant as fetch is doing what you need to already).
And you can write custom hook as you are trying to, but then Articles (must be named useArticles as react needs it to) cant be a function the way you want and you will be complicating for no useful reason.
My suggestion would be to simply move the useEffect, and other state hooks to your parent component like this (Note App is your parent component not Articles):
import React, {useState, useEffect} from 'react';
const App = () => {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
Articles()
.then(res => {
setIsLoaded(true);
setItems(res.hits);
setError(false);
})
.catch(error => {
setIsLoaded(true);
setError(true);
})
// you are repeating then/catch again even though you were doing it already on fetch. Only good reason to do this is if you are reusing the api calls in other places.
}, [])
return (
<React.Fragment>
Test
<ul>
{items.map((item, index) => <li key={index}>{item.title} </li> )}
</ul>
</React.Fragment>
);
}
function Articles() {
// too much code just to hide it inside a function
return new Promise((resolve, reject) =>
fetch("https://hn.algolia.com/api/v1/search?query=redux")
.then(res => resolve(res.json()))
.catch(error => reject(error))
);
}
export default App;
Here is the hooks way to reformat your code. But that itself doesn't mean its better:
import React, {useState, useEffect} from 'react';
const App = () => {
// getting all useful state from the hook
const {error, isLoaded, items, fetchItems} = useGetArticles();
useEffect(() => {
fetchItems(); // calling the method that ask for new data and loads item in the hook's state
})
return (
<React.Fragment>
<ul>
{items.map((item, index) => <li key={index}>{item.title} </li> )}
</ul>
</React.Fragment>
);
}
const useGetArticles = () => {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
const fetchItems = () => {
fetch("https://hn.algolia.com/api/v1/search?query=redux")
.then(res => res.json())
.then(res => {
setIsLoaded(true);
setItems(res.hits);
setError(false);
})
.catch(error => {
setIsLoaded(true);
setError(true);
})
};
// only return useful states and functions that main component can use
return {error, isLoaded, items, fetchItems}
}
export default App;

Putting fetch function in a separate component

I'm trying to take out the fetchImages function from the following component and put it inside a new component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import UnsplashImage from './UnsplashImage';
const Collage = () => {
const [images, setImages] = useState([]);
const [loaded, setIsLoaded] = useState(false);
const fetchImages = (count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey =
'<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
setImages([...images, ...res.data]);
setIsLoaded(true);
});
};
useEffect(() => {
fetchImages();
}, []);
return (
<div className="image-grid">
{loaded
? images.map(image => (
<UnsplashImage
url={image.urls.regular}
key={image.id}
alt={image.description}
/>
))
: ''}
</div>
);
};
export default Collage;
For this, I created a new component called api.js, removed the entire fetchImage function from the above component and put it in to api.js like this:
api.js
const fetchImages = (count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey =
'<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
setImages([...images, ...res.data]);
setIsLoaded(true);
});
};
export default fetchImages;
Next I took setIsLoaded(true); from api.js and paste it inside Collage component like this:
useEffect(() => {
fetchImages();
setIsLoaded(true);
}, []);
Now I can import fetchImages in to Collage component.
However, I don't know what should I do with this line inside the fetchImages function? This needs to go to Collage component, but res.data is not defined inside Collage component.
setImages([...images, ...res.data]);
How should I handle it?
There is many way to do that, but in your case.
You should use
const fetchImages = (afterComplete, count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey = '<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
afterComplete(res.data);
});
};
export default fetchImages;
And in your Collage component:
const afterComplete = (resData) =>{
setImages([...images, ...resData]);
setIsLoaded(true);
}
useEffect(() => {
fetchImages(afterComplete);
}, []);
What you can do is create a custom hook ( sort of like a HOC)... Since I don't have an unsplash API key I'll give you an example with a different API but the idea is the same:
Here is your custom hook:
import { useState, useEffect } from 'react';
export const useFetch = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchUser = async () => {
const response = await fetch(url);
const data = await response.json();
const [user] = data.results;
setData(user);
setLoading(false);
};
useEffect(() => {
fetchUser();
}, []);
return { data, loading };
};
Here is how you can use it in your component:
import { useFetch } from './api';
const App = () => {
const { data, loading } = useFetch('https://api.randomuser.me/');
return (
<div className="App">
{loading ? (
<div>Loading...</div>
) : (
<>
<div className="name">
{data.name.first} {data.name.last}
</div>
<img className="cropper" src={data.picture.large} alt="avatar" />
</>
)}
</div>
);
};
Here is a live demo: https://codesandbox.io/s/3ymnlq59xm

Resources