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.
CodePudding user response:
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?
CodePudding user response:
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 (
<Fragment>
<div>
{stars.map((star, i) => (
<span
className="star"
key={i}
data-id={i}
onClick={rate}
>{star}
</span>
))}
</div>
</Fragment>
);
};
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>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>