0

I seem to be having a very strange issue with regards to how a map of components are seemingly ignoring their properties (despite a console.log() proving that the individual property values are indeed reaching the component).

I am generating multiple of the same component, using a list of objects. Each object has two parameters, a "name" and a "rating" (a value from 0.0 to 1.0). This is all being handled within a component that has been declared as a class , not a function.

categories = [
        {name: "Name1", rating: 0.5},
        {name: "Name2", rating: 1.0},
        {name: "Name3", rating: 0.6},
        {name: "Name4", rating: 1.0},
        {name: "Name5", rating: 0.4}
    ]

...

render = () =>{

    {this.categories.map(c => <StarCard name={c.name} rating={c.rating}/>)}

}

The elements of the list are being mapped to a custom StarCard component, with the name and the rating of each list entry being passed in respectively.

The StarCard then passes just the rating value to a Star component, which is responsible for drawing an SVG graphic of a star on screen, with a mask that's used to fill the star to a certain point, based on it's rating (completely empty = 0.0, completely full - 1.0).

const StarSVG = ({classProps, rating}) => {

    const starFill = parseInt(23.0-(23.0*Number(rating)));
    const num = 13;

    //console.log(typeof starFill + " " + typeof num);
    //console.log(starFill);

    return(
        <svg xmlns="http://www.w3.org/2000/svg" className={classProps} width="24" height="24" viewBox="0 0 24 24">
            <defs>
                <clipPath id="starClip">
                    <rect x="0" y={starFill} width="200" height="200" />
                </clipPath>
            </defs>

            <path clipPath="url(#starClip)" d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.828 1.48 8.279-7.416-3.967-7.417 3.967 1.481-8.279-6.064-5.828 8.332-1.151z"/>

        </svg>
    );

}

export default StarSVG;

As you can see, I am calculating the value of the rating prop to calculate the Y position of the mask that's being used to hide the top portion of the graphic. However, for some reason, all instances of this star across their respective components, only inherit the rating value of the first object in the list. For example, if the first value is set to 0.5 as shown in the first snippet, all stars on screen will be half filled, and completely ignore their own rating values (despite console logging proving that the rating values are being calculated properly).

I am at a loss as to what may be causing it. I have tried changing making the function a var as opposed to a const (same with the variables within the function), and can't seem to remedy the issue.

Thanks for getting this far, and any help is much appreciated! Feel free to comment if you need additional info.

6
  • A side note, I can substitute y={starFill} for either an inline value such as "13", or a different variable (hence the existence of the un-used num variable in the last code snippet), and the changes occur as they should, for all stars. Commented Nov 30, 2021 at 1:18
  • 1
    I’m not entirely sure about using id in svg, is it scoped? If not then when you use url(#starClip) you’ll probably refer to the same clip in the first svg. Commented Nov 30, 2021 at 1:39
  • 1
    To fix that, use instead <clipPath id={"starClip_"+starFill}> and <path clipPath={"url(#starClip_"+starFill+")"} Commented Nov 30, 2021 at 1:44
  • 1
    @hackape you raise a very good point! I'm not 100% sure exactly how to go about clipping the SVG without using an ID. If there is a way, that's definitely something I can try. (Just sent before you recommended a fix!) Commented Nov 30, 2021 at 1:44
  • 1
    After some googling I’m sure it’s because of un-scoped id collision. I’m on a phone and feel reluctant to type too much. You can answer your own question to tell us your finding, cheers! Commented Nov 30, 2021 at 1:54

1 Answer 1

1

So here is how you StarCard should be

const StarSVG = ({ classProps, rating, name }) => {
  const starFill = parseInt(23.0 - 23.0 * Number(rating));

  //console.log(typeof starFill + " " + typeof num);
  console.log(starFill);

  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      className={classProps}
      width="24"
      height="24"
      viewBox="0 0 24 24"
    >
      <defs>
        <clipPath id={name}>
          <rect x="0" y={starFill} width="24" height="24" />
        </clipPath>
      </defs>

      <path
        clipPath={`url(#${name})`}
        d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.828 1.48 8.279-7.416-3.967-7.417 3.967 1.481-8.279-6.064-5.828 8.332-1.151z"
      />
    </svg>
  );
};

export default StarSVG;

Note that clipPath id should be unique so we used the name for this

    <clipPath id={name}>
      <rect x="0" y={starFill} width="24" height="24" />
    </clipPath>

and make sure in element you reference it

>     <path
>     clipPath={`url(#${name})`}
>     d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.828 1.48 8.279-7.416-3.967-7.417 
>     3.967 1.481-8.279-6.064-5.828 8.332-1.151z"   />

Make sure to add a key to in your main component to StarSVG

<div style={{ margin: "10rem" }}>
  {categories.map((c) => (
    <StarSVG name={c.name} rating={c.rating} key={c.name} />
  ))}
</div>
Sign up to request clarification or add additional context in comments.

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.