My idea is : when I click into the page, the page will send a axios request for the top 30 data and show them in the InfoCard. After that, when I scroll to the end of the page it will send a new axios request for the next 30 data and show them in the InfoCard.
I watched this tutorial and tried it myself but I still not sure how it works.
https://www.youtube.com/watch?v=NZKUirTtxcg
//TestScreen3.js
function TestScreen3() {
const [topNdata, setTopNdata] = useState(30)
const [skipNdata, setSkipNdata] = useState(0)
const { loading, ScenicSpot, hasMore } = RequestTest(topNdata, skipNdata)
return (
<div className="App">
<header className="App-header">
<Navbar NavbarTitle="Scenic Spots" />
<CityList />
{ScenicSpot.map((infoCard) => (<InfoCard key={infoCard.ID} Name={infoCard.Name} Description={infoCard.Description} Picture={infoCard.Picture.PictureUrl1} />))}
</header>
</div>
);
}
export default TestScreen3;
//RequestTest.js
export default function RequestTest(topNdata, skipNdata) {
const [ScenicSpot, setScenicSpot] = useState([])
const [hasMore, setHasMore] = useState(false)
const [loading, setLoading] = useState(true)
useEffect(() => {
setScenicSpot([])
}, [topNdata])
useEffect(() => {
setLoading(true)
axios({
method: 'GET',
url: 'https://ptx.transportdata.tw/MOTC/v2/Tourism/ScenicSpot',
params: { $top: topNdata, $skip: skipNdata },
}).then(res => {
setScenicSpot(res.data)
setHasMore(res.data.length > 0)
setLoading(false)
}).catch(err => { console.log(err) })
}, [topNdata, skipNdata])
return { loading, ScenicSpot, hasMore }
}
Though it will seem like a different thing.
But you will found a way for infinite scroll here at StackOverflow.
Read the question and my answer to this question.
Though I was using axios from my API, you will get the idea from there.
Related
I have modal which, when opened, makes an Axios get request and returns a QR Code, but I'm trying to cache the QR Code, so when the modal is reopened, the QR Code doesn't need to be re-requested.
I've tried something like:
const url = window.location.href;
const qrc = useMemo(async () => {
return await getQRCode(url).then((res) => {
return res.data
});
}, [url]);
Then in my jsx:
{qrc ? <img src={qrc} alt="qr code" /> : <LoadingDisplay />}
But of course, qrc is still a promise. I'm thinking my syntax is just incorrect, but I couldn't figure it out.
edit:
I've used useEffect with useState, which works, but still re-calls the request every time the modal is re-rendered.
Something like
const [qrc, setQrc] = useState<string>("");
const url = window.location.href;
useEffect(() => {
getQRCode(url).then((res) => {
setQrc(res.data);
});
}, [url]);
The modal is opened and closed with a button click calling setShowModal(!showModal)
with the jsx: {showModal && <Modal />}
You might want to make the call from a useEffect to get the image source, and store the <img> in a memo.
See: https://codesandbox.io/s/react-qr-code-generator-kjkk9e?file=/src/QRCode.jsx
import { useEffect, useMemo, useState } from "react";
const apiBaseUrl = "https://api.qrserver.com/v1";
const getQRCode = (url, size) =>
fetch(`${apiBaseUrl}/create-qr-code/?size=${size}x${size}&data=${url}`)
.then((res) => res.blob())
.then((blob) => URL.createObjectURL(blob));
const QRCode = (props) => {
const { url, size } = props;
const [src, setSrc] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
getQRCode(url, size)
.then(setSrc)
.finally(() => setLoading(false));
}, [size, url]);
const QRCodeImage = useMemo(() => <img src={src} alt={url} />, [src, url]);
return <>{loading ? <span>Loading...</span> : QRCodeImage}</>;
};
export default QRCode;
I'm using react-intersection-observer npm package for my CRA project.
I want to achieve this Infinite Scroll effect whenever users reach the bottom of the page or click on the Load More button -> It will fetch 8 more items.
Currently, the getMoreData() will only execute when I click on the Load More button.
Also, I put posts state in the dependency array so it will display the first 8 items for activePosts on start. But this leads to status code 429: over rate limit in the Network tab.
My Questions:
How to also apply the getMoreData() function to fetch out more items when we scroll to the end of the page? (Infinity scroll)
There might be a bug if I remove the posts from the dependency array, it won't display the first 8 items on initial page load. How to fix this the right way?
Screenshots:
My Code:
Posts.js
function Posts() {
const url = "https://6264f60294374a2c506b97c9.mockapi.io/posts";
const [posts, setPosts] = useState([]);
const [activePosts, setActivePosts] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const getData = async () => {
try {
let response = await axios(url);
let result = response.data;
setPosts(result);
setActivePosts(posts.slice(0, 8));
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getData();
}, [posts]);
const getMoreData = () => {
setIsFetching(true);
setTimeout(() => {
setActivePosts((prev) => {
return [...prev, ...posts.slice(prev.length + 1, prev.length + 9)];
});
setIsFetching(false);
}, 2000);
};
useEffect(() => {
if (!isFetching) return;
getMoreData();
}, [isFetching]);
return (
<>
<div className="posts">
{activePosts.map((post, index) => (
<Post post={post} key={post.id} index={index} />
))}
</div>
<button onClick={getMoreData}>
{isFetching ? "Loading..." : "Load more"}
</button>
</>
);
}
export default Posts;
Post.js
import { useInView } from "react-intersection-observer";
function Post({ post }) {
const { ref, inView } = useInView({
initialInView: true,
triggerOnce: true,
threshold: 1,
});
return (
<div className="post" ref={ref}>
{inView ?
<img src={post.imgUrl} alt={post.title} className="post__img" loading="lazy" />
: <div className="post__img" />
}
<h1 className="post__title">{post.title}</h1>
</div>
)
}
export default Post
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.
I am trying to implement Product search by text. Fetching data with react-query. The following implementation is working but it does not feel right to me. Let me know if I am overdoing it and if there is a simpler solution with react-query.
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useQueryClient } from 'react-query';
import ProductCard from '#/components/cards/ProductCard';
import { useQueryProducts } from '#/hooks/query/product';
import { selectSearch } from '#/store/search';
// function fetchProductsByFilter(text){}
const Shop = ({ count }) => {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const { text } = useSelector(selectSearch);
const productsQuery = useQueryProducts(count);
useEffect(() => {
setProducts(productsQuery.data);
setLoading(false);
}, []);
const queryClient = useQueryClient();
useEffect(() => {
const delayed = setTimeout(() => {
queryClient.prefetchQuery(['searchProductsByText'], async () => {
if (text) {
const data = await fetchProductsByFilter(text);
setProducts(data);
setLoading(false);
return data;
}
});
}, 300);
return () => clearTimeout(delayed);
}, [text]);
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-3">search/filter menu</div>
<div className="col-md-9">
{loading ? (
<h4 className="text-danger">Loading...</h4>
) : (
<h4 className="text-danger">Products</h4>
)}
{products.length < 1 && <p>No products found</p>}
<div className="row pb-5">
{products.map((item) => (
<div key={item._id} className="col-md-4 mt-3">
<ProductCard product={item} />
</div>
))}
</div>
</div>
</div>
</div>
);
};
// async function getServerSideProps(context) {}
export default Shop;
It doesn't seem very idiomatic to me. With react-query, the key to using filters are to put them into the query key. Since react-query refetches every time the key changes, you'll get a refetch every time you change a filter, which is usually what you want. It's a very declarative way of doing things. No useEffect needed at all.
If this happens when choosing something from a select or clicking an apply button, that's really all you need:
const [filter, setFilter] = React.useState(undefined)
const { data, isLoading } = useQuery(
['products', filter],
() => fetchProducts(filter)
{ enabled: Boolean(filter) }
)
Here, I am additionally disabling the query as long as the filter is undefined - fetching will start as soon as we call setFilter.
if typing into a text field is involved, I'd recommend some debouncing to avoid firing off too many requests. The useDebounce hook is very good for that. You'd still have the useState, but you'd use the debounced value for the query:
const [filter, setFilter] = React.useState(undefined)
const debouncedFilter = useDebounce(filter, 500);
const { data, isLoading } = useQuery(
['products', debouncedFilter],
() => fetchProducts(debouncedFilter)
{ enabled: Boolean(debouncedFilter) }
)
If this happens when choosing something from a select or clicking an apply button, that's really all you need:
const [filter, setFilter] = useState<string>('')
const isEnabled = Boolean(filter)
const { data, isLoading } = useQuery(
['products', filter],
() => fetchProducts(filter)
{ enabled: enabled: filter ? isEnabled : !isEnabled, }
)
<input type="text" onChange={(e) => setFilter(e.target.value)}/>
I have a module that renders an svg. Before it, the module should check on authorization and if ok fetch the file from api via call with a token.
I have the next code
function App() {
const [tableColors, setTableColors] = useState(["gray"]);
const [svg, setSvg] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
const [isErrored, setIsErrored] = useState(false);
new AuthService().getUser().then(user =>{ if(!user) {
Login()
}
else{
useEffect( async () => {
LoadSvg()
.then(res => res.text())
.then(setSvg)
.catch(setIsErrored)
.then(() => setIsLoaded(true))
}, [])
}})
return (
<div className="App">
<header className="App-header">
<SvgLoader svgXML={svg}>
</SvgLoader>
</header>
</div>
);
function Login(){
var a = new AuthService();
a.login();
}
async function LoadSvg(){
return await new ApiService().callApi("https://localhost:44338/api/v1/desk-booking/Plan/getroomplanbyid?roomId=1")
}
}
And the problem I have here is "React Hook "useEffect" cannot be called inside a callback" but without using "useEffect" it fetchs the svg endlessly.
How I can solve the issue?
You are not doing it right, if you do this the "react" way then the solution would look like this
....
function App() {
const [tableColors, setTableColors] = useState(['gray']);
const [svg, setSvg] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
const [isErrored, setIsErrored] = useState(false);
// state to check if user is loaded, used for svg call
const [userLoaded, setUserLoaded] = useState(false);
// useEffect does not prefer async function
useEffect(() => {
if (!userLoaded) {
new AuthService().getUser().then(user => {
if (!user) {
Login();
// indicate user was loaded
// I would move the login function body here instead since, login is async, so this might not work as intended but you get the idea
setUserLoaded(true);
}
});
}
}, [])
useEffect(() => {
// if userLoaded is true then go ahead with svg loading
if (userLoaded) {
LoadSvg()
.then(res => res.text())
.then(setSvg)
.catch(setIsErrored)
.then(() => setIsLoaded(true));
}
// Add svg useEffect dependency on userLoaded
}, [userLoaded]);
......
Note, this solution is intended to give you an idea of how to do it, It might not work if you copy-paste the code.