Hello, there, nice solution!
👉 #1 There are a couple of things I would do a bit differently which might be interesting for you as well.
Intead of directly calling fetch, I would place it inside a function and use it that way. This will enable you to run this fetch not only during the initial render but also on the button click. Because you need to enable the user to generate the new quote and it wouldn't a good choice to achieve that by making the user reload the page.
Besides, it's much better to separate things, this makes the code easier to read. Instead of chaining usually it's more recommended to use async/await which is much easier to read. Maybe here it's not so much code but in more complex situations it can help a lot.
Finally, you are reusing setLoading(false);
in the same fetch twice, which is redundant. You can simply move this to the finally
block. This block comes at the very end of the chain that will execute no matter the result. So this works great in this scenario.
As a result, instead of this:
useEffect(()=> {
generateRandomNumber();
fetch('https://catfact.ninja/fact')
.then(response => response.json())
.then(data => {setQuote(data);
setLoading(false);
})
.catch(error => {console.error('Error:', error);
setLoading(false);
});
}, [])
We would have:
const fetchQuote = async () => {
try {
generateRandomNumber();
const response = await fetch('https://catfact.ninja/fact');
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
const data = await response.json();
setQuote(data);
} catch (error) {
console.error('Error fetching quote:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchQuote();
}, []);
This will also give you a warning in the useEffect
dependency array, the []
at the very end. Here we usually pass states that might change eventually and this helps React to understand when to re-render and show the updated state. If we call fetchQuote
it will add a warning that you need to add it in order to monitor its change.
But the thing is that if we pass this function, it will keep re-rendering all the time. Because functions are objects and when component re-renders, it will create a new reference and treated it as a change. A change will make the useEffect re-render the component which result in infinite re-renders.
To avoid the re-renders and prove React it's always the same function, you can use another hook called useCallback
. What it does that it will cache this function, store it in the memory during the first render.Then component might re-render again because we change the state of the quote but it will reuse the same function.
As as result we have something like this:
const fetchQuote = useCallback(async () => {
try {
generateRandomNumber();
const response = await fetch("https://catfact.ninja/fact");
if (!response.ok)
throw new Error(`HTTP error! Status: ${response.status}`);
const data = await response.json();
setQuote(data);
} catch (error) {
console.error("Error fetching quote:", error);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchQuote();
}, [fetchQuote]);
As you see, useCallback
also has it's dependency array and it works the same way as in useEffect. It will determine whether it should re-create the function but in our case we don't need this. Our function call doesn't depend on any state.
A good example if we needed something is, let's say an API request that changes depending on the state. Let's say if in the fetch we had additional info on what type of quote we need, like category and we had state category const [category, setCategory] = useState("funny");
. And our fetch call is const response = await fetch(
https://api.example.com/quotes?category=${category});
. Here, if category changes we need to monitor its change and that's why we would need to pass the category to the useCallback
dep array.
We could also add fetch function right inside useEffect
and call it there but function will become local to this useEffect
and we can't reuse it outside. But we need to call the same function in the dice which generates another random quote.
This needs to be done here:
<button className='bg-[#52ffa8] w-14 rounded-full h-14 absolute -bottom-7 grid place-items-center'>
<img src="../public/icon-dice.svg" alt="" />
</button>
And to make the function be called we simply add onClick={fetchQuote}
👉 #2 Semantic-wise, it's also not recommended to omit the text completery in the button/a
tags and use only an image. You can add a span and hide the text from the UI using CSS, so when the person who uses screen reader will be notified about the purpose of this button.
👉 #3 In React you can't use images the way we do in Vanilla JS.
<img src="../public/pattern-divider-desktop.svg" className='mt-5 mb-5'/>
You will need default import and place the image that way. You would import at the very top first:
import Divider from "../public/pattern-divider-desktop.svg"
And then insert it:
<img src={Divider} className='mt-5 mb-5'/>
Do the same for <img src="../public/icon-dice.svg" alt="" />
I hope this helps you out in your future projects, good luck 🤞