Tutorial: StarRating Component
You will build a small Star Rating system during this tutorial. This tutorial does not assume any existing Pando or React knowledge. The techniques you'll learn in the tutorial are fundamental to building any React component and using Pando. Following this tutorial should help give you a deeper understanding of Pando.
This tutorial is designed for people who prefer to learn by doing and want to quickly try making something tangible.
What are you building?
In this tutorial, you'll build a StarRating component with Pando React.
You can see what it will look like when you're finished here:
function StarRating(props) {
// Change the 0 to any number 1 - 5 to see stars filled
const rating = useMemo(() => props.rating || 0, [props.rating])
const ratingList = new Array(5).fill('')
return (
<Grid cols={12} gap={6}>
<For each={ratingList}>
{(_, idx) => (
<GridItem key={`rating-${idx}`} colSpan={1}>
<IconButton
ariaLabel={`Rating ${idx + 1} out of 5`}
icon={rating >= idx + 1 ? StarFilledIcon : StarIcon}
onClick={props.onClick}
usage="text"
/>
</GridItem>
)}
</For>
</Grid>
)
}
There are a few important patterns that we are executing here, but the most important one we want to point out from the start is that we are creating a presentational component.
By allowing this component to have the single responsibility of only displaying the status, we help reduce the risks of bugs and allow this component to be more reusable throughout the life span of the code base.
When building components, remember that they are just functions, so you should approach them with the same clean code standards - having a single responsibility.
Setup your environment
We recommend creating a sandbox via CodeSandbox so you can dive as deep as you need with access to a full project environment (HTML, CSS, etc.).
If you are using CodeSandbox, create a new Sanbox with React or React/Typescript if you prefer a typed environment.
All of our libraries are built with Typescript, so we ship type helpers if you ever need them.
You can also follow this tutorial using your local development environment. To do so, create a new project using one of your favorite scaffolding tools: ViteJS and Create React App are great starting points.
Deconstructing the design
Normally, when you start building a component, you start with the design file. In Thinking in React, we learn how to divide each part of a design into single responsibility sections that will in turn become components.
Our StarRating design looks like there are 3 different parts:
Wrapper, which is a list of stars using a grid layout.
Each list item should be actionable via a click, so they should be buttons.
Each button will display either a filled or empty icon.
This means that the parent component should be responsible for fetching the data and the StarRating only responsible for displaying that data (presentational).
Add the Pando CSS Setup
In order to use Pando, we first need to install it! Open the index.html
in your project and add the following to the bottom of the head
section:
<link
rel="preload"
href="https://fonts.pluralsight.com/ps-tt-commons/v1/ps-tt-commons-variable-roman.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@pluralsight/design-tokens/fonts.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@pluralsight/design-tokens/npm/normalize/normalize.css"
/>
This adds the fonts, themes, and CSS resets Pando relies on.
Next, make sure you don't have any initial CSS being used. If you have content in a CSS file being imported into your App
file, go ahead and remove the import so we can start fresh.
At this point, you should see the background color and typography change to use the Pluralsight Commons font and styles.
Installing Pando
Now that we have the CSS reset in place, we are ready to install Pando into our project.
- npm
- Yarn
- pnpm
npm install @pluralsight/{react,headless-styles,icons}
yarn add @pluralsight/{react,headless-styles,icons}
pnpm add @pluralsight/{react,headless-styles,icons}
Once the packages are installed, we are ready to build!
Building the Grid
In your project, let's create a new file called StarRating
. Inside that file, we will create the initial component and include the grid layout using the Pando Grid API.
import { Grid, GridItem } from '@pluralsight/react'
export function StarRating() {
return (
<Grid cols={12} gap={6}>
<GridItem>Star</GridItem>
</Grid>
)
}
On line 1 we import the Pando Grid Components and on line 3 create our StarRating component.
Adding the IconButton
In Pando, there are different types of Button APIs for Accessibility reasons. Not only does Headless-styles add styling, it also adds all the Accessibility properties you need to make your app 100% accessbile! 🎉
Because we need a button
element that only displays an Icon, we should use the IconButton component.
Add the following to your code:
import {
Grid,
Grid,
+ IconButton
} from '@pluralsight/react'
+ import { PlaceholderIcon } from '@pluralsight/icons'
export function StarRating() {
return (
<Grid cols={12} gap={6}>
<GridItem colSpan={1}>
+ <IconButton ariaLabel="un-selected star" icon={PlaceholderIcon} usage="text" />
</GridItem>
</Grid>
)
}
With the IconButton, we are using the ariaLabel
and usage
options. ariaLabel
sets the aria-label
attribute on the button
element and usage
tells the API to style the component like a "text" button.
Your result should now look like this:
function StarRating() {
return (
<Grid cols={12} gap={6}>
<GridItem colSpan={1}>
<IconButton
ariaLabel="un-selected star"
icon={PlaceholderIcon}
usage="text"
/>
</GridItem>
</Grid>
)
}
Adding the Icons
Now that we have the foundation created, let's add the final missing UI piece: Icons. Add the new Icons to your file:
import {
Grid,
GridItem,
IconButton
} from '@pluralsight/headless-styles'
+ import { StarIcon, StarFilledIcon } from '@pluralsight/icons'
export function StarRating() {
return (
<Grid cols={12} gap={6}>
<GridItem colSpan={1}>
+ <IconButton ariaLabel="un-selected star" icon={StarIcon} usage="text" />
</GridItem>
</Grid>
)
}
Your result should now look like this:
function StarRating() {
return (
<Grid cols={12} gap={6}>
<GridItem colSpan={1}>
<IconButton ariaLabel="un-selected star" icon={StarIcon} usage="text" />
</GridItem>
</Grid>
)
}
At this point, we have the UI ready for the logic, so let's add that in now!
Want to create a more performant pattern? Go ahead and create a new component in the StarRating file called StarButton
can move the button contents into that! Now you have more single responsibility components!
When building components, it's easy to add more and more logic to a single component (like "page" components in an app). Because React was never meant to be a application level framework, this will cause major performance issues and bugs in your app. Instead, create smaller components that are more focused on a single responsibility. This will help you create more performant apps and make it easier to debug when things go wrong. Using Next or Remix will not solve this problem, so it's important to understand this concept when building React apps.
Creating a rating Array
Now that we have the presentational UI setup, let's start adding the logic to our component. To do this, we are going to create a dynamic Array and fill its contents with an empty String so React can successfully loop through it.
Update your code with the following:
import {
+ For,
Grid,
GridItem,
IconButton
} from '@pluralsight/react'
import { StarIcon, StarFilledIcon } from '@pluralsight/icons'
export function StarRating() {
+ const ratingList = new Array(5).fill('')
return (
<Grid cols={12} gap={6}>
+ <For each={ratingList}>
+ {(_, idx) => (
<GridItem key={`rating-${idx}`} colSpan={1}>
<IconButton
ariaLabel="un-selected star"
icon={StarIcon}
usage="text"
/>
</GridItem>
+ )}
+ </For>
</Grid>
)
}
Here, we created a new constant called ratingList
which creates a new Array of 5 items which are filled with an empty String. The fill method is important and is required by React in order to be able to map through a dynamic list.
Then, we added the For
component from Pando and passed in the ratingList
constant. The For
component will memoize the data and loop through the list and return the contents of the children
prop for each item in the list.
At this point, your result should look like this:
function StarRating() {
const ratingList = new Array(5).fill('')
return (
<Grid cols={12} gap={6}>
<For each={ratingList}>
{(_, idx) => (
<GridItem colSpan={1} key={`rating-${idx}`}>
<IconButton
ariaLabel="un-selected star"
icon={StarIcon}
usage="text"
/>
</GridItem>
)}
</For>
</Grid>
)
}
Add the filled props
We are on the final step now of adding our props to each component! This is the easy part. We know that at the end of the day, the rating is just a number and in order to update the number and display the new rating, we need to click the button.
Add the following to your code:
import { memo } from 'react'
import {
For,
Grid,
GridItem,
IconButton
} from '@pluralsight/react'
import { StarIcon, StarFilledIcon } from '@pluralsight/icons'
function StarRatingEl(props) {
+ const rating = props.rating ?? 0
const ratingList = new Array(5).fill('')
return (
<Grid cols={12} gap={6}>
<For each={ratingList}>
{(_, idx) => (
<GridItem key={`rating-${idx}`} colSpan={1}>
<IconButton
+ ariaLabel={`Rating ${idx + 1} out of 5`}
+ icon={rating >= idx + 1 ? StarFilledIcon : StarIcon}
+ onClick={props.onClick}
usage="text"
/>
</GridItem>
)}
</For>
</Grid>
)
}
+ export const StarRating = memo(StarRatingEl)
You only need to use memo
on custom components that accept non-primitive properties. We are using it on StarRating because it will accept a Function for the click event which is not a primitive value.
Here we have added the props
parameter to both StarRating and StarButton and included our new filled
prop with the logic that will determine its state.
The result should look similar to below:
function StarRating(props) {
// Change the 0 to any number 1 - 5 to see stars filled
const rating = props.rating || 0
const ratingList = new Array(5).fill('')
return (
<Grid cols={12} gap={6}>
<For each={ratingList}>
{(_, idx) => (
<GridItem key={`rating-${idx}`} colSpan={1}>
<IconButton
ariaLabel={`Rating ${idx + 1} out of 5`}
icon={rating >= idx + 1 ? StarFilledIcon : StarIcon}
usage="text"
/>
</GridItem>
)}
</For>
</Grid>
)
}
Where to go from here
This was a very brief introduction to building a component using Pando. You can start a Pando project right now or dive deeper on all the syntax used in this tutorial.