I have a problem related to AfterVoteProgress component. After I import the Pool and vote for an Option, i render the afterVoteProgress that it’s a progress bar and some text.
I’ve encoutered a problem when the pool’s options are all at 0 and suddenly it sets to 1, the progress bar does not show me the correct parcentage. The state is updated but the maxParcentage prop is not.
How the data is parsed:
0: {id: 44, question_text: 'Here', votes: 0, maxParcentage: 0}
1: {id: 45, question_text: 'There', votes: 0, maxParcentage: 0}
2: {id: 46, question_text: 'Nowhere', votes: 0, maxParcentage: 0}
optionSelect.jsx
import BeforeVoteOption from './beforeVoteOption';
import AfterVoteProgress from './afterVoteProgress';
import { useState, useEffect } from 'react';
import Image from 'next/image'
import { useRouter } from 'next/router'
async function getPoolData(id) {
return fetch('https://xxxxxxxxxxxxx/api/pools/' + id + '/?format=json', {
method: 'GET',
})
.then((response) => response.json())
.then((pools) => {
const updatedOptions = pools.questions.map((option) => ({
...option,
maxParcentage: 0,
}));
return { ...pools, questions: updatedOptions };
});
}
export default function OptionSelect({question, options}) {
// Router
const router = useRouter()
const { slug } = router.query
// Global States
const [option, setOption] = useState([])
const [voteChoice, setVoteChoice] = useState(0)
const [hasVoted, setHasVoted] = useState(false)
const [totalVotes, setTotalVotes] = useState(0)
// Vote select function
useEffect(() => {
const fetchPoolData = async () => {
if (options) {
const value = await getPoolData(slug);
setOption(value.questions);
}
};
fetchPoolData();
}, [options, slug]);
// Before vote handlers
const onVoteOptionChange = e => {
setVoteChoice(e.target.value)
}
const handleVoteForm = async (event) => {
event.preventDefault()
if (!voteChoice == 0) {
const endpoint = 'https://xxxxxxxxxxxxxxx/api/questions/'+ voteChoice +'/addVote/'
const formUpload = {
method: 'POST',
headers: {'Content-Type': 'application/json',}
}
const response = await fetch(endpoint, formUpload)
const data = await response.json();
if (data.status === 'added') {
getPoolData(slug).then((value) => setOption(value.questions));
setHasVoted(true);
}
}
}
// Set Total Votes
useEffect(() => {
if (option) {
console.log(option)
setTotalVotes(0);
option.map((data) => {
setTotalVotes((prevVotes) => prevVotes + data.votes)
})
}
}, [option])
// After Vote Handlers
useEffect(() => {
if (totalVotes > 0) {
option.map(data => {
//console.log(data.votes)
//console.log(totalVotes)
const part = data.votes / totalVotes * 100
//console.log(part)
data.maxParcentage = part;
//console.log(data)
//console.log('-----------------')
})
}
}, [totalVotes])
//<AfterVoteProgress key={index} maxParcentage={data.maxParcentage} voteChoice={voteChoice} votes={data.votes} optionText={data.question_text}/>
return (
<form onSubmit={handleVoteForm} className='flex flex-col box-shadow rounded-3xl p-6 gap-2'>
<span className='font-semibold text-xl pb-1'>{question}</span>
{!hasVoted && option.map((data, index) => (<BeforeVoteOption key={index} index={index} id={data.id} optionText={data.question_text} onVoteOptionChange={onVoteOptionChange}/>))}
{hasVoted && option.map((data, index) => (console.log('showing'), <AfterVoteProgress key={index} maxParcentage={data.maxParcentage !== undefined ? data.maxParcentage : 0} voteChoice={voteChoice} votes={data.votes} optionText={data.question_text}/>))}
<div className='flex justify-between items-center'>
<div className='flex flex-row items-center gap-1 '>
<Image src='/totalVotes.svg' width={41} height={22} />
<span className='text-sm opacity-75'>Total votes: {totalVotes}</span>
</div>
<button className='action-button px-4 py-2 font-semibold' type='submit'>Vote</button>
</div>
</form>
)
}
afterVoteProgress.jsx
import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress';
import { styled } from '@mui/material/styles';
import { animate, motion } from "framer-motion";
import { useState, useEffect } from 'react';
const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 8,
borderRadius: 5,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: '#DBDBFF',
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 5,
backgroundColor: theme.palette.mode === 'light' ? '#5E5BFF' : '#5E5BFF',
},
}));
export default function AfterVoteProgress({optionText, votes, maxParcentage}) {
console.log('max parcentage ', maxParcentage)
const [progress, setProgress] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setProgress((prevProgress) => (prevProgress >= maxParcentage ? maxParcentage : prevProgress + 5));
}, 100);
return () => {
clearInterval(timer);
};
}, []);
return(
<motion.div className='flex flex-col'>
<motion.span animate={{x:0, y:0}} initial={{x:10, y:10}} className=' text-base font-medium'>{optionText}</motion.span>
<motion.div animate={{opacity:1, x:0}} transition={{delay:0.2}} initial={{opacity:0, x:-50}}>
<BorderLinearProgress variant='determinate' value={progress} />
</motion.div>
<motion.span animate={{opacity:1, y:0}} transition={{delay:1.2}} initial={{opacity:0, y:-12}} className='text-xs opacity-70'>{votes} votes</motion.span>
</motion.div>
)
}
I’ve tried to update the component later, but the prop is still not updated, while the state already has been changed.
EDIT: Looks like the problem might be in the afterVoteProgress.jsx, the data is parsed corectly but the progress bar is "late"
After setting the hasVoted to True, i’ve saw that the useEffect inside afterVoteProgress.jsx only triggers the interval after I save and "Fast refresh" the page.
2
Answers
In my opinion,
data.maxParcentage = part
is wrong.This is an anti-pattern in React, because it does not trigger a re-render of the component, and the prop is still using the old value.
To fix this, you need to update state immutably using
setOption
function.For example:
The issue you’re encountering is related to updating the maxParcentage prop in the option array. In React, changing the value of a property in an object within an array doesn’t trigger a re-render of the component. Therefore, even though you update the maxParcentage value in the state, it doesn’t reflect in the rendered component. Try using the below method: