Conversation
I'm not certain where to begin the review on this. I know for a fact this is not the way we want to be doing this. There are WAY to many handlers on the individual stars, an inline in the render function, derived state changes, and many other issues. So I guess I'll start with the explosion of functions in it? One render for 5 stars has 12 functions made for every render(the star, You can easily do this with a surrounding The stars don't need an inline class in the render, it should instead be a class on the parent as a property or contained inside a closure as a private from an IIFE that returns the If you could drill this down to only 1 binding per child then it would be much closer to how it should perform, however I believe the version with 0 bindings would be preferred in a production environment. That is however a different endeavor. Otherwise you should note the performance implications of having many of these on a single page. Since complicating this further makes it a very long snippet, I'm voting for noting the performance implications myself |
We should be using the class properties proposal (autobinding) instead of specifying a constructor. |
@atomiks Care to explain a bit? I am not that great with React just yet. @skatcat31 Granted, the As far as the inlined |
@skatcat31 The |
Here..
const oneToFiveArray = [...Array(5)].map((_, i) => i + 1)
function Star({ id, isMarked, onHover, onRate }) {
return (
<span
style={{ color: '#ff9933' }}
onClick={() => onRate(id)}
onMouseEnter={() => onHover(id)}
>
{isMarked ? '\u2605' : '\u2606'}
</span>
)
}
class StarRating extends React.Component {
state = {
rating: null,
selection: 0
}
hoverOver = val => {
this.setState({ selection: val })
}
setRating = val => {
this.setState({ rating: val })
}
get rating() {
return (
(this.state.rating !== null ? this.state.rating : this.props.rating) || 0
)
}
render() {
return (
<div onMouseOut={() => this.hoverOver(0)}>
{oneToFiveArray.map(v => (
<Star
id={v}
key={`star${v}`}
isMarked={
this.state.selection
? this.state.selection >= v
: this.rating >= v
}
onHover={this.hoverOver}
onRate={this.setRating}
/>
))}
</div>
)
}
} Also it needs to be accessible which it isn't right now |
@atomiks I can move the component outside, I thought that if I inlined it, it would not pollute the global namespace, but it's actually slightly better with it outside the parent component. Meanwhile, if you check the updated version with
As far as accessibility is concerned, please suggest something, I know that is an issue, I have no idea how to deal with it, though. TL;DR: I'm gonna move the |
@atomiks The component is not inlined anymore. Could use some more pointers on how to improve this. |
I stand by what I wrote 🐝 Checked on CodeSandbox and both behave the exact same.
That's already done by its |
@atmiks check the updated code, the |
Event delegation? Hm, any reason why? |
@atomiks If you read skatcat31's comment, it seems like it's a bad idea to bind 5 |
In this case it makes it messier and non-React like for minor perf benefit. It's 5x fewer bindings - the main reason I'd use event delegation is when adding new children without registering new listeners or there are thousands of children which isn't a problem here. Also you're still binding |
@atomiks I've found that PS: Is there a way to test React component's performance like jsPerf does for simple JS code? We could really use that. |
@atomiks event delegation is preferable for variable amounts of children due to the fact that we aren't limited to a 5 star limit, but instead we are limited to however big we want based on the size of the array which could technically be done in the constructor as a numberOfStars prop or something so it's contained in the class in your proposal @atomiks have we decided to use the class fields proposition? I thought we weren't using things that haven't been finalized or has it moved past stage 3? I know they're implementing it in tooling, but I was unaware of it's current status if it has been approved for ESN or if will be coming after as a stage 4. AFAIK it's still stage 3 but has had testing and an un-updated status since June in the list that was updated as of 7 days ago by ljharb. @Chalarangelo you'll see performance degradation as each parsed function will delay the render, and it updates on mouse move(which could mean a per frame update, which means sub 16ms performance is required) and without de-bouncing could immediately impact performance with just 2 stars and heavy mouse movement. The solution would be no new bindings, or a de-bounced state update based on timeout to instance change with bound function. However a good middle ground is avoiding O(n+x) complexity and large values for n/other higher orders is to just put a warning and let the developer worry about their implementation. That may be rendered moot by how the key logic works for vdom in React however. This is after all an example, and my vote was just for a warning while moving the functional private component inside an IIFE. const StarRating = (function(){
function Star({ marked, starId, onHover }) {
return (
<span
star-id={starId}
style={{ color: '#ff9933' }}
onMouseEnter={() => onHover(starId)}
>
{marked ? '\u2605' : '\u2606'}
</span>
);
}
return class StarRating extends React.Component {
constructor(props) {
super(props);
this.state = {
rating: typeof props.rating == 'number' ? props.rating : 0,
selection: 0
};
this.hoverOver = this.hoverOver.bind(this);
this.handleClick = this.handleClick.bind(this);
}
hoverOver(val) {
this.setState(state => ({ selection: val }));
}
handleClick(event) {
const val = event.target.getAttribute('star-id') || this.state.rating;
this.setState(state => ({ rating: val }));
}
render() {
return (
<div onMouseOut={() => this.hoverOver(0)} onClick={this.handleClick}>
{Array.from({ length: 5 }, (v, i) => i + 1).map(v => (
<Star
starId={v}
key={`star_${ v } `}
marked={
this.state.selection
? this.state.selection >= v
: this.state.rating >= v
}
onHover={this.hoverOver}
/>
))}
</div>
);
}
}
})() Note:This did not take into account @atomiks suggestions, it was a simple copy pasta to show that we don't want to leak multiple |
@skatcat31 I see what you mean, I really really like this idea of using an IIFE, as it is the best middle ground. As far as non-finalized things go, that's a hard no from me. Let's stick to things that work properly across the board. We can always come back later and change the code. So what about handling all the movement without binding children's events to the handler? |
That's what I'll be looking at when I'm done filling out an expenses report |
Come to think of it, can we just add a hover style to element and less than n children? |
@skatcat31 As a CSS person, I thought about this and it sounded like a good idea at first... Until I realized that we would be adding |
I'm not that good with accessibility but I know that:
Problem: It increases code considerably yet accessibility isn't something that should just be ignored! Maybe we need two snippets like "Basic" vs. "Fully accessible"? 😖 function Star({ id, isMarked, onHover, onRate }) {
return (
<button
aria-label={`Rate ${id} out of ${TOTAL_STARS}`}
style={{ color: "#ff9933" }}
onClick={() => onRate(id)}
onMouseEnter={() => onHover(id)}
>
{isMarked ? "\u2605" : "\u2606"}
</button>
);
}
class StarRating extends React.Component {
state = {
rating: null,
selection: 0
};
hoverOver = val => {
this.setState({ selection: val });
};
setRating = val => {
this.setState({ rating: val });
};
get rating() {
return (
(this.state.rating !== null ? this.state.rating : this.props.rating) || 0
);
}
get stars() {
return [...Array(this.props.maxRating)].map((_, i) => i + 1)
}
render() {
return (
<div
onMouseOut={() => this.hoverOver(0)}
aria-label={
this.state.rating
? `You rated this ${this.state.rating} stars`
: this.props.rating
? `Rate this item. Current rating: ${this.props.rating} stars`
: "Be the first to rate this item."
}
>
{this.stars.map(v => (
<Star
id={v}
key={`star${v}`}
isMarked={
this.state.selection
? this.state.selection >= v
: this.rating >= v
}
onHover={this.hoverOver}
onRate={this.setRating}
/>
))}
</div>
);
}
}
StarRating.defaultProps = {
maxRating: 5
}; I'm not sure if |
@atomiks Buttons have inherent styling across all browsers, so we would have to go out of our way to change their style to something different. We can just use |
Updated the snippet with an IIFE as suggested, added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still have one parsed function that can be changed to a bind. This can be dismissed since it's mostly a flavor thing, but it shows a better design pattern of using known situations to design consistent complexity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could just use the i value directly
Co-Authored-By: Chalarangelo <chalarangelo@gmail.com>
Co-Authored-By: Chalarangelo <chalarangelo@gmail.com>
Co-Authored-By: Chalarangelo <chalarangelo@gmail.com>
Co-Authored-By: Chalarangelo <chalarangelo@gmail.com>
Co-Authored-By: Chalarangelo <chalarangelo@gmail.com>
Co-Authored-By: Chalarangelo <chalarangelo@gmail.com>
Co-Authored-By: Chalarangelo <chalarangelo@gmail.com>
@skatcat31 Great suggestions, all changes applied, I think we're set! |
Errm there are still many problems with this 😒
We should have StarRating.defaultProps = {
totalStars: 5
}; Also should use Squash & Merge because there are about 20+ "Updated" commits with no value now 😅 I suggest we revert the merge and incorporate those changes at the very least. See: #12 (comment) |
@atomiks I generally prefer that we don't squash when there are multiple authors. Let's work on the existing file right away. Open a PR and we can start from there. |
Also class properties are Stage 3, we are not going to be doing that until it moves to Stage 4, in case we run into problems with it. |
I understand, but the majority of React projects are using it so they'd all face the same problem if the spec changed. So I don't see a problem with this project using it for now. It simplifies the code greatly. Also Stage 3 is pretty safe compared to Stage 2 and prior. |
Still, we generally don't use Stage 3 features in other project and I don't think we should start now even if the majority of projects use this feature. |
@atomiks majority does not mean adoption. You can in fact use React without Babel. Granted at that point using JSX becomes the oddity. That aside, while you personally like them it seems, I am at odds with them as long as we have a single constructor in our language. If we had more than one constructor I would be ALL for the suggestion. However this is going off topic. We would need to open an issue and decide now, as an org, if we should adopt that proposal in our React code base, if not the org as a whole at this moment in time(if they reach stage 4 and are planned for release, then I see no problem with them whatsoever) |
That aside, IIFEThe IIFE does seem a little confusing if you use ES Modules, however if you are just copy and pasting, we need a way to not overwrite any other We could account for this in the build script, and when we do remove the IIFEs, however importing multiple files versus a single build would be a problem for slower internet users(slower initial load due to multiple tunnels), however would be a boon to updating a single script versus the entire collection. It also allows updates in piece wise. These will need to be thing to be considered when we do the build scripts.
|
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for any follow-up tasks. |
A star rating component:
state
.props
.