React Hooks: Loading state displaying twice - javascript

I have an isLoading useState variable. In the code below purposefully commented out the isLoading(false) to force the Loading UI to test it. However I'm seeing it render twice, any idea why?
API Call
export default function App() {
const [upcoming, setUpcoming] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchUpcoming = async () => {
setIsLoading(true);
const res = await fetch(
"https://api.themoviedb.org/3/movie/upcoming?api_key={API_KEY}&language=en-US&page=1"
);
const data = await res.json();
const results = data.results;
setUpcoming(results);
// setIsLoading(false);
};
fetchUpcoming();
}, []);
return (
<div className="App">
<Recommendations title={"Upcoming"} data={upcoming} loading={isLoading} />
</div>
);
}
Render Results
export default function Recommendations({ title, data, loading }) {
return (
<div className="recommendationSection">
<h3>{title}</h3>
{loading ? (
<h3>Loading...</h3>
) : (
data.map((movie) => {
return (
<div className="banner" key={movie.title}>
<img
src={
movie.poster_path
? `https://image.tmdb.org/t/p/original/${movie.poster_path}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={movie.title}
/>
</div>
);
})
)}
</div>
);
}

Found the answer here: https://github.com/kenwheeler/slick/issues/940#issuecomment-181815974
I found the second instance had a class of slick-cloned and basically since i'm using Slick Slider, it's making a clone of my element. The fix is to add infinite: false in my slider settings.

Related

Component renders and gives an error before the data is completely loaded from the API

I am pulling data from a crypto coin API. It has 250 coins data in one request. But if I load all of them, the data is not loaded and component tries to render which gives an error. I am following the regular practice of await and useEffect but still the error is persistent.
const Home = () => {
const [search, setSearch] = useContext(SearchContext);
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const getCoinsData = async () => {
try {
const response = await Axios.get(
`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=100&page=1&sparkline=true&price_change_percentage=1h%2C24h%2C7d`
);
setData(response.data);
setLoading(false);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
getCoinsData();
}, []);
const negStyle = {
color: "#D9534F",
};
const positiveStyle = {
color: "#95CD41",
};
return (
<div className="home">
<div className="heading">
<h1>Discover</h1>
<hr className="line" />
</div>
{!loading || data ? (
<div style={{ width: "100%", overflow: "auto" }}>
<table *the entire table goes here* />
</div>
) : (
<img className="loading-gif" src={Loading} alt="Loading.." />
)}
</div>
);
};
export default Home;
This is the entire code. Still when I try to refresh, it gives errors based on how much data loads. Sometimes, .map function is not defined or toFixed is defined etc. It does not keep loading till the whole data is loaded.
Can you show the errors and how did you initialize your state loading and data so we can debug better ?
Otherwise, what I usually do in this case is:
if (!loading && data) return <Table />;
return <img className="loading-gif" ... />;
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
import axios from "axios";
import React from "react";
import { Image } from "#chakra-ui/image";
const Crypto = () => {
const { data, isLoading } = useQuery("crypto", () => {
const endpoint =
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=80&page=1&sparkline=true&price_change_percentage=1h%2C24h%2C7d";
return axios.get(endpoint).then(({ data }) => data);
});
return (
<>
{!isLoading && data ? (
data?.map((e, id) => <Image key={id} src={e.image} />)
) : (
<p>Loading</p>
)}
</>
);
};
export default function App() {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<Crypto />
</QueryClientProvider>
);
}
CodeSandBox Link, Preview
enter image description here

Problem displaying an item according to the url - React

I have a question. I have a component that when entering /category/:categoryId is rendered doing a fecth to "api url + categoryId". My problem is that if I change from one category to another the page only changes if the useEffect is executed infinitely which generates problems to the view as seen below. How can I make it run once and when I change from /category/1 to /category/2 the useEffect is executed correctly?
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;
My router file:
<Route path="/category/:categoryId" component={Categories} />
This is the problem that is generated, there comes a time when a fetch is made to a previously requested category and then the new requested category is executed.
See my problem in video
You can simply add categoryId to useEffect array argument. Function inside the useEffect is called, only when categoryId changes
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[categoryId]);
you can not edit producto directly, you should use productos :
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto && producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;

Why useEffect runs every time when component re-render?

In my Home component(I call it Home Page!) I am using Cards.JS component which has posts attribute as shown in following code.
const Home = () => {
const dispatch = useDispatch()
const isLoading = useSelector(state => state.isLoading)
const currentPage = useSelector((state) => state.idFor.currentPageHome)
const homePosts = useSelector((state) => state.posts)
useEffect(() => {
dispatch(setIsLoading(true))
dispatch(getAllPosts(currentPage))
}, [dispatch, currentPage])
return (
isLoading ? (
<Loader type="ThreeDots" color="#000000" height={500} width={80} />
) : (
<Cards posts={homePosts} setCurrentPage={setCurrentPageHome} currentPage={currentPage} pageName={"LATEST"} />
)
)
}
And Cards.Js is as following
const Cards = ({ posts, setCurrentPage, currentPage, pageName }) => {
console.log('Cards.JS called', posts);
const dispatch = useDispatch()
useEffect(() => {
dispatch(setIsLoading(false))
})
const handleNextPage = () => {
dispatch(setIsLoading(true))
dispatch(setCurrentPage(currentPage + 1))
}
const handlePreviousPage = () => {
dispatch(setIsLoading(true))
dispatch(setCurrentPage(currentPage - 1))
}
return (
<div className="container">
<h4 className="page-heading">{pageName}</h4>
<div className="card-container">
{
posts.map(post => <Card key={post._id} post={post} />)
}
</div>
<div className="page-div">
{currentPage !== 1 ? <span className="previous-page" onClick={handlePreviousPage}><</span>
: null}
<span className="next-page" onClick={handleNextPage}>></span>
</div>
</div>
)
}
My Problem:
When i come back to home page useEffect is called everytime and request same data to back-end which are already avaliable in Redux store.
Thanks in Advance :)
useEffect will run every time the component rerenders.
However, useEffect also takes a second parameter: an array of variables to monitor. And it will only run the callback if any variable changes in that array.
If you pass an empty array, it will only run once initially, and never again no matter how many times your component rerenders.
useEffect(() => {
dispatch(setIsLoading(false))
}, [])

UseEffect API Request not displaying on page load

Apologies in advance if similar questions like this have already been answered. I have tried everything, but still cannot figure out why I am experiencing this small bug.
I want this collection of tweets from my Firestore to render on the page when it loads. Right now, it only happens after I make a post request.
This is my request
useEffect(() => {
const getTweets = async () => {
const tweets = await firestore.collection('tweet').get();
tweets.forEach(doc => {
results.push(doc.data());
})
}
getTweets()
}, [])
This is where I'm mapping it to the page:
return (
<>
<main className="tweet-container">
<div className="tweet-container--content">
{results.map((tweet, index) => (
<InputResults
key={index}
tweet={tweet}
/>
))}
</div>
</main>
</>
)
}
Thank you so much
Try something like this,
import React, {useState, useEffect} from "react";
function App() {
const [results, setResults] = useState([]);
useEffect(() => {
const getTweets = async () => {
const tweetsData = [];
const tweets = await firestore.collection('tweet').get();
tweets.forEach(doc => {
tweetsData.push(doc.data());
})
setResults(tweetsData);
}
getTweets()
}, [])
return (
<>
<main className="tweet-container">
<div className="tweet-container--content">
{results.map((tweet, index) => (
<h1>{tweet}</h1>
))}
</div>
</main>
</>
)
}
Reference: https://reactjs.org/docs/hooks-state.html
Always add a key property to the elements when used a map function to show them on UI .
As it would help react to differentiate between the elements .
return (
<>
<main className="tweet-container">
<div className="tweet-container--content">
{results.map((tweet, index) => (
<h1 key={index} >{tweet}</h1>
))}
</div>
</main>
</>
)

How to move state outside of component using context provider

I currently have a preview component which has a reloading functionality attached into it using the useState hook. I now want the ability to refresh this component with the same functionality but with an external component. I know that this can be achieved by the useContext API, however i'm struggling to plug it all together.
Context:
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
const PreviewProvider = PreviewContext.Provider;
PreviewFrame:
const PreviewFrame = forwardRef((props, ref) => {
const { height, width } = props;
const classes = useStyles({ height, width });
return (
<Card className={classes.root} ref={ref}>
<div className={classes.previewWrapper} > {props.children} </div>
<div className={classes.buttonContainer}>
<IconButton label={'Refresh'} onClick={props.toggleReload} />
</div>
</Card>
);
});
PreviewFrameWrapped:
<PreviewFrame
toggleReload={props.toggleReload}
height={props.height}
width={props.width}
ref={frameRef}
>
<PreviewDiv isReloading={props.isReloading} containerRef={containerRef} height={height} width={width} />
</PreviewFrame>
const PreviewDiv = ({ isReloading, containerRef, height, width }) => {
const style = { height: `${height}px`, width: `${width}px`};
return !isReloading ?
<div className='div-which-holds-preview-content' ref={containerRef} style={style} />
: null;
};
Preview:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
So now i want to just be able to import the preview component and be able to refresh it using an external button, so not using the one that's already on the <PreviewFrame>.
I ideally want to consume it like this:
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>
function PreviewControls () {
let { handleRefresh } = React.useContext(PreviewContext);
return <div><button onClick={handleRefresh}>↺ Replay</button></div>
}
Preview With My Attempt at Wrapping with Provider:
export default function Preview(props) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return (<PreviewProvider value={{ reloading: reloading, setReloading: setReloading, handleRefresh: toggleReload }} >
<PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
{/* it works if i put the external button called <PreviewControls> here*/}
</PreviewProvider>
);
}
So yeah as i said in the commented out block, it will work if put an external button there, however then that makes it attached/tied to the Preview component itself, I'm really not sure how to transfer the reloading state outside of the Preview into the Provider. Can someone please point out what i'm missing and what i need to do make it work in the way i want to.
All you need to do is to write a custom component PreviewProvider and store in the state of reloading and toggleReload function there. The preview and previewControls can consume it using context
const PreviewContext = React.createContext({
handleRefresh: () => null,
reloading: false,
setReloading: () => null
});
export default function PreviewProvider({children}) {
const [reloading, setReloading] = useState(false);
useEffect(() => {
setReloading(false);
}, [ reloading ]);
const toggleReload = useCallback(() => setReloading(true), []);
return <PreviewContext.Provider value={{reloading, toggleReload}}>{children}</PreviewContext.Provider>
}
export default function Preview(props) {
const {reloading, toggleReload} = useContext(PreviewContext);
return <PreviewFrame isReloading={reloading} toggleReload={toggleReload} {...props} />
}
function PreviewControls () {
let { toggleReload } = React.useContext(PreviewContext);
return <div><button onClick={toggleReload}>↺ Replay</button></div>
}
Finally using it like
import { PreviewContext, PreviewProvider, Preview } from "../../someWhere"
<PreviewProvider>
<Preview />
<PreviewControls />
</PreviewProvider>

Categories