Working on modifying a project I'm learning from.
The initial project is to create a sort of infinitely scrolling Twitter Clone. Posts were grabbed from https://jsonplaceholder.typicode.com/ api, and I'm trying to add another level by bringing in images from another image placeholder API.
I'm a noob, so I was able to successfully grab data from both API's and populate the DOM. Trouble is, with my current structure, I'm repeating placeholder images as what I'm grabbing from the placeholder api is in 5 object blocks. Could use some help in making sure every post has a different photo. Thanks, total newb here. Wondering if I should just be populating an array of objects and working from there, but maybe there's a shortcut I'm missing?
const postsContainer = document.getElementById("posts-container");
const loading = document.querySelector(".loader");
const filter = document.getElementById("filter");
let limit = 5;
let page = 1;
//Fetch Posts from API
async function getPosts() {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}`
);
const data = await res.json();
return data;
}
//Fetch Photos from another API
async function getPhoto(photo) {
const res = await fetch(`https://randomuser.me/api`);
const data = await res.json();
photo = data.results[0].picture.thumbnail;
return photo;
}
//Show items in DOM
async function showPosts() {
const posts = await getPosts();
const pic = await getPhoto();
posts.forEach((post) => {
const postEl = document.createElement("div");
postEl.classList.add("post");
postEl.innerHTML = `
<div class="number">
<img class="profile-pic" src="${pic}" alt="user photo" />
</div>
<div class="post-info">
<h2 class="post-title">${post.title}</h2>
<p class="post-body">
${post.body}
</p>
</div>
</div>
`;
postsContainer.appendChild(postEl);
});
}
//Show loader and fetch more posts
function showLoading() {
loading.classList.add("show");
setTimeout(() => {
loading.classList.remove("show");
setTimeout(() => {
page++;
showPosts();
}, 1000);
}, 300);
}
//Show initial posts
showPosts();
window.addEventListener("scroll", () => {
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
if (scrollTop + clientHeight >= scrollHeight - 5) {
showLoading();
}
});
GitHub Repo: https://github.com/unsubstantiated-Script/infinite-scroller
TIA
Problem solved. Needed to move the pic variable inside my forEach loop. That and async the forEach loop.
Related
I'm trying to make a page that gets picture from a server and once all pictures are downloaded display them, but for some reason the page doesn't re-render when I update the state.
I've seen the other answers to this question that you have to pass a fresh array to the setImages function and not an updated version of the previous array, I'm doing that but it still doesn't work.
(the interesting thing is that if I put a console.log in an useEffect it does log the text when the array is re-rendered, but the page does not show the updated information)
If anyone can help out would be greatly appreciated!
Here is my code.
export function Profile() {
const user = JSON.parse(window.localStorage.getItem("user"));
const [imgs, setImages] = useState([]);
const [num, setNum] = useState(0);
const [finish, setFinish] = useState(false);
const getImages = async () => {
if (finish) return;
let imgarr = [];
let temp = num;
let filename = "";
let local = false;
while(temp < num+30) {
fetch("/get-my-images?id=" + user.id + "&logged=" + user.loggonToken + "&num=" + temp)
.then(response => {
if(response.status !== 200) {
setFinish(true);
temp = num+30;
local = true;
}
filename = response.headers.get("File-Name");
return response.blob()
})
.then(function(imageBlob) {
if(local) return;
const imageObjectURL = URL.createObjectURL(imageBlob);
imgarr[temp - num] = <img name={filename} alt="shot" className="img" src={imageObjectURL} key={temp} />
temp++;
});
}
setNum(temp)
setImages(prev => [...prev, ...imgarr]);
}
async function handleClick() {
await getImages();
}
return (
<div>
<div className="img-container">
{imgs.map(i => {
return (
i.props.name && <div className="img-card">
<div className="img-tag-container" onClick={(e) => handleView(i.props.name)}>{i}</div>
<div className="img-info">
<h3 className="title" onClick={() => handleView(i.props.name)}>{i.props.name.substr(i.props.name.lastIndexOf("\\")+1)}<span>{i.props.isFlagged ? "Flagged" : ""}</span></h3>
</div>
</div>
)
})}
</div>
<div className="btn-container"><button className="load-btn" disabled={finish} onClick={handleClick}>{imgs.length === 0 ? "Load Images" : "Load More"}</button></div>
</div>
)
}
I think your method of creating the new array is correct. You are passing an updater callback to the useState() updater function which returns a concatenation of the previous images and the new images, which should return a fresh array.
When using collection-based state variables, I highly recommend setting the key property of rendered children. Have you tried assigning a unique key to <div className="img-card">?. It appears that i.props.name is unique enough to work as a key.
Keys are how React associates individual items in a collection to their corresponding rendered DOM elements. They are especially important if you modify that collection. Whenever there's an issue with rendering collections, I always make sure the keys are valid and unique. Even if adding a key doesn't fix your issue, I would still highly recommend keeping it for performance reasons.
It is related to Array characteristics of javascript.
And the reason of the console log is related with console log print moment.
So it should be shown later updated for you.
There are several approaches.
const getImages = async () => {
... ...
setNum(temp)
const newImage = [...prev, ...imgarr];
setImages(prev => newImage);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => JOSN.parse(JSON.object([...prev, ...imgarr]);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => [...prev, ...imgarr].slice(0));
}
Maybe it could work.
Hope it will be helpful for you.
Ok the problem for me was the server was not sending a proper filename header so it was always null so the condition i.props.name was never true... lol sorry for the confusion.
So the moral of this story is, always make sure that it's not something else in your code that causes the bad behavior before starting to look for other solutions...
I have written a javascript to fetch API but it is not working can anyone please see that is there any error in my javascript. I have given some parts of my javascript which I think has error.
MY JAVASCRIPT
const redraw = () => {
resultEl.innerHTML = '';
const paged = pageResponse(results, getPageSize(), getCurrPage());
const contents = document.createElement('div');
contents.innerHTML = paged.map(record => `<div class='latestatus'><p class='copytxt'>${record.status}</p><div> <button class="copystatus btn">Copy</button></div></div>`).join('');
resultEl.append(contents);
};
//Fetch API
const retrieveAllStatus = async function() {
// write your asynchronous fetching here
// here we are making a network call to your api
const response = await fetch('https://goodman456.000webhostapp.com/api.php');
// then converting it to json instead of a readable stream
const data = await response.json();
// finally go over the array and return new object with renamed key
const results = data.map(val => ({val.status}));
return results;
}
Please just once visit the link below. It's my request.
https://goodman456.000webhostapp.com/api.php
I would make your work easy, My friends said that the below section
contents.innerHTML = paged.map(record => `<div class='latestatus'><p class='copytxt'>${record.status}</p><div> <button class="copystatus btn">Copy</button></div></div>`).join('');
resultEl.append(contents);
};
Especially the {$record.status} where I need the JSON data to be placed. My friend also said that there is something wrong in fetching API.
Please I have stucked here for a long time. And thanks in advance for those who answer this question.
It could be that I'm typing the wrong things in Google and can't get a good answer.
Is there a "svelte recommended" way to store the value of a GET result, so that, on every refresh or link switch, the result in the store is used in the component until a timeout (where the api is called again)?
My purpose is to fetch blogposts from an external API and show them in a list but not on every refresh, or link switch.
My code:
<script>
let posts = [];
onMount(async () => {
const res = await fetch(apiBaseUrl + "/blogposts");
posts = await res.json();
});
</script>
{#each posts as post}
<h5>{post.title}</h5>
{/each}
In pseudocode what i want:
if (store.blogposts.timeout === true){
onMount(...);
// renew component
}
you can use stores to achieve this. Initial page load fetch posts data from api and save in stores. Then use the posts data in further page mounts. Set timeout to true whenever you want to refresh data.
./stores.js
import {writable} from 'svelte/store';
export const posts = writable([]);
export const timeout = writable(false);
./posts.svelte
<script>
import {posts, timeout} from "./stores.js"
onMount(async () => {
if($posts.length<1 || $timeout == true){
const res = await fetch(apiBaseUrl + "/blogposts");
$posts = await res.json();
}
});
</script>
{#each $posts as post}
<h5>{post.title}</h5>
{/each}
When you refresh the page the posts in stores will clear. To avoid that use localstorage to cache data. pls check the below code.
./posts.svelte
<script>
let posts = [];
onMount(async () => {
posts = await getdata();
}
const getdata = async ()=>{
// set cache lifetime in seconds
var cachelife = 5000;
//get cached data from local storage
var cacheddata = localStorage.getItem('posts');
if(cacheddata){
cacheddata = JSON.parse(cacheddata);
var expired = parseInt(Date.now() / 1000) - cacheddata.cachetime > cachelife;
}
//If cached data available and not expired return them.
if (cacheddata && !expired){
return cacheddata.posts;
}else{
//otherwise fetch data from api then save the data in localstorage
const res = await fetch(apiBaseUrl + "/blogposts");
var posts = await res.json();
var json = {data: posts, cachetime: parseInt(Date.now() / 1000)}
localStorage.setItem('posts', JSON.stringify(json));
return posts;
}
}
{#each posts as post}
<h5>{post.title}</h5>
{/each}
svelte-query can help:
Svelte Query is often described as the missing data-fetching library for Svelte, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your Svelte applications a breeze.
note: svelte-query is abandoned and will be replaced with #tanstack/svelte-query
I am trying to scrape this page.
https://www.psacard.com/Pop/GetItemTable?headingID=172510&categoryID=20019&isPSADNA=false&pf=0&_=1583525404214
I want to be able to find the grade count for PSA 9 and 10. If we look at the HTML of the page, you will notice that PSA does a very bad job (IMO) at displaying the data. Every TR is a player. And the first TD is a card number. Let's just say I want to get Card Number 1 which in this case is Kevin Garnett.
There are a total of four cards, so those are the only four cards I want to display.
Here is the code I have.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://www.psacard.com/Pop/GetItemTable?headingID=172510&categoryID=20019&isPSADNA=false&pf=0&_=1583525404214");
const tr = await page.evaluate(() => {
const tds = Array.from(document.querySelectorAll('table tr'))
return tds.map(td => td.innerHTML)
});
const getName = tr.map(name => {
//const thename = Array.from(name.querySelectorAll('td.card-num'))
console.log("\n\n"+name+"\n\n");
})
await browser.close();
})();
I will get each TR printed, but I can't seem to dive into those TRs. You can see I have a line commented out, I tried to do this but get an error. As of right now, I am not getting it by the player dynamically... The easiest way I would think is to create a function that would think about getting the specific card would be doing something where the select the TR -> TD.card-num == 1 for Kevin.
Any help with this would be amazing.
Thanks
Short answer: You can just copy and paste that into Excel and it pastes perfectly.
Long answer: If I'm understanding this correctly, you'll need to map over all of the td elements and then, within each td, map each tr. I use cheerio as a helper. To complete it with puppeteer just do: html = await page.content() and then pass html into the cleaner I've written below:
const cheerio = require("cheerio")
const fs = require("fs");
const test = (html) => {
// const data = fs.readFileSync("./test.html");
// const html = data.toString();
const $ = cheerio.load(html);
const array = $("tr").map((index, element)=> {
const card_num = $(element).find(".card-num").text().trim()
const player = $(element).find("strong").text()
const mini_array = $(element).find("td").map((ind, elem)=> {
const hello = $(elem).find("span").text().trim()
return hello
})
return {
card_num,
player,
column_nine: mini_array[13],
column_ten: mini_array[14],
total:mini_array[15]
}
})
console.log(array[2])
}
test()
The code above will output the following:
{
card_num: '1',
player: 'Kevin Garnett',
column_nine: '1-0',
column_ten: '0--',
total: '100'
}
I am learning how to use fetch APIs and I can't seem to figure out why only 1 image is showing up when using the Unsplash API. I think it has something to do with map, but I'm not sure. Any help our guidance is appreciated.
document.getElementById("search").addEventListener("click", () => {
const searchTerm = document.getElementById("searchBox").value;
console.log(searchTerm);
fetch(
`https://api.unsplash.com/search/photos?client_id=e72d3972ba3ff93da57a4c0be4f0b7323346c136b73794e2a01226216076655b&query=${searchTerm}`
)
.then(response => response.json())
.then(data => {
console.log(data);
console.log(data.results);
let searchResults = data.results
.map(searchResults => {
return (document.getElementById("app").innerHTML = `<img src="${
searchResults.urls.small
}">`);
})
.join("");
});
});
Code Sandbox here: https://codesandbox.io/s/yq918mok29
The default 10 images should appear but only 1 shows up. How can I map through the images and show them on the page?
You're overwriting the element's content in each map iteration, so you only see the last one. Use map to build the markup, then drop it into the document.
Something like this:
.then(data => {
let imagesMarkup = '';
data.results
.map(searchResults => {
return (imagesMarkup += `<img src="${searchResults.urls.small}">`);
}); // note that the join was removed here
document.getElementById("app").innerHTML = imagesMarkup;
});
Demo