I'm writing a react Header component and have added the functionality to click in and out of a dropdown menu. I'm getting the following error whenever I load the page:
next-dev.js?3515:20 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render
I imagine I'm making a mistake somewhere in a useEffect (I am running 2 useEffects, one inside a component and one inside another function), but am unsure where I'm going wrong. Here is the code below:
import React from "react";
import { useState, useEffect, useRef } from "react";
import { FaChevronDown, FaLastfmSquare } from "react-icons/all";
import { signOut } from "firebase/auth";
import { useRouter } from "next/router";
import { auth } from "../utils/auth";
function useOutsideAlerter() {
const [visible, setVisible] = useState(false);
const ref = useRef(null);
useEffect(() => {
const handleHideDropdown = (event) => {
if (event.key === "Escape") {
setVisible(false);
}
};
const handleClickOutside = (e) => {
if (ref.current && !ref.current.contains(e.target)) {
setVisible(false);
}
};
document.addEventListener("keydown", handleHideDropdown, true);
document.addEventListener("mousedown", handleClickOutside, true);
return () => {
document.removeEventListener("keydown", handleHideDropdown, true);
document.removeEventListener("mousedown", handleClickOutside, true);
};
}, []);
return { visible, ref, setVisible };
}
const Header = () => {
const [user, setUser] = useState(null);
const router = useRouter();
const { ref, visible, setVisible } = useOutsideAlerter();
useEffect(() => {
const user = localStorage.getItem("user");
if (user) {
setUser(JSON.parse(user));
}
});
const logUserOut = () => {
signOut(auth)
.then(() => {
localStorage.removeItem("user");
router.push("/");
})
.catch((error) => {
console.error(error);
alert("There was a problem signing out.");
});
};
return (
<div className="flex items-center justify-between p-4 border-b border-gray-300">
<h1 className={"font-bold text-lg"}>Matrice</h1>
<div ref={ref}>
<button
onClick={() => setVisible(!visible)}
className="hover:bg-gray-100 rounded-md p-2 flex items-center justify-center text-gray-400"
>
{user?.displayName || "Login"} <FaChevronDown className={"ml-2"} />
</button>
{visible && (
<div
className={
"absolute top-14 right-4 rounded-md border border-gray-300 bg-white overflow-hidden"
}
>
{user && (
<button
onClick={logUserOut}
className={
"text-left text-sm hover:bg-blue-700 hover:text-white w-full px-4 py-2"
}
>
Log Out
</button>
)}
<button
onClick={() =>
router.replace(
"mailto:email"
)
}
className={
"text-left text-sm hover:bg-blue-700 hover:text-white w-full px-4 py-2"
}
>
Request Feature
</button>
</div>
)}
</div>
</div>
);
};
export default Header;
I imagine I'm making a mistake somewhere in a useEffect
Yes, you haven't specified a dependency array for your useEffect() within your Header component:
useEffect(() => {
const user = localStorage.getItem("user");
if (user) {
setUser(JSON.parse(user));
}
}, []); // <--- added empty dependency array
Without a dependeency array, your useEffect() callback will run after every render/rerender. So in your current code, this happens:
React renders your JSX
Your useEffect() callback is called, calling setUser if the user key in your local storage is set
As setUser() was called with a new object reference (a new object is created when doing JSON.parse()), React sees this as an update and triggers a rerennder, and so we go back to step 1 above, causing an infinite loop.
By adding an empty dependency array [] to the useEffect() call, you're telling React to only call your function on the initial mount, and not subsequent rerenders.
Related
This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 16 days ago.
I make an ecommerce site
I am trying to display the page with the details of a product:
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import axios from "axios";
import Layout from "../components/Layout";
const ProductPage = () => {
const router = useRouter();
const [product, setProduct] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const response = await axios.get(`/api/products?id=${router.query.id}`);
const currentProduct = response.data;
setLoading(false);
setProduct(currentProduct);
} catch (error) {
console.error(error);
}
};
fetchProduct();
}, [router.query.id]);
return (
<Layout title={product.name}>
{loading ? (
<div className="text-center">Loading...</div>
) : (
<div className="max-w-lg mx-auto p-5">
<h1 className="text-2xl font-bold mb-5">{product.name}</h1>
<img
src={product.imageUrl}
alt={product.name}
className="w-full mb-5"
/>
<p className="mb-5">{product.description}</p>
<p className="text-xl font-bold mb-5">
Price: {product.price}
</p>
<Link href="/" legacyBehavior>
<a className="btn btn-primary">Go back to the products list</a>
</Link>
</div>
)}
</Layout>
);
};
export default ProductPage;
The values are not displayed so I put some console.log
When I put a console.log in the "try" product returns an empty object, and when I put the console.log after the useffect the product returns an object with my values
You need to set the "isLoading" state in false whene the "product" state when the state has finished setting. In order to do that you need to add 1 more useEffect to listen for changes in the product state, for that you must add the "product" state as a dependency of the useEffect.
Try the next code:
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import axios from "axios";
import Layout from "../components/Layout";
const ProductPage = () => {
const router = useRouter();
const [product, setProduct] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const response = await axios.get(`/api/products?id=${router.query.id}`);
const currentProduct = response.data;
setProduct(currentProduct);
} catch (error) {
console.error(error);
}
};
fetchProduct();
}, [router.query.id]);
useEffect(() => {
setLoading(false);
}, [product]);
return (
<Layout title={product.name}>
{loading ? (
<div className="text-center">Loading...</div>
) : (
<div className="max-w-lg mx-auto p-5">
<h1 className="text-2xl font-bold mb-5">{product.name}</h1>
<img
src={product.imageUrl}
alt={product.name}
className="w-full mb-5"
/>
<p className="mb-5">{product.description}</p>
<p className="text-xl font-bold mb-5">Price: {product.price}</p>
<Link href="/" legacyBehavior>
<a className="btn btn-primary">Go back to the products list</a>
</Link>
</div>
)}
</Layout>
);
};
export default ProductPage;
useState hook does not update the state value instantly. There are a lot of things happening when you change the state, like batching state updates, recomputing DOM manipulations, re-rendering components, etc.
If you want to know what is going to be set as your state, log the argument that you are passing to the setProduct function. You may also use React dev tools.
I've built a todo app. I'm using nextjs as a framework, tailwind for css, and firebase to authenticate users to login via Google. I've got a component that adds todo items (called ToDoEntry.jsx), a component that shows the resulting todos which can be edited or deleted (called ToDoCard.jsx), and a Navbar component with a login and logout functionality called Navbar.jsx. All of these components show up on the same page called page.jsx, where ToDoCard.jsx and ToDoEntry.jsx only show up if I am logged via the Google login in from the navbar. I've also got a firebase configuration file called fbconfig.js, and a login authentication file called AuthHelper.js to help manage the user's login state.
The functionality worked JUST FINE before but I wanted to hide the ToDoEntry.jsx and ToDoCard.jsx components behind the login feature. I wanted to make it such page.jsx checks if the user is logged in before rendering those components.
Below is the last version of what the app looked like on vercel before I broke it:
https://todo-app-jet-pi.vercel.app/
My current problem is that after adding the AuthHelper.js and importing it into the Navbar.jsx, the login button in the navbar is no longer working, and the user is stuck on the main page. Clicking the button does not do anything, and the signInWithPopup method I used to have directly on the Navbar (but now in Authhelper.js) no longer works. I've pasted below the page.jsx, Navbar.jsx, fbconfig.js (minus the actual config details) and AuthHelper.js. Can someone tell me why the login button is not working?
P.S.: the firebase configuration and the login popup was working 100% just fine until I tried to hide components behind a login screen.
page.jsx
import ToDoEntry from "#/components/ToDoEntry";
import ToDoCard from "#/components/ToDoCard";
import Navbar from "#/components/Navbar";
import { useState } from "react";
export default function Home() {
const [loggedIn, setLoggedIn] = useState(false);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
return (
<div>
<Navbar setLoggedIn={setLoggedIn} setUser={setUser} />
{loading ? (
<div>Loading...</div>
) : loggedIn ? (
<>
<ToDoEntry user={user} />
<ToDoCard user={user} />
</>
) : (
<div className="flex flex-col items-center justify-top h-screen mt-10">
<h1 className="text-center text-4xl font-medium">
✏️ Welcome to{" "}
<span className="underline decoration-blue-500/30">
Walid's to-do list!
</span>{" "}
</h1>
<p className="mt-3 font-medium py-2 px-4">
Please login to continue.
</p>
</div>
)}
</div>
);
}
Navbar.jsx
import { FaGoogle, FaSignOutAlt } from "react-icons/fa";
import { auth } from "../app/fbconfig";
import { handleLogin } from "../app/AuthHelper";
export default function Navbar({ setLoggedIn, setUser }) {
const [user, setAuthUser] = useState(null);
useEffect(() => {
auth.onAuthStateChanged((user) => {
if (user) {
setAuthUser(user);
setLoggedIn(true);
setUser(user);
} else {
setAuthUser(null);
setLoggedIn(false);
setUser(null);
}
});
}, [setLoggedIn, setUser]);
const handleLogout = async () => {
try {
await auth.signOut();
setAuthUser(null);
setLoggedIn(false);
} catch (error) {
console.error(error);
}
};
const handleNavbarLogin = async () => {
const user = await handleLogin();
if (user) {
setAuthUser(user);
setLoggedIn(true);
setUser(user);
}
};
return (
<nav className="bg-blue-500">
<div className="flex p-4 justify-center">
{user ? (
<div className="flex">
<div className="flex items-center mr-3 text-white">
{user ? "Welcome, " + user.displayName : ""}
</div>
<button
className="flex bg-white text-blue-500 font-medium p-2 rounded-lg justify-items-center hover:bg-blue-800 hover:text-white transition-left duration-500 ease-in-out"
onClick={handleLogout}
>
<p className="mr-1">Logout</p>{" "}
<FaSignOutAlt className="my-auto" />
</button>
</div>
) : (
<button
className="flex bg-white text-blue-500 font-medium p-2 rounded-lg justify-items-center hover:bg-blue-800 hover:text-white transition-left duration-500 ease-in-out"
onClick={handleNavbarLogin}
>
<p className="mr-1">Login with</p> <FaGoogle className="my-auto" />
</button>
)}
</div>
</nav>
);
}
AuthHelper.js
const handleLogin = async () => {
try {
const result = await auth.signInWithPopup(provider);
return result.user;
} catch (error) {
console.error(error);
return null;
}
};
export { handleLogin };
fbconfig.js
import { getAuth, GoogleAuthProvider } from "firebase/auth";
const firebaseConfig = {
apiKey: "XXX",
authDomain: "XXX",
projectId: "XXX",
storageBucket: "XXX",
messagingSenderId: "XXX",
appId: "XXX",
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const provider = new GoogleAuthProvider();
export { auth, provider };
Here's the console error I get in runtime when I try to click login:
react_devtools_backend.js:4012 TypeError: _fbconfig__WEBPACK_IMPORTED_MODULE_0__.auth.signInWithPopup is not a function
at handleLogin (webpack-internal:///(:3000/app-client)/./app/AuthHelper.js:9:74)
at handleNavbarLogin (webpack-internal:///(:3000/app-client)/./components/Navbar.jsx:47:88)
The Authhelper.js was not working the way it should because I wrote
const result = await auth.signInWithPopup(provider);
instead of
const result = await signInWithPopup(auth, provider);
import { signInWithPopup } from "firebase/auth";
const handleLogin = async () => {
try {
const result = await signInWithPopup(auth, provider);
return result.user;
} catch (error) {
console.error(error);
return null;
}
};
export { handleLogin };
I made a custom hook(useData) that takes query as an argument and then returns data and runtime(time to fetch the data from the API). But I need access to the runtime to my Editor component when I click on the run button. Right now what is happening is when I click on the run button(inside Editor.js), it sets the query to the App component using the setter function, and then it passes that query to the Table component and then calls the custom hook using that query and then table make use of that data. but I want the runtime in the Editor component, not in the Table component. I know I can call the useData hook in the Editor component but my editor component gets rerender every time when we write on the editor, so It calls the useData() hook on each change.
Any way to get the runtime without calling useData() on every change?
App.js code
import React, { useState } from "react";
import "./assets/output.css";
import Footer from "./components/layouts/Footer";
import Navbar from "./components/layouts/Navbar";
import Sidebar from "./components/layouts/Sidebar";
import TableSection from "./components/table/TableSection";
import Editor from "./components/editor/Editor";
const App = () => {
const [query, setQuery] = useState("");
const [value, setValue] = useState("select * from customers");
return (
<>
<div className="grid grid-cols-layout-desktop grid-rows-layout-desktop bg-gray-600 h-screen">
<Navbar />
<Sidebar setQuery={setQuery} setValue={setValue} />
<Editor setQuery={setQuery} value={value} setValue={setValue} />
{query ? <TableSection query={query} /> : null}
<Footer />
</div>
</>
);
};
export default App;
Editor.js
import React from "react";
import AceEditor from "react-ace";
import "ace-builds/src-min-noconflict/ext-language_tools";
import "ace-builds/src-min-noconflict/mode-mysql";
import "ace-builds/src-noconflict/theme-github";
import useData from "../../hooks/useData";
const Editor = ({ setQuery, value, setValue }) => {
const { runtime } = useData();
const onChange = (newValue) => {
setValue(newValue);
};
const onSubmit = () => {
var Z = value.toLowerCase().slice(value.indexOf("from") + "from".length);
setQuery(Z.split(" ")[1]);
};
return (
<div className="col-start-2 col-end-3 row-start-2 row-end-3 m-6">
<AceEditor
aria-label="query editor input"
mode="mysql"
theme="github"
name={Math.floor(Math.random() * 100000).toString()}
fontSize={16}
minLines={15}
maxLines={10}
width="100%"
showPrintMargin={false}
showGutter
placeholder="Write your Query here..."
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
value={value}
onChange={onChange}
showLineNumbers
/>
<div className="">
<button
className="bg-white text-gray-800 rounded-md font-semibold px-4 py-2 my-4"
onClick={onSubmit}
>
<i className="fas fa-play"></i> Run SQL
</button>
</div>
</div>
);
};
export default Editor;
Hook code:
import { useEffect, useState } from "react";
import alasql from "alasql";
import toast from "react-hot-toast";
import TABLE_NAMES from "../utils/tableNames";
const getURL = (name) =>
`https://raw.githubusercontent.com/graphql-compose/graphql-compose-examples/master/examples/northwind/data/csv/${name}.csv`;
const useData = (tableName) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [runtime, setRuntime] = useState("");
const convertToJson = (data) => {
alasql
.promise("SELECT * FROM CSV(?, {headers: false, separator:','})", [data])
.then((data) => {
setData(data);
toast.success("Query run successfully");
})
.catch((e) => {
toast.error(e.message);
});
};
const fetchData = (tableName) => {
setData([]);
const name = TABLE_NAMES.find((name) => name === tableName);
if (name) {
setError(false);
fetch(getURL(tableName))
.then((res) => res.text())
.then((data) => convertToJson(data));
} else {
setError(true);
toast.error("Please enter a valid query");
}
};
useEffect(() => {
let t0 = performance.now(); //start time
fetchData(tableName);
let t1 = performance.now(); //end time
setRuntime(t1 - t0);
console.log(
"Time taken to execute add function:" + (t1 - t0) + " milliseconds"
);
}, [tableName]);
return { data, runtime, error };
};
export default useData;
I have a React project where the parent component (functional) holds state to determine what formatting is applied to a list. When the user clicks a button, a modal is generated - I pass an anonymous function to that modal as props (onMainButtonClick), which when called flips the state and changes the formatting of the list (logic for this is in parent component).
When I use the button in my modal component (onClick={() => onMainButtonClick()}), the code works as expected. However, I would also like an enter press to trigger this. Therefore I have the following code implemented for this, but it doesn't function as expected. The modal closes and the function fires (I know this as I put a console log in there...) but the state that impacts the formatting is not changed.
EDIT: Having made a proposed change below (to memo'ize the onEnterPress function so it gets removed properly), here's the full code for the modal:
import { React, useRef, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
const Modal = ({
title,
description,
isOpen,
onClose,
onMainButtonClick,
mainButtonText,
secondaryButtonText,
closeOnly,
}) => {
const node = useRef();
const onEnterPress = useCallback(
(e) => {
if (e.key === "Enter") {
onMainButtonClick();
}
},
[onMainButtonClick]
);
useEffect(() => {
window.addEventListener("keydown", onEnterPress);
return () => {
window.removeEventListener("keydown", onEnterPress);
};
}, [onMainButtonClick]);
const handleClickOutside = (e) => {
if (node.current.contains(e.target)) {
return null;
}
onClose();
};
if (!isOpen) return null;
return ReactDOM.createPortal(
{// JSX here, removed for brevity},
document.body
);
};
export default Modal;
And the code for the parent (which shows the modal and passes down onMainButtonClick):
import { React, useState } from "react";
import { decode } from "html-entities";
import useList from "../../queries/useList";
import useBuyItem from "../../mutations/useBuyItem";
import Header from "../Shared/Header";
import Description from "../Shared/Description";
import ListItem from "./ListItem";
import BuyOverlay from "./BuyOverlay";
import Modal from "../Modal";
import ViewOperations from "./ViewOperations";
const View = (props) => {
const [buyOverlayOpen, setBuyOverlayOpen] = useState(false);
const [viewBuyersOverlayOpen, setBuyersOverlayOpen] = useState(false);
const [viewBuyers, setViewBuyers] = useState(false);
const [buyItemId, setBuyItemId] = useState();
const { isLoading, isError, data, error } = useList(props.match.params.id);
const buyItemMutation = useBuyItem(buyItemId, props.match.params.id, () =>
setBuyOverlayOpen(false)
);
if (isLoading) {
return <div>Loading the list...</div>;
}
if (isError) {
return <div>An error occured, please refresh and try again</div>;
}
return (
<div className="w-full">
<div className="mb-10 text-gray-600 font-light">
<Header text={data.name} />
<Description text={data.description} />
<ViewOperations
toggleViewBuyers={() => setViewBuyers(!viewBuyers)}
toggleBuyersOverlay={() =>
setBuyersOverlayOpen(!viewBuyersOverlayOpen)
}
viewBuyers={viewBuyers}
/>
<div id="list" className="container mt-10">
{data.items.length === 0
? "No items have been added to this list"
: ""}
<ul className="flex flex-col w-full text-white md:text-xl">
{data.items.map((item) => {
return (
<ListItem
item={decode(item.item)}
description={decode(item.description)}
itemId={item._id}
isBought={decode(item.bought)}
boughtBy={decode(item.boughtBy)}
boughtDate={decode(item.boughtDate)}
viewlink={item.link}
handleBuy={() => {
setBuyItemId(item._id);
setBuyOverlayOpen(true);
}}
key={item._id}
viewBuyers={viewBuyers}
/>
);
})}
</ul>
</div>
</div>
<BuyOverlay
isOpen={buyOverlayOpen}
message="Message"
onClose={() => setBuyOverlayOpen(false)}
onConfirmClick={(buyerName) => {
buyItemMutation.mutate(buyerName);
}}
/>
<Modal
title="Title"
description="Description"
isOpen={viewBuyersOverlayOpen}
onClose={() => {
setBuyersOverlayOpen(false);
}}
onMainButtonClick={() => {
setViewBuyers(true);
setBuyersOverlayOpen(false);
}}
mainButtonText="OK"
secondaryButtonText="Cancel"
/>
</div>
);
};
export default View;
Any ideas for why this is happening? I have a feeling it might be something to do with useEffect here, but I'm a bit lost otherwise...
onEnterPress is most probably directly defined in your functional component and that's why its reference changes every time your component re-renders. Your useEffect closes over this newly defined function on each render so your handler is not going to work as you expected.
You can wrap your onEnterPress with useCallback to memoize your handler onEnterPress so its definition stays the same throughout each render just like this:
const onEnterPress = useCallback(e => {
if (e.key === "Enter") {
onMainButtonClick();
}
}, [onMainButtonClick]); // Another function dep. You can also wrap it with useCallback or carry over its logic into the callback here
useEffect(() => {
window.addEventListener('keydown', onEnterPress);
return () => {
window.removeEventListener('keydown', onEnterPress);
};
}, [onEnterPress]);
I figured this out in the end - turns out it was as simple as my enterPress callback needing a preventDefault statement before the if block (e.preventDefault()).
There must have been something else on the page that was capturing the enter press and causing it to behave strangely.
I am using axios for communicate with server.I want to show loader when user request to server and hide the loader when request is complete
So i make a custom component to do this task .but my UI hang when I click multiple times on same button
const Loader = () => {
const { loadingCount } = useLoadingState(),
{showLoading, hideLoading} = useLoadingActions();
useEffect(()=>{
const self = this
axios.interceptors.request.use(function (config) {
showLoading();
return config
}, function (error) {
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
// spinning hide
// self.props.loading(false)
hideLoading()
return response;
}, function (error) {
hideLoading();
return Promise.reject(error);
});
})
return (
<div>
{loadingCount > 0 ?<div style={{position:"fixed",display:"flex",justifyContent:"center",alignItems:"center",width:'100%',height:'100%',zIndex:999999}}>
{/*{loadingCount > 0 ? */}
<Spin tip="Loading..." style={{zIndex:999999}}></Spin>
{/*: null}*/}
</div>: null}
</div>
);
};
Problem is on useeffect
when I comment out useEffect code it works perfectly .
NoTe : showloading and hideloading increase and decrease the loading count.
I think I have deallocate axios object the when component is unmount.???
Add empty array to sencod parameter to useEffect.
It works like componentDidMount() in functional component.
const { useState, useEffect } = React;
const Counter = () => {
const [count, setCount] = useState(0)
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
setTimeout(() => {
setIsLoaded(true);
}, 3000);
}, []); // here
return (
<div>
{
isLoaded &&
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
}
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="app"></div>
i usualy use this code to show loading when request data is processing and hide when it's done
const Loader = () => {
const {data, setdata} = useState([])
useEffect(()=>{
axios.get('your host').then(res => {
setdata(res.data);
}).catch(err => {
setdata(res.data);
}
});
return (
<div>
{data.length > 0
?
<div style={{position:"fixed",display:"flex",justifyContent:"center",alignItems:"center",width:'100%',height:'100%',zIndex:999999}}> </div>
:
<Spin tip="Loading..." style= {{zIndex:999999}}>
</Spin>
</div>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
First I created Loading components in shared folder. I am using Daisy UI, that's why you have to first install tailwind & daisy otherwise it will not work. My Loading Code:
import React from 'react';
const Loading = () => {
return (
<div className="flex items-center justify-center ">
<div className="w-16 h-16 border-b-2 border-gray-900 rounded-full animate-spin"></div>
</div>
);
};
export default Loading;
Then I am using this Loading component in my Allproduct component. For viewing Loading i created Reload useState.You will see below in my code, that will help my loader show when fetching time is very long.
import React, { useEffect, useState } from 'react';
import Loading from '../Shared/Loading';
import AllProduct from './AllProduct';
const AllProducts = () => {
const [products, setProduct]=useState([])
const [Reload, setReload] = useState(true);
useEffect(()=>{
fetch('https://stormy-hamlet-97462.herokuapp.com/products/')
.then(res=>res.json())
.then(data=>{setProduct(data)
setReload(false)})
},[])
if(Reload){
return <Loading></Loading>
}
return (
<div>
<h4 className='text-4xl text-primary text-center sm:w-full px-32 mx-5
lg:my-12 '>All Products</h4>
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5'>
{
products.map(product=><AllProduct key={product._id} product={product} ></AllProduct>)
}
</div>
</div>
);
};
export default AllProducts;