React Basics: A Simple Fitting Room Component
React and Everything Amazing About It
I've been writing apps in React for a little over eight months now, and I love it. Although it may be difficult at first to dive into the React ecosystem and learn all the important players, after you get the hang of it, you'll see how easy it is to create dynamic, complex UIs quickly.
React itself is a simple view library, not a framework, which makes it easy to customize your app, but difficult to get all the right pieces together when you're first getting started. In order to take advantage of all the benefits of React, you'll need to buy into the whole system and learn ES6 syntax, JSX, and React Router. Also helpful is learning a bit of webpack and incorporating a library such as Redux for state management. Once you get a production app running, you probably should use Flow or TypeScript to add typechecking to your app and prevent simple, yet annoying JavaScript errors.
In terms of state management, Redux seems to be the most popular choice at the moment, and that's the one I've primarily focused on learning, but I've also seen people use Alt and MobX. I've also seen apps that don't use a state management library, including the software I worked on for the past three months at Rent The Runway. As far as typechecking goes, I'm pro Flow because it's easy to adopt incrementally and it's pretty simple to begin using from the outset, but you can read more here.
If I had to identify the key features of React that make it so delightful, it would likely be unidirectional data flow and a simple API, both of which work together to encourage the creation of small, isolated components. In this post, I'll cover my thought process regarding how to structure state within my React components and how to create a simple component. This should be suitable for beginners and up.
Creating A Simple Component
For context, I worked for the last three months on internal software for retail stores. One of the requirements in the app I was building was for stylists in the stores to be able to manage fitting rooms. This includes the ability to add a customer to a fitting room, mark the fitting room as open, and mark the fitting room as closed.
As I primarily focused on front-end work, I didn't need to worry too much about how the data was getting to me, but I would need to hit an API to fetch the list of fitting rooms and the list of customers.
When people write about React, they often talk about thinking in React
because writing apps in React does require a different mental model than,
say, Backbone or just unstructured spaghetti jQuery. When you think about
composing components in React, you need to consider where the state will
live. Ideally, state would live in one place, your single source of truth,
and that state would be passed down into subcomponents as props. State is
mutable, but only by calling the method setState
through the established API.
You should never, ever try to mutate state directly. That will not trigger a re-render and will only leave you very confused about what state your app is actually in.
That said, when structuring this part of the app, I decided that there
should be a FittingRooms
component that would hit the backend API and
fetch the list of fitting rooms and that this component would render a list
of subcomponents called FittingRoom
. In pseudocode, that would probably
look something like the following:
// not real code!
class FittingRooms extends React.Component {
constructor(props) {
// here we set the default state of the component so that it doesn't
// blow up if the fitting rooms are null
// flow would also advise us to set all the possible pieces of state
this.state = {
loading: true,
fittingRooms: []
}
}
componentDidMount() {
// this is some API
FittingRooms.fetch().then((data) => {
this.setState({
fittingRooms: data.fittingRooms,
loading: false
})
})
}
render() {
if (this.state.loading) {
return <Spinner />
}
let fittingRooms = this.state.fittingRooms.map((fr) => <FittingRoom
fittingRoom={fr}/>)
return(
{fittingRooms}
)
}
}
But for now, let's turn our attention to the actual FittingRoom
component
itself.
The FittingRoom
has three possible states: open, occupied, or closed.
Displaying this visually and controlling the behavior of each will need to
be accomplished through a mix of CSS and JavaScript. I prefer to use
JavaScript to calculate the state and then use CSS (with Sass and BEM syntax) to bring this UI to life.
All that a basic React component needs is a render method and to inherit
from React.Component
. If you're using older syntax, you could use the
React.createClass
interface, but I strongly advise everyone to get off
that and enter the modern era.
class FittingRoom extends React.Component {
render() {
return(<div/>)
}
}
This component doesn't do much, but there's the beginning of what will soon be a beautiful, fully functioning component representation of a Fitting Room.
Now, let's start thinking about how to derive the state of the component. As I mentioned above, fitting rooms can either be open, closed, or occupied.
To begin, I write two functions to determine the state of the fitting room. If it's occupied, it'll have a fitting room ticket attached. If it's open, it'll have an open status of true. And finally, if it's neither of those, it'll be closed.
class FittingRoom extends React.Component {
isOccupied (fittingRoom) {
// here I use the lodash function to be sure I don't make a
// null-checking error.
// it happens more often than I'd like to admit
return !_.isEmpty(fittingRoom.fittingRoomTicket)
}
isOpen (fittingRoom) {
return fittingRoom.open === true
}
render () {
// Before the return keyword, you can do a bunch of calculations
// used in the component
if (this.isOccupied(fittingRoom)) {
status = 'Occupied'
} else if (this.isOpen(fittingRoom)) {
status = 'Open'
} else {
status = 'Closed'
}
return (
<div className={`FittingRoom FittingRoom--${status}`}>
{status}
</div>
)
}
}
Now, let's take it one step further and render different markup based on the status of the component.
class FittingRoom extends React.Component {
isOccupied (fittingRoom) {
return !_.isEmpty(fittingRoom.fittingRoomTicket)
}
isOpen (fittingRoom) {
return fittingRoom.open === true
}
renderOccupiedFittingRoom (fittingRoom) {
let customer = fittingRoom.fittingRoomTicket.customer
return (
<div className='FittingRoom__Container'>
<div className='FittingRoom__Label'>
{fittingRoom.label}
</div>
<div className='FittingRoom__Status'>
{customer.firstName} {customer.lastName}
</div>
</div>
)
}
renderFittingRoom (fittingRoom) {
return (
<div className='FittingRoom__Container'>
<div className='FittingRoom__Label'>
{fittingRoom.label}
</div>
<div className='FittingRoom__Status'>
// check out this cool ternary statement
{fittingRoom.open ? 'Open' : 'Closed'}
</div>
</div>
)
}
render () {
let {fittingRoom} = this.props
// This is object destructuring syntax provided by ES6
let status
let fittingRoomComponent
// Up here I'm declaring the variables that will later hold the markup
// for the component
if (this.isOccupied(fittingRoom)) {
status = 'Occupied'
fittingRoomComponent = this.renderOccupiedFittingRoom(fittingRoom)
} else if (this.isOpen(fittingRoom)) {
status = 'Open'
fittingRoomComponent = this.renderFittingRoom(fittingRoom)
} else {
status = 'Closed'
fittingRoomComponent = this.renderFittingRoom(fittingRoom)
}
return (
<div className={`FittingRoom FittingRoom--${status}`}>
// Down here I reference that variable, which is just another
// component. Since it's JSX, it compiles down to JavaScript objects
{fittingRoomComponent}
</div>
)
}
}
Awesome, so now we've got our basic component, and it's time to style it.
Atomic Design, SCSS, & BEM in Practice
Instead of using a CSS framework, here I am writing custom CSS for each component and using BEM syntax to both namespace and identify each chunk of the UI.
Sprinkled throughout the component already you'll see various classNames
applied to each piece of the fitting room. At the top level of the component
is the block, the FittingRoom
. Then, because each status has a different
look, I give that FittingRoom
a modifier. That is denoted by the double
dashes --
. In the end, we should have FittingRoom--Open
,
FittingRoom--Occupied
, and FittingRoom--Closed
.
Each FittingRoom
is composed of a Label
and a Status
. These are both
elements of the FittingRoom
, and are indicated as elements by the double
underscores: FittingRoom__Label
and FittingRoom__Status
.
Here's what the stylesheet for this component looks like:
.FittingRoom {
font-family: Helvetica;
text-align: center;
background-color: #ffffff;
width: 150px;
color: #111111;
margin: 4px;
border: 1px solid #aaaaaa;
border-top: 0 solid #aaaaaa;
position: relative;
&__Container {
padding: 16px 16px 24px 16px;
}
&__Label {
margin: 0 auto;
font-size: 24px;
padding: 4px;
text-align: center;
height: 32px;
width: 32px;
border-radius: 9999px;
background-color: #aaaaaa;
color: white;
}
&__Status {
margin-top: 8px;
padding: 8px 0;
font-size: 18px;
}
}
.FittingRoom--Open {
color: #66A97A;
.FittingRoom__Container {
border-top: 8px solid #66A97A;
}
.FittingRoom__StatusBar {
background-color: #66A97A;
}
.FittingRoom__Label {
background-color: #66A97A;
}
}
.FittingRoom--Occupied {
.FittingRoom__Container {
border-top: 8px solid #ff0000;
}
.FittingRoom__Label {
background-color: #ff0000;
}
}
.FittingRoom--Closed {
color: #aaaaaa;
.FittingRoom__Container {
border-top: 8px solid #aaaaaa;
}
.FittingRoom__Label {
background-color: #aaaaaa;
}
}
As you can see, there's the main block, and then the extensions to indicate each status. Nested within the extensions are the modifictions to the child elements that need to be applied to achieve the styles we want.
And that's all for now! To see the code, check out this Codepen example.