Reputation: 1600
I'm trying to use React hooks (useState & useEffect) in a component to show a vote count. After the user clicks up/down the 'vote' state should be increment or decremented and then the app makes an API call to PATCH the "votes" count in the database. The original vote count is coming from props passed from the parent component. But for some reason, the initial state for the votes is always 0 -- even though when I console.log the props it shows the correct number of votes?
PATCH request seems to be working fine, but something with hooks I think is wrong. Thanks in advance for any help I can get on this!
export default function Votes(props) {
const { item, voteCount, itemType } = props
const [votes, setVotes] = useState(voteCount || 0)
useEffect(() => {
if (itemType == 'question') {
questionApiService.updateQuestionFields({
questionId: item.id,
questionFields : { votes: `${votes}` }
})
} else if (itemType === 'answer') {
console.log('answer')
// questionApiService.updateAnswerFields({
// answerId: item.id,
// answerFields: { votes: votes }
//})
}
}, [votes])
const handleClick = e => {
e.currentTarget.id === "increment"
? setVotes(prevCount => prevCount + 1)
: setVotes(prevCount => prevCount - 1)
}
return (
<div className="QuestionPage__votes-count">
<button id="increment" onClick={handleClick}>
<FontAwesomeIcon icon={faCaretUp} size="2x" />
</button>
{votes}
<button id="decrement" onClick={handleClick}>
<FontAwesomeIcon icon={faCaretDown} size="2x" />
</button>
</div>
)
}
Upvotes: 1
Views: 11530
Reputation: 1600
tobiasfried's answer below really helped me out. I'm posting the full solution code here in case it can help anyone else out in the future. And of course, if anyone has suggestions to improve this code I'd love to hear them.
export default function Votes(props) {
const { item, itemType } = props
const [votes, setVotes] = useState()
useEffect(() => {
setVotes(item.votes);
}, [item.votes])
useEffect(() => {
if (itemType == 'question') {
questionApiService.updateQuestionFields({
questionId: item.id,
questionFields : { votes: votes }
})
} else if (itemType === 'answer') {
// questionApiService.updateAnswerFields({
// answerId: item.id,
// answerFields: { votes: votes }
//})
}
}, [votes, itemType])
return (
<div className="QuestionPage__votes-count">
<button
onClick={() => setVotes(prevCount => prevCount + 1)}>
<FontAwesomeIcon icon={faCaretUp} size="2x" />
</button>
{votes}
<button
onClick={() => setVotes(prevCount => prevCount - 1)}>
<FontAwesomeIcon icon={faCaretDown} size="2x" />
</button>
</div>
)
}
Upvotes: 0
Reputation: 3270
import React from 'react';
import ReactDOM from 'react-dom';
function useCounter(initialCount = 0) {
const [count, setCount] = React.useState(initialCount);
const increment = React.useCallback(() => setCount(c => c + 1), []);
const decrement = React.useCallback(() => setCount(c => c - 1), []);
return { count, increment, decrement };
}
const delay = duration => new Promise(resolve => setTimeout(resolve, duration));
function App() {
const { count: votes, increment, decrement } = useCounter(0);
React.useEffect(() => {
(async () => {
console.log('Call your API');
await delay(2000);
console.log('API Called with votes :', votes);
})();
}, [votes]);
return (
<div>
<h1>Votes {votes}</h1>
<button onClick={decrement}>Decrement</button>
<button onClick={increment}>Increment</button>
</div>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
<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>
Upvotes: 1
Reputation: 359
You can use useState()
as @tobiasfreid said to change the counter state or if you want to create a custom hook to implement the same functionality.
function ChangeCounter() {
const [counter,onChangeValue] = useCounter(0);
return (
<div className="Form">
<button type="button" onClick={onChangeValue(counter + 1)}> Increase Counter </button>
</div>
);
}
function useCounter(initialValue){
const [value, setValue] = useState(initialValue || 0);
const onChange= (data) => {
setValue(data)
};
return [value, onChange];
}
In this way, you can create your custom hooks.
Upvotes: 1
Reputation: 1842
You need to add itemType
to the dependencies of useEffect
, since you can't expect the prop to be available on the very first render, or to remain static throughout the component lifecycle. With your current example, the itemType
referenced will always refer to its value at the very first time this function was run.
Also as others have mentioned, setting state initial value from props is a no-no. You can solve this by using a separate Effect to set state once your component receives its props:
...
const [votes, setVotes] = useState(0);
...
useEffect(() => {
setVotes(voteCount);
}, [voteCount]);
Upvotes: 3