I'm currently working my way through the Free Code Camp assignments and I just completed my most recent one. I just wanted to discuss some of the lessons I learned and new techniques I tried. Hopefully you will learn something from my experience.
My finished assignment is on Codepen here.
Topics
- The Assignment
- CSS Transforms
- ARIA Roles and CSS
- Sass
- React
- Local Storage
The Assignment
The assignment was to create a "Recipe Box" using ReactJS and Sass. It had to fulfill the following user stories:
- I can create recipes that have names and ingredients
- I can see an index view where the names of all recipes are visible
- I can click into any of those recipes to view it
- I can edit these recipes
- I can delete these recipes
- All new recipes I add are saved in my browser's local storage
It seemed simple enough and, after I decided how to organize my components, it actually was pretty simple. So, I added a little bit of complexity.
CSS Transforms
I love CSS. I envy developers who can recreate JavaScript functionality using just CSS and who can make amazing CSS animations. Someday, I'll get there. For now, I mostly mess around and see what I can come up with. For this assignment, I decided I wanted to mimic a folded piece of paper using CSS.
My first step was to rotate the list elements so that they would alternate moving away from the viewer and moving toward the viewer.
.perspective
transform: rotateX(-20deg)
.perspective:nth-child(2n)
transform: rotateX(20deg)
This didn't look quite right, though. By default, elements rotate on their central axis. I needed them to alternate rotating away from and toward the viewer. That's where transform-origin comes in.
.perspective
transform: rotateX(-20deg)
transform-origin: bottom
.perspective:nth-child(2n)
transform: rotateX(20deg)
transform-origin: top
Now my list elements were hinging so that each pair formed a "V", like a fold in a piece of paper. Still, it didn't look quite right. The rotate affect was so minor that it hardly looked like anything had been done at all. To fix that, I used the perspective property. Perspective controls how far an element is placed from the viewer. Even after I'd figured this out, I had trouble getting it to work because it turns out that the perspective property affects child elements, not the element you set it on. So, I needed a wrapper.
.perspective-wrapper
perspective: 800px
.perspective
transform: rotateX(-20deg)
transform-origin: bottom
.perspective:nth-child(2n)
transform: rotateX(20deg)
transform-origin: top
To help you visualize this affect, imagine that a perspective of 1000px means an element is so far away that you can't tell whether it's been rotated. At 0px, it's so close that you can't see what it is you're looking at (imagine holding an object directly against your eyes). So, 800px gives us more definition on the rotation without distorting the element too much.
I had to use negative margins and magic numbers to close the gaps caused by the rotations, so it's certainly not a perfect technique. I'm hoping I can refine it at some point. While we're talking about CSS flaws, I'll also just point out that my specificity is way too high due to the way I set up my .perspective class. Pre-planning your HTML/CSS structure will save you headaches later!
ARIA Roles and CSS
Back in June, Eric Meyer gave an AMA in which he recommended this specifications-based method of writing CSS. I was excited to give it a try.
A large part of this CSS method is familiarizing yourself with ARIA roles, which are kind of like unofficial HTML tags. For example, there is an ARIA role for a button just like there is an HTML tag for a button. The idea is that you'll be building based on a specification, which provides documentation and consistency.
My list elements revealed further content when clicked, which fit the specification for the ARIA role of a tab. So, I used the tab, tablist, and tabpanel roles:
<!-- Just to be clear, this is JSX, not HTML -->
<div className="recipe-box">
<h1>Recipe List</h1>
<div role="tablist" className="recipes">
<div role="tab">...</div>
<div role="tabpanel">...</div>
</div>
</div>
Then, I could associate my tabs with their tabpanels using aria-controls and I could define their states using aria-selected and aria-expanded:
<div className="recipe-box">
<h1>Recipe List</h1>
<div role="tablist" className="recipes">
<div role="tab" aria-controls="recipe-1" aria-selected={this.state.isSelected}>...</div>
<div role="tabpanel" id="recipe-1" aria-expanded={this.state.isExpanded}>...</div>
</div>
</div>
And I can write CSS like this:
[role=tablist]
padding: 2em
[role=tab]
padding: 1em
cursor: pointer
[role=tab][aria-selected=true]
background-color: $color-highlight
[role=tabpanel][aria-expanded=false]
transform: translateY(-100em) // Used to create slide-in affect
[role=tabpanel][aria-expanded=true]
transform: none
The value of this is that now my HTML and CSS are tied to an official specification that I or anyone else can refer to in the future. If there are accessibility features tied to the ARIA role, then great, my app is now more accessible. My components are more general and reusable than if I were to just make up a naming convention. Yes, it takes more time to get familiar with ARIA roles, but it also took time to learn HTML5 semantic elements and whatever naming convention you're currently using.
This method isn't necessarily better than your current CSS method, but then again, it might be. Just something to consider.
Sass
I used to avoid Sass, opting instead for SCSS because it's basically CSS with nice add-ons. However, after using Sass for a few projects, I'm starting to like not having to use brackets and parentheses. It's just more efficient and clean. I don't necessarily think Sass is better than SCSS or that everyone should make the switch, but I like it...for now.
React
My biggest bottleneck when working with React is always figuring out how to properly pass data and state between components. In other words, I usually jump in without getting organized first. I'd like to reiterate: Plan out your structure at least a little bit before getting started. At least know what components you're going to be using. If you want to go the extra mile, figure out which components hold what state information and which other components might need it passed to them.
Local Storage
Man, how did I not know about this sooner!? I used to use session storage all the time back when I worked with MeteorJS, but for some reason I never considered that VanillaJS could manage local storage.
From my understanding, using local storage is similar to calling a database that is on the client instead of a server and can only hold strings. It's a far cry from something like MongoDB, but you can still do so much with it! I'm going to have to experiment with more client-side apps and see what I come up with.
That's All, Folks
This is my first time writing anything like this, so please let me know what you think. I usually focus on more conceptual, abstract topics, so it's nice to just discuss code for once. Maybe I'll write another when I finish my next project.