At the moment the votes {count}
is not showing up on mount. I’m using useEffect
to get the article and useState
to set the count
to article.votes
. The votes go up after clicking the button twice as the first render doesn’t show the vote count.
const Article = () => {
const { article_id } = useParams();
const [article, setArticle] = useState({ title: "Not Found" });
useEffect(() => {
getArticle(article_id).then((article) => {
setArticle(article);
});
}, []);
const [count, setCount] = useState(article.votes);
function handleCountUp() {
updateArticleVotes(article_id, count, article.votes);
setCount(article.votes++);
}
function handleCountDown() {
setCount(article.votes--);
updateArticleVotes(article_id, count, article.votes);
}
const navigate = useNavigate();
function handleNavigate() {
navigate("./#form");
}
return (
<div>
<div className="one-article-div" id="top">
<h5>Votes: {count}</h5>
<button
onClick={handleCountUp}
name="Vote Up"
className="input-submit margin-left-right"
>
Vote Up
</button>
<button
onClick={handleCountDown}
name="Vote Down"
className="input-submit margin-left-right"
>
Vote Down
</button>
</div>
</div>
);
};
export default Article;
I’m expecting the <h5>votes: {count}</h5>
to show up on mount. At the moment it only shows up after clicking the up vote or down vote.
Inside the useEffect you need to add a setCount(article.votes).
In your case, it doesn’t work because the useEffect run after the mounting of your component. So at that time articles is undefined so votes is also undefined. And the initializer of the count state is set to undefined.
useEffect(() => {
getArticle(article_id).then((article) => {
setArticle(article);
setCount(article.votes);
});
}, []);
At the moment the votes {count} is not showing up on mount
It would not show up, because here:
const [count, setCount] = useState(article.votes);
you have initialized count
with article.votes
.
Even if you change article.votes
to something else afterwards, the count
will not be re-initialized. From the docs:
The value you want the state to be initially. It can be a value of any
type, but there is a special behavior for functions. This argument is
ignored after the initial render.
You have to update count
too using setCount
, after you get new article
in useEffect
.
I’m expecting the
votes: {count}
to show up on mount.
It does display/render on the initial render cycle, but the problem is that the initial article
state has an undefined votes
property.
const [article, setArticle] = useState({
title: "Not Found" // no votes property
});
const [count, setCount] = useState(article.votes); // undefined
I suggest you provide a default votes
property value, and update the effect to also update the count
state when it updates the article
state. Don’t forget to include the article_id
route path parameter as a dependency so the effect also runs if/when the article id parameter value changes.
const [article, setArticle] = useState({
title: "Not Found",
votes: 0,
});
const [count, setCount] = useState(article.votes);
useEffect(() => {
getArticle(article_id)
.then((article) => {
setArticle(article);
setCount(article.votes);
});
}, [article_id]);
The vote handlers are also mutating the article
state with the post-increment/decrement operators. You should avoid mutating React states. Add or subtract 1 from the current article.votes
value and pass that result to the setCount
state setter function.
function handleCountUp() {
updateArticleVotes(article_id, count, article.votes);
setCount(article.votes + 1);
}
function handleCountDown() {
updateArticleVotes(article_id, count, article.votes);
setCount(Math.max(0, article.votes - 1));
}
The count
state appears to be “derived” state from the article
state, and typically is not recommended to also exist as React state. I suggest you update the code to update the article.votes
state value directly and avoid the effectively duplicate count
state.
Example:
const [article, setArticle] = useState({
title: "Not Found",
votes: 0,
});
useEffect(() => {
getArticle(article_id)
.then((article) => {
setArticle(article);
});
}, [article_id]);
...
function handleCountUp() {
updateArticleVotes(article_id, article.votes + 1);
setArticle(article => ({
...article,
votes: article.votes + 1,
}));
}
function handleCountDown() {
updateArticleVotes(article_id, Math.max(0, article.votes - 1);
setArticle(article => ({
...article,
votes: Math.max(0, article.votes - 1),
}));
}
...
return (
<div>
<div className="one-article-div" id="top">
<h5>Votes: {article.votes}</h5>
<button
onClick={handleCountUp}
name="Vote Up"
className="input-submit margin-left-right"
>
Vote Up
</button>
<button
onClick={handleCountDown}
name="Vote Down"
className="input-submit margin-left-right"
>
Vote Down
</button>
</div>
</div>
);