skip to Main Content

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"
data

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


  1. 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:

    useEffect(() => {
      if (totalVotes > 0) {
        const updatedOptions = option.map((data) => {
          const part = (data.votes / totalVotes) * 100;
          return { ...data, maxParcentage: part };
        });
        setOption(updatedOptions);
      }
    }, [totalVotes]);
    
    Login or Signup to reply.
  2. 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:

    
    useEffect(() => {
      if (totalVotes > 0) {
        const updatedOptions = option.map(data => ({
          ...data,
          maxParcentage: (data.votes / totalVotes) * 100
        }));
    
        setOption(updatedOptions);
      }
    }, [totalVotes]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search