I'm trying to fetch data from an api.
So far I have tried to fetch data using an api. I have been using this website: https://www.geeksforgeeks.org/how-to-use-the-javascript-fetch-api-to-get-data/
It currently console.logs my array but it does not show up in the table rows that i have created.
I think i might have created the rows wrong in my html, but i cant figure how they should have been set up otherwise. Please show a small example or what to google if that is what is wrong.
The current error message i get is
Uncaught (in promise) TypeError: data.list is not iterable
at show (news.js:37:21)
at getapi (news.js:17:2)
My javascript looks like this:
// api url
const api_url =
"https:newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=xxxxxxxxx";
// Defining async function
async function getapi(url) {
// Storing response
const response = await fetch(url);
// Storing data in form of JSON
var data = await response.json();
console.log(data);
if (response) {
hideloader();
}
show(data);
}
// Calling that async function
getapi(api_url);
// Function to hide the loader
function hideloader() {
document.getElementById('loading').style.display = 'none';
}
// Function to define innerHTML for HTML table
function show(data) {
let tab =
`<tr>
<th>Author</th>
<th>Title</th>
<th>Description</th>
<th>Url</th>
</tr>`;
// Loop to access all rows
for (let r of data.list) {
tab += `<tr>
<td>${r.author}</td>
<td>${r.title}</td>
<td>${r.description}</td>
<td>${r.url}</td>
</tr>`;
}
// Setting innerHTML as tab variable
document.getElementById("news").innerHTML = tab;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="news.js"></script>
<link rel="stylesheet" href="test.css" />
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- Here a loader is created which
loads till response comes -->
<div class="d-flex justify-content-center">
<div class="spinner-border"
role="status" id="loading">
<span class="sr-only">Loading...</span>
</div>
</div>
<h1>News</h1>
<!-- table for showing data -->
<table id="news"></table>
</body>
</html>
You need to handle the response correctly.
Working example:
// api url
const api_url = 'https://jsonplaceholder.typicode.com/comments';
// Defining async function
async function getapi(url) {
await fetch(url)
.then((response) => response.json())
.then((data) => {
console.log(data)
hideloader();
show(data)
});
}
// Calling that async function
getapi(api_url);
// Function to hide the loader
function hideloader() {
document.getElementById('loading').style.display = 'none';
}
// Function to define innerHTML for HTML table
function show(data) {
let tab =
`<tr>
<th>id</th>
<th>Name</th>
<th>Body</th>
<th>Email</th>
</tr>`;
// Loop to access all rows
for (let r of data) {
tab += `<tr>
<td>${r.id}</td>
<td>${r.name}</td>
<td>${r.body}</td>
<td>${r.email}</td>
</tr>`;
}
// Setting innerHTML as tab variable
document.getElementById("news").innerHTML = tab;
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="news.js"></script>
<link rel="stylesheet" href="test.css" />
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- Here a loader is created which
loads till response comes -->
<div class="d-flex justify-content-center">
<div class="spinner-border"
role="status" id="loading">
<span class="sr-only">Loading...</span>
</div>
</div>
<h1>News</h1>
<!-- table for showing data -->
<table id="news"></table>
</body>
</html>
This is what i ended up doing
const url = "xxxxxxx"
fetch(url)
.then((response) => response.json())
.then((data) => {
console.log(data)
let title = document.getElementById("title");
title.innerText = data.articles[0].title;
let title1 = document.getElementById("title1");
title1.innerText = data.articles[1].title;
let title2 = document.getElementById("title2");
title2.innerText = data.articles[2].title;
let title3 = document.getElementById("title3");
title3.innerText = data.articles[3].title;
let title4 = document.getElementById("title4");
title4.innerText = data.articles[4].title;
let title5 = document.getElementById("title5");
title5.innerText = data.articles[5].title;
});
And then just a p tag looking like this:
<p id="title1"></p>
And so on
Related
Ok I'm trying to consume a simple API and loop through all the data I received and display it on html. I'm stumbling on something simple and I cannot figure out where I'm making the mistake.
Currently I get the data with fetch, however when I try to display that data on html I'm just getting the very first object in the array not all the objects.
I will like to get a list of all the posts in my html.
Thanks in advance
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Document</title>
</head>
<body>
<div class="d-flex justify-content-center">
<div class="spinner-border" role="status" id="loading">
<span class="sr-only">Loading...</span>
</div>
</div>
<h1>List of Posts</h1>
<section id="section">
<ul id='posts'></ul>
</section>
</body>
<script>
const API_URL = `https://jsonplaceholder.typicode.com`
async function fetchPosts() {
const response = await fetch(`${API_URL}/posts`)
let data = await response.json()
// console.log(data)
if (response) {
hideloader();
}
show(data)
}
function hideloader() {
document.getElementById('loading').style.display = 'none';
}
function show(data) {
console.log(data, ' inside show')
const ul = document.getElementById('posts')
const list = document.createDocumentFragment();
let li = document.createElement('li');
let title = document.createElement('h1');
let body = document.createElement('p')
data.map(function (post) {
title.innerHTML = `${post.title}`;
body.innerHTML = `${post.body}`;
li.appendChild(title);
li.appendChild(body);
list.appendChild(li);
// console.log(list)
// console.log(li)
})
ul.appendChild(list);
}
fetchPosts()
</script>
</html>
In the show(data) function, when you map the data, the title.innerHTML and body.innerHTML are reassigned constantly.
You should create the list title and body elements in the iteration.
function show(data) {
const ul = document.getElementById('posts');
const list = document.createDocumentFragment();
data.map(function (post) {
let li = document.createElement('li');
let title = document.createElement('h1');
let body = document.createElement('p');
title.innerHTML = `${post.title}`;
body.innerHTML = `${post.body}`;
li.appendChild(title);
li.appendChild(body);
list.appendChild(li);
});
ul.appendChild(list);
}
I'm a beginner when it comes to coding and the biggest issue I have is to understand WHY something doesn't work (how to diagnose an error). I tried to combine what I learned from Colt Steele on Udemy with fetch API and so far, I've managed to make it work to list the NAMES of the movies when you search, but when I try to display the IMAGES, they seem to not work and it seems like it's trying to load them from my PC rather than from the TVMaze API. Here's my code:
function searchShow(query) {
const url = `https://api.tvmaze.com/search/shows?q=${query}`;
fetch(url)
.then(response => response.json())
.then((jsonData) => {
const resultsNames = jsonData.map(element => element.show.name);
const resultsImages = jsonData.map(e => e.show.image);
console.log(resultsNames);
renderResults(resultsNames);
console.log(resultsImages);
renderImages(resultsImages);
document.getElementById("errorMessage").innerHTML = "";
})
.catch((error) => {
document.getElementById("errorMessage").innerHTML = error;
})
}
function renderResults(resultsNames) {
const list = document.getElementById("resultsList");
list.innerHTML = "";
resultsNames.forEach(result => {
const element = document.createElement("li");
element.innerText = result;
list.appendChild(element);
});
}
function renderImages(resultsImages) {
const list2 = document.getElementById("imagesDisplay");
list2.innerHTML = "";
resultsImages.forEach(result => {
const imgShow = document.createElement("IMG");
imgShow.src = result;
list2.appendChild(imgShow);
})
}
let searchTimeoutToken = 0;
window.onload = () => {
const searchFieldElement = document.getElementById("searchField")
searchFieldElement.onkeyup = (event) => {
clearTimeout(searchTimeoutToken);
searchTimeoutToken = setTimeout(() => {
searchShow(searchFieldElement.value);
}, 250);
};
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TV Show</title>
</head>
<body>
<h1>TV Search</h1>
<input type="text" id="searchField" placeholder="Search a TV Show...">
<ul id="resultsList"></ul>
<ul id="imagesDisplay"></ul>
<div id=" errorMessage">
</div>
<script src="script.js"></script>
</body>
</html>
Can you please help me understand why is this not working and also, how can I make it display in a list like this:
-Name of the show
-Image of the show
-2nd name of the 2nd show
-2nd image of the 2nd show
etc.
Thank you in advance!
If you look at your images, you will see the src as [object Object] instead of the url to your image. You need to access the property of your object, in this case there's a few to choose from that represent different sized images.
I've modified your snippet to get what you want.
function searchShow(query) {
const url = `https://api.tvmaze.com/search/shows?q=${query}`;
fetch(url)
.then(response => response.json())
.then((jsonData) => {
let shows = jsonData.map(element => element.show);
renderShows(shows);
document.getElementById("errorMessage").innerHTML = "";
})
.catch((error) => {
document.getElementById("errorMessage").innerHTML = error;
})
}
function renderShows(shows) {
const list = document.getElementById("resultsList");
list.innerHTML = "";
shows.forEach(show => {
const element = document.createElement("li");
const img = document.createElement("img");
const text = document.createElement("span");
img.src = show.image.original;
text.innerText = show.name;
element.appendChild(text);
element.appendChild(img);
list.appendChild(element);
});
}
let searchTimeoutToken = 0;
window.onload = () => {
const searchFieldElement = document.getElementById("searchField")
searchFieldElement.onkeyup = (event) => {
clearTimeout(searchTimeoutToken);
searchTimeoutToken = setTimeout(() => {
searchShow(searchFieldElement.value);
}, 250);
};
}
img {
max-width: 100px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TV Show</title>
</head>
<body>
<h1>TV Search</h1>
<input type="text" id="searchField" placeholder="Search a TV Show...">
<ul id="resultsList"></ul>
<ul id="imagesDisplay"></ul>
<div id="errorMessage">
</div>
<script src="script.js"></script>
</body>
</html>
I want to change my target div variable's display property from none to block.
const userListEl = document.getElementById('user-list').innerHTML;
const template = Handlebars.compile(userListEl);
const targetDiv = document.getElementById("userDetail");
fetch("https://5dc588200bbd050014fb8ae1.mockapi.io/assessment")
.then(response => response.json())
.then((data) => {
var userData = template({ usersList: data })
document.getElementById('test').innerHTML = userData;
}).catch((error) => {
console.log(error)
})
this is my function
const hanldeClick = () => {
if (targetDiv.style.display === "none") {
targetDiv.style.display = "block";
} else {
targetDiv.style.display = "none";
}
};
#userDetail {
display: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Exercise 1</title>
</head>
<body>
<div id="test"></div>
<script id="user-list" type="text/x-handlebars-template">
<ul class="people_list"> {{#each usersList}}
<li>
<p class="">{{name}}</p>
<img src={{avatar}} alt={{name}}>
<div id="userDetail">
<p>Id: {{id}}</p>
<p>Created at: {{createdAt}}</p>
</div>
<button onclick="hanldeClick()"> Detail </button>
</li>
{{/each}}
</ul>
</script>
<script type="text/javascript" src="app.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars#latest/dist/handlebars.js"></script>
</body>
I want to change the display value none to block in an onclick handler, but when the button is clicked I get this error:
TypeError: Cannot read properties of null (reading 'style')
Can someone help me?
targetDiv is null when you click the "Detail" button and trigger the handleClick function because there is no DOM element with the id userDetail when you invoke document.getElementById("userDetail");.
To verify this, you could log the value of targetDiv immediately after your assignment:
const targetDiv = document.getElementById("userDetail");
console.log(targetDiv); // null
Why is <div id="userDetail"> not a DOM element?
Because it is a part of a template within a script tag. The type="text/x-handlebars-template" attribute you have added to your script block is basically telling the browser to ignore what it contains. This is what allows you to add arbitrary content, like the mustaches understood by the Handlebars library. For more on this: see Explanation of <script type = "text/template"> ... </script>
In order for your code to reference the DOM element with id userDetail, you will need to get it from the DOM after you have injected your template-rendered HTML into document - ie., after you set the innerHTML of #test to userData:
let targetDiv = null;
fetch("https://5dc588200bbd050014fb8ae1.mockapi.io/assessment")
.then(response => response.json())
.then((data) => {
var userData = template({ usersList: data })
document.getElementById('test').innerHTML = userData;
targetDiv = document.getElementById("userDetail");
}).catch((error) => {
console.log(error)
})
I have created a fiddle for your reference.
Additionally, even with this fix, I think you are going to find that your code does not work as you intend. It looks like you want handleClick to toggle a sibling <div> - and that this should be the effect for each <li> in your <ul>. However, your document.getElementById("userDetail") will return only the first element with id userDetail, so no matter which "Detail" <button> is clicked, only the first <li>'s detail will be toggled. You will need to find a way to specify to your handler which detail to toggle.
This question already has answers here:
addEventListener Executing Before Being Clicked
(2 answers)
Closed 2 years ago.
Can anyone help me with that- I am using fetch api here and this is linked to a button ,here I used fetch api for a get request, but the problem is that without clicking the button ,my data is fetched from the api.
When I clicked the button to fetch data first time, it works perfectly but after that on reload my data is fetched automatically without clicking button. what's the problem here and how to fix it?
easyhttp.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>easy http</title>
</head>
<body>
<ol id="getRequestData">
</ol>
<button id="btn1">get</button>
<button id="btn2">post</button>
<button id="btn3">put</button>
<button id="btn4">delete</button>
<script src="easyHttpWithFetch.js"></script>
</body>
</html>
easyHttpWithFetch.js
document.getElementById("btn1").addEventListener("click",get("https://jsonplaceholder.typicode.com/posts"));
function get(url){
fetch(url)
.then(response => response.json())
.then((data) => {
let str = "";
data.forEach(element => {
str += '<li><ol type="a">';
for (const key in element) {
if (Object.hasOwnProperty.call(element, key)) {
const value = element[key];
str+= `<li>${value}</li>`;
}
}
str += '</ol></li>';
let getRequestData = document.getElementById("getRequestData");
getRequestData.innerHTML = str;
});
}).catch((err) => {
console.log(err);
});
}
The second parameter of the addEventListener() is the function name that we want to call when the click occurs. But you are currently trying to execute the get() method by passing the url parameter immediately.
That's why get() is first called initially when btn1 is attached to the click event.
To fix this, try to use the arrow function.
document.getElementById("btn1").addEventListener("click", () => get("https://jsonplaceholder.typicode.com/posts"));
function get(url) {
fetch(url)
.then(response => response.json())
.then((data) => {
let str = "";
data.forEach(element => {
str += '<li><ol type="a">';
for (const key in element) {
if (Object.hasOwnProperty.call(element, key)) {
const value = element[key];
str += `<li>${value}</li>`;
}
}
str += '</ol></li>';
let getRequestData = document.getElementById("getRequestData");
getRequestData.innerHTML = str;
});
}).catch((err) => {
console.log(err);
});
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>easy http</title>
</head>
<body>
<ol id="getRequestData">
</ol>
<button id="btn1">get</button>
<button id="btn2">post</button>
<button id="btn3">put</button>
<button id="btn4">delete</button>
</body>
</html>
I would like to use a single fetch request when the page is loaded and when some text is put in the input field. For example, there is page A with an input field, the user will type something and then will be redirected to page B where fetch request will be called immediately and results will be returned. Then, when the user is on page B and if wants to change search criteria I would like to create a new fetch request and get data back. I'm able to get data back but then if the user clicks on result console log is fired twice instead of once. Any idea how to overcome this?
thanks
Here is my setup.
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text" name="search" value=1 id="search">
<button type="button" name="button" id="startSearch">Search</button>
<table id="results"></table>
<script type="text/javascript">
let searchString = document.getElementById('search')
let resultDiv = document.getElementById('results')
fetch('https://jsonplaceholder.typicode.com/users/'+searchString.value)
.then(response => response.json())
.then(json => {
resultDiv.innerHTML = ''
let tr = document.createElement('tr')
let td = document.createElement('td')
let text = document.createTextNode(json.name)
td.appendChild(text)
tr.appendChild(td)
resultDiv.appendChild(tr)
})
resultDiv.addEventListener('click', (e) => {
console.log('click1')
console.log(e.target.innerText)
})
</script>
</body>
</html>
what I have also tried is this
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text" name="search" value=1 id="search">
<button type="button" name="button" id="startSearch">Search</button>
<table id="results"></table>
<script type="text/javascript">
let searchString = document.getElementById('search')
let resultDiv = document.getElementById('results')
fetch('https://jsonplaceholder.typicode.com/users/'+searchString.value)
.then(response => response.json())
.then(json => {
resultDiv.innerHTML = ''
let tr = document.createElement('tr')
let td = document.createElement('td')
let text = document.createTextNode(json.name)
td.appendChild(text)
tr.appendChild(td)
resultDiv.appendChild(tr)
})
resultDiv.addEventListener('click', (e) => {
console.log(e.target)
})
searchString.addEventListener('input', (e) => {
fetch('https://jsonplaceholder.typicode.com/users/'+searchString.value)
.then(response => response.json())
.then(json => {
resultDiv.innerHTML = ''
let tr = document.createElement('tr')
let td = document.createElement('td')
let text = document.createTextNode(json.name)
td.appendChild(text)
tr.appendChild(td)
resultDiv.appendChild(tr)
})
})
resultDiv.addEventListener('click', (e) => {
console.log(e.target)
})
</script>
</body>
</html>
but then when I click on td element I get it twice, and it is clear to me why. So, my question is can I have somehow single fetch request which is working on page load and on the input event listener
Your code does not fire the console.log event twice, it's fired only once, as expected.
Open jsfiddle.net
Paste your code in the html panel
Open the browser console
Run the code
Click the search result
console.log is fired only once