0

I'm new to React. I have a rating component that I created. The problem I'm having is onClick I can see the rating is updating in the console, but on the page I don't see the change happening. My understanding is that React components need to be pure functions and it's better to prefer functional components over classes. When I try to use the useState hook my rating is added to the previous state and doesn't update correctly on the page.

My first question is what is the best/preferred way to update state within a component? If I'm using a functional component do I have to use a hook?

Please see my code below

export default function Rating(props) {
    let stars = [];
    // const [stars, setStars] = useState([]);
    let star = '☆';

    for (let i = 0; i < props.stars; i++) {
        stars.push(star);
    }

    function rate(index) {
        let filledStar = '★'
        let stars = [];
        // setStars([])


        for (let i = 0; i < props.stars; i++) {
            stars.push(star);
        }
        stars.forEach((star, i) => {
            while (index >= 0) {
                stars[index] = filledStar;
                index--;
            }

        })
        console.log('stars filled', stars)
        return stars

     }

    return (
        <>
        <div className="stars">
            {stars.map((star, i) => (
                <h2 
                    key={i}
                    onClick={() => rate(i)}>{star}
                </h2>
            ))}
        </div>

        </>

    )

}

If I click the fourth star this is returned as expected, but the UI doesn't update

If I click the fourth star this is returned as expected, but the UI doesn't update.

3 Answers 3

1

Here's a working version that fills in stars up to and including the star that was clicked on - using useState, and useEffect.

const { Fragment, useEffect, useState } = React;

function Rating(props) {

  // Set state as an empty array
  const [stars, setStars] = useState([]);

  // Called by useEffect his function
  // creates a new array of empty stars
  function initStars() {
    const stars = [];
    for (let i = 0; i < +props.stars; i++) {
      stars.push('☆');
    }
    setStars(stars);
  }

  // Called when the component
  // first mounts, and called only once
  useEffect(() => {
    initStars();
  }, []);

  function rate(e) {

    // Get the id from the star dataset
    const { id } = e.target.dataset;

    // Create a fresh array
    const stars = [];

    // Loop; if the current index is less or
    // equal to the id set it as a filled star
    // otherwise set it to an empty star
    for (let i = 0; i < +props.stars; i++) {
      stars.push(i <= +id ? '★' : '☆');
    }

    // Set the state with the new array
    setStars(stars);

  }

  return (
    <div>
      {stars.map((star, i) => (
        <span
          className="star"
          key={i}
          data-id={i}
          onClick={rate}
        >{star}
        </span>
      ))}
    </div>
  );

};

ReactDOM.render(
  <Rating stars="10" />,
  document.getElementById('react')
);
.star { font-size: 1.8em; }
.star:hover { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Sign up to request clarification or add additional context in comments.

Comments

0

As you suggested, you will need to use hooks in order to seek the functionality you're looking for (or use a class component). In your current implementation, you're not actually using React states, so React doesn't know that you want it to re-render your component. Also, the stars array you're using in rate() is different from the stars array in your component. You don't seem to be making use of the stars array created and returned by rate()

How exactly have you tried to use useState?

4 Comments

If you look at my code I've commented out useState and setStars. I used those in place of the stars variable. However, when I use that in it's place it adds the previous state to what I changed in the console and in the UI it just doubles the amount of stars I have set and then doesn't change again.
Also, it was intentional not to use the same stars array in the rate function because every time the user sets the rating I want to reset the array.
Is your intention to click on a star and have that star, and the stars preceding it, to be filled in?
Yes. So if I click the fourth star (third index) it should fill in that star and the proceeding stars. My logic is correct, but the component isn't updating and I'm not sure what the best way to do that is.
0

So, it seems that I need to use the useState hook. I'm sure there is another way to do this, but this worked for me.

 export default function Rating(props) {
        const [stars, setStars] = useState([]);
        let star = '☆';

        function initStars() {
            const stars = [];
            for (let i = 0; i < props.stars; i++) {
               stars.push(star);
            }
            setStars(stars);
        }

        // Called when the component
        // first mounts, and called only once
        useEffect(() => {
            initStars();
        }, []);

        function rate(index) {
            console.log('index', index);
            let filledStar = '★'
            let stars = [];

            console.log('setStars', stars);

            for (let i = 0; i < props.stars; i++) {
               stars.push(star);
            }
            stars.forEach((star, i) => {
                while (index >= 0) {
                   stars[index] = filledStar;
                   index--;
                }

            })
            console.log('stars filled', stars)
            // return stars
            setStars(stars)

         }

         // console.log('stars', stars);

        return (
        <>
            <div className="stars">
                {stars.map((star, i) => (
                    <h2 
                        key={i}
                        onClick={() => rate(i)}>
                        {star}
                    </h2>
                ))}
            </div>

        </>

    )

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.