I followed the online course to make a website and I realized that my website is blinking when I move to other pages. Are there any problems in my codes?
parent
import "./Product.css";
import Product from "./Product";
import { useLayoutEffect, useState } from "react";
import axios from "axios";
export default function Body() {
const [products, SetProducts] = useState([]);
useLayoutEffect(() => {
async function fetchProducts() {
const { data } = await axios.get("http://127.0.0.1:8000/api/products/");
SetProducts(data);
}
fetchProducts();
}, []);
return (
<div className="product">
{products.map((data) => (
<div key={data.id} className="product_child">
<Product product={data} />
</div>
))}
</div>
);
}
child
import { Link } from "react-router-dom";
export default function Product({ product }) {
return (
<div className="product">
<div className="prodcut_child">
<Link
style={{ color: "inherit", textDecoration: "none" }}
to={`/product/${product.id}`}
>
<img src={product.img} alt="img" />
<p>{product.title}</p>
<h1 style={{ fontSize: "1.3rem" }}>${product.price}</h1>
</Link>
</div>
</div>
);
}
I used useEffect and it was blinking, so I tried useLayoutEffect instead but still, it happens.. (I used Link tag tho)
Is it because I use async await to fetch api data?
Or I am using django rest_framework, is it a problem with low speed of django rest_framework??
thank you..!
I think its because of the async await request that your doing.
You should try and implement the loading feature in your app
something like this
export default function Body() {
const [products, SetProducts] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
async function fetchProducts() {
setLoading(true);
const { data } = await axios.get("http://127.0.0.1:8000/api/products/");
SetProducts(data);
}
fetchProducts();
setLoading(false);
}, []);
if (loading) return <div>loading...</div>
...
}
Related
I am building an image search app using Unsplash API and trying to implement react infinite scroll (https://www.npmjs.com/package/react-infinite-scroll-component), but it is not working properly.
Now, the search form works fine and it displays 10 images(which is the default number of images using Unsplash API) when you search something, but when I scroll down to the end of of the page, it only displays the loader (h4 'Loading') but it does not display more images.
App.js
import './App.css';
import Main from './components/Main';
function App() {
return (
<div className="App">
<Main />
</div>
);
}
export default App;
Main.js
import React from 'react'
import Header from './Header'
import Image from './Image'
import { useState, useEffect } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component';
function Main() {
const [input, setInput] = useState('')
const [allImages, setAllImages] = useState([])
const [favorites, setFavorites] = useState(() => JSON.parse(localStorage.getItem("favorites")) || [])
useEffect(() => {
localStorage.setItem("favorites", JSON.stringify(favorites))
console.log(favorites)
}, [favorites])
function handleChange(event) {
setInput(event.target.value)
}
async function fetchImages() {
try {
const res = await fetch(`https://api.unsplash.com/search/photos?&query=${input}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
const data = await res.json();
setAllImages(data.results)
} catch(error) {
alert("Sum ting wong");
}
}
const handleSubmit = async (event) => {
event.preventDefault();
fetchImages()
}
console.log(`allImages: ${allImages.length}`);
// use parameter 'id' to read specific one
function isLiked(id) {
return favorites.find(el => el.id === id) ? true : false
}
return (
<main>
<Header
input={input}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
<InfiniteScroll
dataLength={allImages.length} //This is important field to render the next data
next={fetchImages}
hasMore={true}
loader={<h4>Loading...</h4>}
>
<div className='main--image-list mt-5 pb-5'>
{allImages.map(el => (
<Image
key={el.id}
// do need spread operator below for img's src to work in Image.js
{...el}
el={el}
isLiked={isLiked(el.id)}
favorites={favorites}
setFavorites={setFavorites}
/>
))}
</div>
</InfiniteScroll>
</main>
)
}
export default Main
for visuals
Here is, my code. i want to add a spinner when data is loading then after finish loading display the data.
{/* product section */}
<section className="container mx-auto my-5 row">
<h6 className="text-center">WANT TO SEE</h6>
<h2 className="text-center mb-5">Our Services</h2>
{
Products.slice(0,6)?.map((product) => {
return <Product key={product._id} product={product}></Product>;
})
}
</section>
And This is the custom hook
import { useEffect, useState } from "react";
const LoadProducts = () => {
const [Products, setProducts] = useState([]);
useEffect(()=>{
fetch('http://localhost:8080/products')
.then(res=>res.json())
.then(data=>setProducts(data))
},[]);
return [Products, setProducts]
}
export default LoadProducts;
Try this:
import { useEffect, useState } from "react";
const LoadProducts = () => {
const [Products, setProducts] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(()=>{
setLoading(true);
fetch('http://localhost:8080/products')
.then(res=>res.json())
.then(data=>setProducts(data))
.finally(()=>setLoading(false))
},[]);
return [Products, setProducts, loading]
}
export default LoadProducts;
Note the custom hook now returns [Products, setProducts, loading].
Now you can add some conditional code to your JSX that shows a loading spinner while loading is true
{/* product section */}
<section className="container mx-auto my-5 row">
<h6 className="text-center">WANT TO SEE</h6>
<h2 className="text-center mb-5">Our Services</h2>
{loading ? (
<MyLoadingSpinner/>
) : (
Products.slice(0,6)?.map((product) => {
return <Product key={product._id} product={product}></Product>;
})
)}
</section>
I would do something like this but you can customize it to your needs. The trick is really to create a loading state which initially will be false. When you start calling your api, you will set your loading to true. Once you either get a response or fail you set it to false.
import { useEffect, useState } from "react";
export default function App() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch("https://jsonplaceholder.typicode.com/posts")
.then((response) => response.json())
.then((json) => {
console.log(json);
setPosts(json);
setLoading(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
}, []);
if (loading) {
return <div>I am loading</div>;
}
return (
<div>
{posts.map((post) => (
<p>{post.title}</p>
))}
</div>
);
}
I have seen this asked before but I can't seem to be able to wrap my head around it with my situation.
I am using a search bar to filter the data down and it works but the image will not update. The URL passing to the child works fine but it's just not changing its state. I just don't really understand how to implement it.
PokemonList.jsx
import axios from "axios";
import React, { useEffect, useState } from "react";
import PokemonSprite from "./PokemonSprite";
import Card from "#material-tailwind/react/Card";
import CardBody from "#material-tailwind/react/CardBody";
import CardFooter from "#material-tailwind/react/CardFooter";
import H6 from "#material-tailwind/react/Heading6";
import Paragraph from "#material-tailwind/react/Paragraph";
import Button from "#material-tailwind/react/Button";
// const baseURL = "https://pokeapi.co/api/v2/pokemon?limit=898";
const baseURL = "https://pokeapi.co/api/v2/pokemon?limit=20";
export default function PokemonList() {
const [post, setPost] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
axios.get(baseURL).then((response) => {
setPost(response.data.results);
});
}, []);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<input type="text" placeholder="Search..." onChange={e => {setSearchTerm(e.target.value)}}/>
{post.filter((data) => {
if (searchTerm == "") {
return data;
} else if (data.name.toLowerCase().includes(searchTerm.toLowerCase())) {
console.log(data);
return data;
}
}).map((data, idx) => (
<div className="p-5">
<Card key={idx}>
<PokemonSprite url={data.url} />
<CardBody>
<H6 color="gray">{data.name}</H6>
<Paragraph color="gray">
Don't be scared of the truth because we need to restart the human
foundation in truth And I love you like Kanye loves Kanye I love
Rick Owens’ bed design but the back is...
</Paragraph>
</CardBody>
<CardFooter>
<Button color="lightBlue" size="lg" ripple="light">
Read More
</Button>
</CardFooter>
</Card>
</div>
))}
</div>
);
}
PokemonSprite.jsx
import axios from "axios";
import React, { useEffect, useState } from "react";
import CardImage from "#material-tailwind/react/CardImage";
export default function PokemonList(url) {
const [post, setPost] = useState();
console.log(url);
useEffect(() => {
axios.get(url.url).then((response) => {
//console.log(response.data);
setPost(response.data);
});
}, []);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<CardImage
src={post.sprites.front_default}
alt="Card Image"
/>
</div>
);
}
Please rewrite your PokemonSprite component like this to enable re rendering on updates to the Url...
import axios from "axios";
import React, { useEffect, useState } from "react";
import CardImage from "#material-tailwind/react/CardImage";
export default function PokemonList(url) {
const [post, setPost] = useState();
console.log(url);
const getUpdatedImage = async (imageUrl) => {
const response = await axios.get(imageUrl);
setPost(response.data);
return post;
}
useEffect(() => {
getUpdatedImage(url.url);
}, [url]);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<CardImage
src={post.sprites.front_default}
alt="Card Image"
/>
</div>
);
}
Is there a way we can have a loading state similar to when fetching data on the client-side?
The reason I would like a loading state is to have something like a loading-skeleton with for instance react-loading-skeleton
On the client-side we could do:
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json())
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
But for SSR (getServerSideProps) I cannot figure out if that is doable for example could we have a loading state?
function AllPostsPage(props) {
const router = useRouter();
const { posts } = props;
function findPostsHandler(year, month) {
const fullPath = `/posts/${year}/${month}`;
router.push(fullPath);
}
if (!data) return <div>loading...</div>; // Would not work with SSR
return (
<Fragment>
<PostsSearch onSearch={findPostsHandler} />
<PosttList items={posts} />
</Fragment>
);
}
export async function getServerSideProps() {
const posts = await getAllPosts();
return {
props: {
posts: posts,
},
};
}
export default AllPostsPage;
Recently Next.js has released getServerSideProps should support props value as Promise https://github.com/vercel/next.js/pull/28607
With that we can make a promise but am not sure how to implement that and have a loading state or if that is even achievable. Their example shows:
export async function getServerSideProps() {
return {
props: (async function () {
return {
text: 'promise value',
}
})(),
}
}
Currently watching Next.conf (25/10/2022) this issue looks promising:
https://beta.nextjs.org/docs/data-fetching/streaming-and-suspense
You can modify the _app.js component to show a Loading component while the getServerSideProps is doing async work like a fetch as shown here https://stackoverflow.com/a/60756105/13824894. This will apply on every page transition within your app.
You can still use your loading logic client-side independently.
you can set loading state on _app.js
import Router from "next/router";
export default function App({ Component, pageProps }) {
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
const start = () => {
console.log("start");
setLoading(true);
};
const end = () => {
console.log("findished");
setLoading(false);
};
Router.events.on("routeChangeStart", start);
Router.events.on("routeChangeComplete", end);
Router.events.on("routeChangeError", end);
return () => {
Router.events.off("routeChangeStart", start);
Router.events.off("routeChangeComplete", end);
Router.events.off("routeChangeError", end);
};
}, []);
return (
<>
{loading ? (
<h1>Loading...</h1>
) : (
<Component {...pageProps} />
)}
</>
);
}
My choice is to use isReady method of useRouter object
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
function MyApp({ Component, pageProps }) {
const [isLoading, setIsLoading] = useState(true)
const router = useRouter()
useEffect(() => {
router.isReady && setIsLoading(false)
}, []
)
return <>{isLoading ? <>loading...</> : <Component {...pageProps} />}</>
}
export default MyApp
I have not tried this feature yet but in theory I think it should work. If all you want is to have the client side access to a promise via server props, try as below. Basically your props is a async lambda function so you do any work needed e.g fetching data etc inside it so the client-side should access props as a promise and await for it.
export async function getServerSideProps() {
return {
props: (async function () {
const posts = await getAllPosts();
return {
posts: posts,
}
})(),
}
}
//then on client-side you can do the following or similar to set loading state
function MyComponent(props) {
const [isLoading, setIsLoading] = useState(false);
const [posts, setPosts] = useState({});
useEffect(async () => {
setIsLoading(true);
const tempPosts = await props?.posts;
setPosts(posts);
setIsLoading(false);
}, [])
return (
{isLoading && <div>loading...</div>}
);
}
export default MyComponent;
This works for me using MUI v.5
import Router from "next/router";
import Head from "next/head";
import { useEffect, useState } from "react";
import { CacheProvider } from "#emotion/react";
import {
ThemeProvider,
CssBaseline,
LinearProgress,
CircularProgress,
circularProgressClasses,
Box,
} from "#mui/material";
import { alpha } from "#mui/material/styles";
import createEmotionCache from "/src/createEmotionCache";
import theme from "/src/theme";
import Layout from "/src/components/layout/Layout";
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
function Loader(props) {
return (
<Box
sx={{
position: "fixed",
top: 0,
left: 0,
right: 0,
}}
>
<LinearProgress />
<Box sx={{ position: "relative", top: 8, left: 8 }}>
<CircularProgress
variant="determinate"
sx={{
color: alpha(theme.palette.primary.main, 0.25),
}}
size={40}
thickness={4}
{...props}
value={100}
/>
<CircularProgress
variant="indeterminate"
disableShrink
sx={{
animationDuration: "550ms",
position: "absolute",
left: 0,
[`& .${circularProgressClasses.circle}`]: {
strokeLinecap: "round",
},
}}
size={40}
thickness={4}
{...props}
/>
</Box>
</Box>
);
}
function MyApp({
Component,
pageProps,
emotionCache = clientSideEmotionCache,
}) {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
Router.events.on("routeChangeStart", () => {
setIsLoading(true);
});
Router.events.on("routeChangeComplete", () => {
setIsLoading(false);
});
Router.events.on("routeChangeError", () => {
setIsLoading(false);
});
}, [Router]);
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
{isLoading && <Loader />}
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</CacheProvider>
);
}
export default MyApp;
I was about rounding up this activity and I started getting this error:
Module not found: Can't resolve './components/Post' in ./src/pages/index.js
I have tried all I could but was not able to solve this problem. Below is the list of my codes:
index.js
import React, { useState, useEffect } from 'react';
import Posts from './components/Pagination';
import Pagination from './components/Post';
import axios from 'axios';
const Home = () => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
useEffect(() => {
const fetchPosts = async () => {
setLoading(true);
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
setPosts(res.data);
setLoading(false);
};
fetchPosts();
}, []);
// Get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost);
// Change page
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div
style={{
display: 'flex',
height: '90vh'
}}
>
<img src={require('../images/top_img.jpg')} alt='logo' height='500px' width='100%'/>
<div className='container mt-5'>
<h1 className='text-primary mb-3'>LATEST NEWS</h1>
<Posts posts={currentPosts} loading={loading} />
<Pagination
postsPerPage={postsPerPage}
totalPosts={posts.length}
paginate={paginate}
/>
</div>
</div>
);
};
export default Home;
Post.js
import React from 'react';
const Posts = ({ posts, loading }) => {
if (loading) {
return <h2>Loading...</h2>;
}
return (
<ul className='list-group mb-4'>
{posts.map(post => (
<li key={post.id} className='list-group-item'>
{post.title}
</li>
))}
</ul>
);
};
export default Posts;
Pagination.js
import React from 'react';
const Posts = ({ posts, loading }) => {
if (loading) {
return <h2>Loading...</h2>;
}
return (
<ul className='list-group mb-4'>
{posts.map(post => (
<li key={post.id} className='list-group-item'>
{post.title}
</li>
))}
</ul>
);
};
export default Posts;
Below is the structure of my code:
I believe your object names are swapped.
Isn't there an error of relative path?
import Posts from '../components/Pagination';
import Pagination from './components/Post';
both imports are in the same file and both files are in the same folder, but one has ../ and other ./
First, you I would make the named imports match the file structure:
import Post from "./components/Post"
import Pagination from "./components/Pagination"
Also, your default exports are messing things up as well. You will want to export Pagination and then in ./Post you want to export Post.
Lastly, your paths were messed up. The "components" folder is on the same level as index.js. Thus, you can access it through "./components"