How to add loader correctly while infinite scrolling? - javascript

I tried multiple ways to implement loading while fetching more data during infinite scrolling, but nothing worked properly, so I deleted loader; I have here state (with redux) named: loading but cannot write the logic of loading correctly. Could you please tell me how I can make it work?
Here I will provide with code:
import React, {useEffect} from 'react';
import { Link } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import {setAllUsers, setLoading, setPage} from '../redux/actions/actions';
import User from './User';
import '../styles/AllUsersList.css';
const AllUsersList = () => {
const allUsers = useSelector(state => state.setAllUsersReducer);
const page = useSelector(state => state.setPageReducer);
const loading = useSelector(state => state.setLoadingReducer);
const dispatch = useDispatch();
const fetchAllUsers = () => {
fetch(`${url}/${page}/15`)
.then(res => res.json())
.then(data => {
dispatch(setAllUsers(data.list));
})
.catch(err => console.log('Error message: ', err))
}
useEffect(() => {
fetchAllUsers();
}, [page])
const handleScroll = () => {
dispatch(setPage());
}
window.onscroll = function () {
if(window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
handleScroll();
}
}
return (
<div className="allUsersList">
{
allUsers ? (
allUsers.map((user, index) => (
<Link key={user.id} to={`/user/${user.id}`}>
<User name={user.name} lastName={user.lastName} prefix={user.prefix} title={user.title} img={user.imageUrl}/>
</Link>
))
) : (
<div> Loading... </div>
)
}
</div>
)
}
export default AllUsersList;

Your state loading would be set to true in your function fetchAllUsers the data and when the promise resolves it gets set to false.
Here's an example on how you would do it, you can adapt it to use a redux dispatcher to change loading state.
const loading = useState(false);
...
const fetchAllUsers = () => {
setLoading(true);
fetch(`${url}/${page}/15`)
.then(res => res.json())
.then(data => {
dispatch(setAllUsers(data.list));
})
.catch(err => console.log('Error message: ', err))
.finally(() => {
setLoading(false);
})
}
...
{
!loading ? (
allUsers.map((user, index) => (
<Link key={user.id} to={`/user/${user.id}`}>
<User name={user.name} lastName={user.lastName} prefix={user.prefix} title={user.title} img={user.imageUrl}/>
</Link>
))
) : (
<div> Loading... </div>
)
}

Related

How to stop a React App breaking on refreshing when using UseEffect

import {useState, useEffect } from 'react'
import axios from 'axios'
const Singlecountry = ({searchedCountries, setWeather, weather}) => {
const weatherName = searchedCountries[0].capital
const iconname = () => {
if (weather === undefined) {
return null
}
weather.map(w => w.weather[0].icon)
}
console.log(iconname)
useEffect(() => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${weatherName}&appid=${process.env.REACT_APP_API_KEY}`)
.then(response => {
const apiResponse = response.data;
console.log(apiResponse)
console.log(`Current temperature in ${apiResponse.name} is ${apiResponse.main.temp - 273.15}℃`);
setWeather([apiResponse])
}).catch(error => {
console.log(error);
})
}, [])
return(
<div>
capital: {searchedCountries.map(c => <p>{c.capital}</p>)}
area: {searchedCountries.map(c => <p>{c.area}</p>)}
<h2>Languages</h2>
<ul>
{
searchedCountries.map(c =>
<ul>
{Object.values(c.languages).map(l => <li>{l}</li>)}
</ul>
)
}
</ul>
{searchedCountries.map(c => <img src={Object.values(c.flags)[0]} alt="" /> )}
<h3>Weather</h3>
<p>temperature is {weather.map(w => w.main.temp - 273.15)} degrees Celsius</p>
<p>wind is {weather.map(w => w.wind.speed)} miles per hour</p>
<img src={`http://openweathermap.org/img/wn/${iconname}.png`} alt="" />
</div>
)
}
const Countries = ({ searchedCountries, handleClick, show, setWeather, setCountries, weather}) => {
if (weather === undefined) {
return null
}
if (searchedCountries.length >= 10) {
return (
<div>
<p>too many countries to list, please narrow your search</p>
</div>
)
}
if (searchedCountries.length === 1) {
return (
<Singlecountry searchedCountries={searchedCountries} setWeather={setWeather} weather={weather}/>
)
}
if (show === true) {
return (
<Singlecountry searchedCountries={searchedCountries} setWeather={setWeather} />
)
}
return (
<ul>
{searchedCountries.map(c => <li>{c.name.common}<button onClick={handleClick} >show</button></li>)}
</ul>
)
}
const App = () => {
const [countries, setCountries] = useState([])
const [newSearch, setNewSearch] = useState('')
const [show, setShow] = useState(false)
const [weather, setWeather] = useState('')
const handleSearchChange = (event) => {
setNewSearch(event.target.value)
}
const handleClick = () => {
setShow(!show)
}
const searchedCountries =
countries.filter(c => c.name.common.includes(newSearch))
useEffect(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setCountries(response.data)
})
}, [])
return (
<div>
<div><p>find countries</p><input value={newSearch} onChange={handleSearchChange} /></div>
<div>
<h2>countries</h2>
<Countries searchedCountries={searchedCountries} handleClick={handleClick} show={show} setCountries={setCountries} setWeather={setWeather} weather={weather}/>
</div>
</div>
)
}
export default App
The following code is designed to display information on countries when the user types in the countries' name in the search bar, including capital city, temperature and its weather.
The app fetches country data from a Countries API and when the user searches for a specific country, the weather its then fetched from a Weather API.
However, when the app is refreshed, the app breaks when searching for an individual country's weather.
Does anyone know why this is and how to solve it?
Thanks
It looks like you're using axios inside useEffect which can cause and infinite loop and crash your app. I recommend creating a separate function for your data fetching and then call the function in the useEffect like so:
const fetchCountries = useCallback(() => {
axios
.get('https://restcountries.com/v3.1/all')
.then(response => {
setCountries(response.data)
})
}, [])
useEffect(() => {
fetchCountries()
}, [fetchCountries])
The key is the dependency array in useEffect which will only update if there is a change in the list of countries from fetchCountries function, thus preventing the infinite loop.

Why can't I render multiple cards using map?

I'm trying to render multiple cards by pulling data from the API. But the return is an array, I don't understand why the map is not working.
const CharacterCard = () => {
const [showModal, setShowModal] = useState(false)
const openModal = () => {
setShowModal(prev => !prev)
}
const characters = useRequestData([], `${BASE_URL}/characters`)
const renderCard = characters.map((character) => {
return (
<CardContainer key={character._id} imageUrl={character.imageUrl}/>
)
})
return (
<Container>
{renderCard}
<ModalScreen showModal={showModal} setShowModal={setShowModal} />
</Container>
)
}
export default CharacterCard
The hook is this
import { useEffect, useState } from "react"
import axios from "axios"
const useRequestData = (initialState, url) => {
const [data, setData] = useState(initialState)
useEffect(() => {
axios.get(url)
.then((res) => {
setData(res.data)
})
.catch((err) => {
console.log(err.data)
})
}, [url])
return (data)
}
export default useRequestData
console error image
requisition return image
API: https://disneyapi.dev/docs
Looks like the default value of the characters is undefined.
So something like (characters || []).map.. will help I think.
For deeper look at this you can debug useRequestData hook, as I can't see the source of that hook from you example

Search function now working in React photo gallary

Working on a small application that takes a pexels api and displays photos dynamically. When I send the search request for my api to fectch based on the new params, it does actually update the page with new photos but not the ones based on the params. I though I got the search function correct, maybe it's cause I'm not using it in a useEffect? But if I did use it in a useEffect, I wouldn't be able to set it on the onClick handle. I tried to console.log the query I was getting from the onChange but it doesn't seem like it's getting the result. What am I doing wrong?
import { useState, useEffect } from 'react'
import pexelsApi from './components/pexelsApi'
import './App.css'
const App = () => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const [nextPage, setNextPage] = useState(1);
const [perPage, setPerPage] = useState(25);
const [query, setQuery] = useState('');
const [error, setError] = useState('');
useEffect(() => {
const getImages = async () => {
setLoading(true);
await pexelsApi.get(`/v1/curated?page=${nextPage}&per_page=${perPage}`)
.then(res => {
setImages([...images, ...res.data.photos]);
setLoading(false);
}).catch(er => {
if (er.response) {
const error = er.response.status === 404 ? 'Page not found' : 'Something wrong has happened';
setError(error);
setLoading(false);
console.log(error);
}
});
}
getImages();
}, [nextPage, perPage]);
const handleLoadMoreClick = () => setNextPage(nextPage + 1)
const search = async (query) => {
setLoading(true);
await pexelsApi.get(`/v1/search?query=${query}&per_page=${perPage}`)
.then(res => {
setImages([...res.data.photos]);
console.log(res.data)
setLoading(false);
console.log(query)
})
}
if (!images) {
return <div>Loading</div>
}
return (
<>
<div>
<input type='text' onChange={(event) => setQuery(event.target.value)} />
<button onClick={search}>Search</button>
</div>
<div className='image-grid'>
{images.map((image) => <img key={image.id} src={image.src.original} alt={image.alt} />)}
</div>
<div className='load'>
{nextPage && <button onClick={handleLoadMoreClick}>Load More Photos</button>}
</div>
</>
)
};
export default App
import axios from 'axios';
export default axios.create({
baseURL: `https://api.pexels.com`,
headers: {
Authorization: process.env.REACT_APP_API_KEY
}
});
Your main issue is that you've set query as an argument to your search function but never pass anything. You can just remove the arg to have it use the query state instead but you'll then need to handle pagination...
// Helper functions
const getCuratedImages = () =>
pexelsApi.get("/v1/curated", {
params: {
page: nextPage,
per_page: perPage
}
}).then(r => r.data.photos)
const getSearchImages = (page = nextPage) =>
pexelsApi.get("/v1/search", {
params: {
query,
page,
per_page: perPage
}
}).then(r => r.data.photos)
// initial render effect
useEffect(() => {
setLoading(true)
getCuratedImages().then(photos => {
setImages(photos)
setLoading(false)
})
}, [])
// search onClick handler
const search = async () => {
setNextPage(1)
setLoading(true)
setImages(await getSearchImages(1)) // directly load page 1
setLoading(false)
}
// handle pagination parameter changes
useEffect(() => {
// only action for subsequent pages
if (nextPage > 1) {
setLoading(true)
const promise = query
? getSearchImages()
: getCuratedImages()
promise.then(photos => {
setImages([...images, ...photos])
setLoading(false)
})
}
}, [ nextPage ])
The reason I'm passing in page = 1 in the search function is because the setNextPage(1) won't have completed for that first page load.

API giving data in second render in React

I was trying to fetch api with react.js but on first render its gives nothing and the second render its gives data. This makes it so when I try to access the data later for an image I get an error, TypeError: Cannot read property 'news.article' of undefined, because it is initially empty. how can I solve this?
here is my code ..
import React, { useEffect, useState } from 'react';
const HomeContent = () => {
const [news, updateNews] = useState([]);
console.log(news);
useEffect(() => {
const api = 'http://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=940c56bd75da495592edd812cce82149'
fetch(api)
.then(response => response.json())
.then(data => updateNews(data))
.catch((error) => console.log(error))
}, [])
return (
<>
</>
);
};
export default HomeContent;
There is no issue with the code itself, the output you receive is expected. However, you can render the content after it is retrieved as such
import React, { useEffect, useState } from 'react';
const HomeContent = () => {
const [news, updateNews] = useState([]);
const [isLoading, setIsLoading] = useState(true);
console.log(news);
useEffect(() => {
const api = 'http://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=940c56bd75da495592edd812cce82149'
fetch(api)
.then(response => response.json())
.then(data => {
updateNews(data.articles);
setIsLoading(false);
})
.catch((error) => {
console.log(error);
setIsLoading(false);
})
}, [])
return (
<>
{isLoading ?
<p>Loading...</p> :
// Some JSX rendering the data
}
</>
);
};
export default HomeContent;

Why is the fetch statement in my react app resulting in two calls?

Can anybody please explain to me why the fetch statement is resulting in 2 API calls? Both the chrome console and dev tools > network tab is showing two versions. The following is the code that I am using.
import React, { useState } from 'react';
import './contact.css';
const App = () => {
const [contacts, setContacts] = useState([]);
fetch("https://randomuser.me/api/?results=3")
.then(response => response.json())
.then(data => console.log(data));
return (
<>
{
contacts.map(contact => (
<ContactCard
avatar="https://via.placeholder.com/150"
name={contact.name}
email={contact.email}
age={contact.age}
/>
))
}
</>
)
};
const ContactCard = props => {
const [showAge, setShowAge] = useState(false);
return (
<div className="contact-card">
<img src="https://via.placeholder.com/150" alt="profile" />
<div className="user-details">
<p>Name: {props.name}</p>
<p>Email: {props.email}</p>
<button onClick={() => setShowAge(!showAge)}>{!showAge ? 'Show' : 'Hide'} Age</button>
{
showAge && <p>Age: {props.age}</p>
}
</div>
</div>
);
};
export default App;
const App = () => {
const [contacts, setContacts] = useState([]);
// the issue is here, each time the component renders this statement will be exectuted
fetch("https://randomuser.me/api/?results=3")
.then(response => response.json())
.then(data => console.log(data));
// if you want to execute code after component is mounted into dom, use useEffect
// like this
useEffect(() => {
fetch("https://randomuser.me/api/?results=3")
.then(response => response.json())
.then(data => console.log(data));
}, []) // the second param for useEffect is dependencies array, pass an empty array if you want your effect to run only once (which is equivalent to componentDidMount in react class based components)
return (
<>
{
contacts.map(contact => (
<ContactCard
avatar="https://via.placeholder.com/150"
name={contact.name}
email={contact.email}
age={contact.age}
/>
))
}
</>
)
};

Categories

Resources