Advanced React Patterns

Big Head
  • 👀 View the deployed code on Github.
  • Not happy with the solution? 🐞🐛 Suggest a change.
  • Grammar errors? ✏️ Edit this page.

Control Props, II

Summary: Create a similar rating component as that of Material UI using the Control Props pattern. It's okay if it's a different API and that you don't handle half star ratings.

Once you have made the rating component, instantiate two of them. They should be in sync with each other like in the accompanying demonstration. The two components have a maximum score of five and ten respectively, say they're called rating5 and rating10. The component with 10 components, can only have even ratings (2, 4, 6, 8, 10). When one of the events occur in the component (mouseEnter, mouseOut, or onClick), the other component makes a similar update in its state. In other words, when rating5 has a rating of 3, rating10 has a rating of 6, always twice that of rating5. If the icon corresponding to 5 of rating 10 is clicked, the rating would be 6. The specific icon corresponding to the rating should be different from the rest.

Just a heads up: The logic to achieve this functionality can be very tricky... It has a lot of edge cases! Let me know if I missed something!

My Solution

You could call the uncontrolled component like this:

And the controlled version like this:

Here's an example of icons you can pass

For simplicity, the state of the component could be composed only three things.

  1. The rating (A positive integer from 0 to maxRating),
  2. the hoverIndex (null or a positive integer from 0 to maxRating - 1 ), null when nothing is hovered, 0 when the first rating icon is hovered, and maxRating - 1 when the last rating icon is hovered
  3. The lastEvent recorded that resulted the current rating and hover.

The lastEvent could have 4 possible event types

And the actionTypes possible could be just one of two. If an icon was clicked (eventType = "rate") or if there was either a mouseEnter or mouseLeave (eventType ="hover") examples of an action could be

  1. action = { type: "rate", rating: 3} indicating the 3rd icon was clicked
  2. action = {type: "hover", hoverIndex: 2} indicating the pointer entered the 3rd icon
  3. action = { type: "hover", hoverIndex: null} indicating the pointer left the icon it was currently pointing to

Here's the default reducer logic which is essentially:

  1. If the user clicks ("rate") an icon, the rating toggle between 0 and the rating corresponding to the clicked icon.
  2. If the event is a hover, if the hoverIndex is null then the eventType is mouseLeave, if it's a number, then the eventType is mouseEnter

Here's the useRating hook which is pretty self explanatory, following the Control Props pattern. It has two responsibilities:

  1. It is responsible for determining and storing the state of the Rating component.
  2. It also returns getButtonProps which are the props that we should pass to each button of the rating component.

We could have a helper function to check if the lastEvent was an onClick

Here's the main logic of the action Rating component. Essentially, based on the state from the useRating hook, it's responsible for determining which icons should be displayed.

Here's the logic to determine the which icon to display for each button (It's a bit tricky)

Here's the main logic of the top level app that renders two Rating components. The app makes sure the two controlled rating components are synchronized

Here's the main logic (this one is very tricky, the the somewhat confusing, I get confused myself), this is the logic for the component with a rating of 10 (heart)

The logic for the helper functions for the component with the rating of five

That's all!