Hi all, I was hoping for some help with the Random Quote Machine task. I have finished, but I made a couple hackish tweaks in order to get around the issue I’m having. The code is available at CodePen at https://codepen.io/anth-volk/pen/vYaYzJe or below:
JavaScript
const App = () => {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
const [quote, setQuote] = React.useState({
quote: "",
author: "",
index: null
});
const [backgroundColor, setBackgroundColor] = React.useState("");
const src = "https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json";
// Function that chooses random quote from FCC quote list
const randomQuote = () => {
let randomizer = Math.floor(Math.random() * data.quotes.length);
const quoteObj = data["quotes"][randomizer];
setQuote({
quote: quoteObj.quote,
author: quoteObj.author,
index: randomizer
});
}
// Function that chooses random color from array declared inside function
const randomColor = () => {
// Array of colors
const backgroundColors = [
"#ac3b61",
"#5783c9",
"#b06d25",
"#93b88f",
"#b29bc2",
"#750204"
];
const index = Math.floor(Math.random() * backgroundColors.length);
setBackgroundColor(backgroundColors[index]);
}
// Click handler wrapper for above two functions
const handleClick = () => {
randomQuote();
randomColor();
}
// Based on guide at https://www.freecodecamp.org/news/fetch-data-react/
React.useEffect(() => {
// Fetch https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json within useEffect and don't re-fetch unless src changes
fetch(src)
.then (response => {
if (response.ok) {
return response.json();
}
else {
throw response;
}
})
.then(data => {
setData(data);
setQuote(data.quotes[Math.floor(Math.random() * data.quotes.length)]);
randomColor();
})
.catch(error => {
console.log("Error: ", error);
setError(error);
})
.finally(() => {
setLoading(false);
});
}, [src]);
if (loading) {
return (
<p className="devMessage">Loading</p>
);
}
if (error) {
return (
<p className="devMessage">Error</p>
);
}
return (
<div id="quote-box">
<style> {`
:root {
--primary-color: ${backgroundColor};
`}
</style>
<div id="text">
<h1>{quote.quote}</h1>
</div>
<div id="author">
<p className="text-black">{quote.author}</p>
</div>
<div id="button-wrapper">
<a id="tweet-quote" target="_blank" href={`https://www.twitter.com/intent/tweet/?text=${quote.quote}`}>
<i className="fa-brands fa-square-twitter"></i>
</a>
<button className="button" onClick={handleClick}>New Quote</button>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
HTML
<div id="root">
</div>
<div id="attribution">
<p>By <span class="italic white"><a href="https://www.github.com/anth-volk">anth-volk</a></span></p>
</div>
CSS
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300&family=Unbounded:wght@300&display=swap');
/* Root vars */
:root {
--max-quote-box-width: 700px;
--h1-font-family: 'Unbounded', cursive;
--h1-font-size: 1.75em;
--h1-font-weight: 300;
--p-font-family: 'Nunito', sans-serif;
--p-font-size: 1.25em;
--p-font-weight: 200;
/* Fallback primary color */
--primary-color: #ac3b61;
--secondary-color: white;
}
/* CSS reset */
* {
box-sizing: border-box;
}
html,
body,
h1,
h2,
h3,
h4,
h5,
p {
margin: 0;
padding: 0;
}
a {
text-decoration: none;
color: unset;
}
/* Begin styles */
html {
background-color: var(--primary-color);
}
body {
height: 100vh;
width: 100vw;
margin: 50px auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
h1 {
color: var(--primary-color);
font-size: var(--h1-font-size);
font-family: var(--h1-font-family);
font-weight: var(--h1-font-weight);
}
p {
color: var(--secondary-color);
font-size: var(--p-font-size);
font-family: var(--p-font-family);
font-weight: var(--p-font-weight);
}
p.devMessage {
font-family: monospace;
}
i {
display: inline-block;
font-size: calc(1.75 * var(--p-font-size) + 4px);
color: var(--primary-color);
}
.button {
font-size: var(--p-font-size);
font-family: var(--p-font-family);
padding: calc(0.125 * var(--p-font-size)) 0.5vw;
border: 1px solid var(--primary-color);
border-radius: 2px;
background-color: var(--primary-color);
color: var(--secondary-color);
}
.italic {
font-style: italic;
}
.text-black {
color: black;
}
#quote-box {
width: 80vw;
max-width: var(--max-quote-box-width);
background-color: #fafafa;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
border-radius: 4px;
}
#text {
width: 100%;
height: auto;
padding: 2vw 1.5vw 0 1.5vw;
text-align: center;
}
#author {
font-style: italic;
width: 100%;
height: auto;
padding: 1.5vw 1.5vw 0 0;
text-align: right;
}
#author p::before {
content: "- ";
}
#button-wrapper {
width: 100%;
height: auto;
padding: 3vw 1.5vw 1.5vw 1.5vw;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
#attribution {
width: var(--quote-box-width);
min-height: 100px;
padding: 1vw 0 0 0;
}
What I’m trying to understand is this: the randomQuote() function that I wrote is able to update the “quote” state variable when called outside of the Effect hook, but when I tried to utilize it to set the quote in the second .then() after fetching the data, it never worked, and I ended up coding its function into the setQuote call in that block. Why would that be? My theory is that it has to do with how React Effect hooks schedule DOM re-rendering, but is that accurate? And if so, how could I circumvent this in the future?
Also, any general comments about the code are also appreciated. Thank you very much.