Introduction, Philosophy, and Tips
Welcome! (Don’t skip this)
Learning anything new is always a significant endeavor. I don’t take for granted that there’s an opportunity cost in taking this course. Trust that every aspect of this course, from the text and videos to the quizzes and curriculum have been meticulously thought over and debated. The end result is what we strongly believe is the most in-depth and effective way to learn React that exists.
Before we dive into the material, there are some important housekeeping items to mention first.
- I believe very strongly in creating a linear approach to learning. You should feel as if you’re walking up a staircase, not rock climbing. Logistically what this means is that this course is very in-depth. At times you may be tempted to skip certain texts or videos, don’t. Because of this linear approach, each new section builds upon the previous sections. If you’re already familiar with a certain topic, skim it, don’t skip it.
- Throughout the course, you’ll see various types of content. Here’s a breakdown.
- The first introduction you’ll see to any new concept will be in a blog post/video format. This ensures you’re able to hyperfocus on the new concept without needing any external context. If you see a section without the () tags (listed below), it’s this format. You’re not expected to code along during these sections.
- (Project) - Throughout the course, we’ll build a project together. Above each (Project) video will be a link to its coinciding branch as well as the specific commit on Github. If you get stuck or your code isn’t working, check your code against the code in the commit. You are expected to code along with these sections.
- (Practice) - These are usually hosted on Code Sandbox and are designed to get you experience with a particular topic before you’re tasked with implementing it into the project. You might be tempted to skip these, don’t.
- (Solution) - These are the solution videos to the (Practice)s.
- (Quiz) - Your typical quiz. If you find yourself guessing on the quiz problems, redo the section. If the quizzes seem “easy”, that’s a good sign.
- (Bonus) - Bonus material that doesn’t fit into the flow of the normal course but is still important to know.
- If you skipped #2, go read it.
- Once the course is over, you’ll be given a link to the curriculum. This will be the hardest part of the course but also the most rewarding.
- You can take this course regardless of if you’re on Mac, Linux, or Windows. If you’re wondering, I’m running Node v11.10.0 and NPM v6.8.0.
- If you haven’t already, request to join our official private Facebook Group for subscribers. It’s where other members of the team and I will be hanging out answering support questions. Again, if your code isn’t working after following a video, very carefully compare it to the specific commit for that video before asking a question in the facebook group.
- If you run into any errors/typos/etc. related to the course material, you can make a comment on this issue and I’ll get it fixed.
Good luck!
Projects (What you’ll build)
There are two projects you’ll build during this course.
Why React?
I’ve been using and teaching React since 2014. Back then we all knew it was special, but I don’t think any of us could have predicted the growth and impact it would eventually have on the JavaScript ecosystem. In this post, I hope to break down why I personally love React as well as what I believe have been important factors to its success.
Composition
You’re probably familiar with the idea of taking individual functions and composing them together to get some value.
function getProfilePic (username) {
return 'https://photo.fb.com/' + username
}
function getProfileLink (username) {
return 'https://www.fb.com/' + username
}
function getAvatarInfo (username) {
return {
pic: getProfilePic(username),
link: getProfileLink(username)
}
}
getAvatarInfo('tylermcginnis')
Now, what if instead of composing functions together to get some value , we compose functions together to get some UI ? Sounds a little strange at first, but this idea is foundational to what makes React so powerful.
function ProfilePic (props) {
return (
<img src={'https://photo.fb.com/' + props.username'} />
)
}
function ProfileLink (props) {
return (
<a href={'https://www.fb.com/' + props.username}>
{props.username}
</a>
)
}
function Avatar (props) {
return (
<div>
<ProfilePic username={props.username} />
<ProfileLink username={props.username} />
</div>
)
}
<Avatar username="tylermcginnis" />
Because composition is a language-agnostic programming principle, regardless of what programming language you have experience with, the same intuition you have about building and composing functions together can be directly applied to building and composing React components together. On top of that, by bringing composition to the UI layer, the same benefits you typically see from composition will now directly apply to your UIs.
Another benefit of component composition is you naturally get a vibrant ecosystem of third-party components. At this point pretty much any type of React component that can be made as a third party component has been and is available on NPM.
To get your brain thinking this way, here are some example components that you might build or download. React really is just Legos for developers.
<Calendar />
<Map />
<Chart />
<Datepicker />
<Slider />
<Navbar />
<Router />
<Header />
<Date />
<Avatar />
<Carousel />
<Icon />
Unidirectional Dataflow
A big part of building predictable and robust applications is knowing when and how state changes. If you’re coming from a jQuery background, you may be familiar with the following scenario. You start building your app, and everything is fine. You have event handlers which are responsible for updating the state of your application (which lives in the DOM, primarily).
Then your application starts to grow. Slowly each event handler gets tasked with updating more and more pieces of state. What once was nice and predictable begins to look more like this.
As you can imagine, relying on shared mutable state is typically a bad idea. React takes a different approach. Instead of the source of truth for the state of your application living in the DOM, it lives inside of your React components. From there, you can explicitly decide how and when the state should change as well as what the UI looks like based off of that state.
An interesting way to think about this is that your UI is just a function of your state, UI = fn(state)
.
Declarative UI
One thing I glossed over with our jQuery and React examples above is exactly how the DOM gets updated. With jQuery, you’re responsible for writing the imperative code to update the DOM. That typically ends up looking something like this.
$('btn').click(() => {
$(this).toggleClass('highlight')
$(this).text() === 'Add Highlight'
? $(this).text('Remove Highlight')
: $(this).text('Add Highlight')
})
With React, however, that responsibility is abstracted from you. Instead, React allows you to describe what the UI should look like, not how it gets updated. In other words, when a component’s state changes, React will do the hard work of actually updating the DOM. This process lowers the cognitive load of building your application since you only need to worry about 1. how the state in a component changes and 2. what the UI looks like based off that state.
<Btn
highlight={this.state.highlight}
onToggle={this.toggleBtn}
/>
For further reading, see Imperative vs. Declarative Programming
“Just JavaScript”
This one’s a bit controversial, but I still hold it to be true. Generally speaking, if you’re already comfortable with JavaScript, you’ll have an easier time picking up React. There are two reasons for that. First is that the surface level of the React API is pretty minimal. There’s not a whole lot of API that you need to remember. Second is that React tries to not re-create functionality that you can already do in JavaScript. For example, say you had an array of friends
that you wanted to loop over and show to the UI. In some front-end frameworks, there would be a special API for this. For example, in Vue, you would use their v-for
directive.
<ul id="friends">
<li v-for="friend in friends">
{{ friend }}
</li>
</ul>
With React, you’d use JavaScript’s .map
method.
<ul>
{friends.map((name) => (
<li>
{name}
</li>
))}
</ul>
Neither approach is “wrong” or “bad,” they simply prioritize different things.
The Right Abstraction
React is such a good idea that we will spend the rest of the decade continuing to explore its implications and applications. - Guillermo Rauch. CEO ZEIT.
Part of what makes creating abstractions difficult is, by their nature, they have to be opinionated. The right abstraction should empower the user without getting in their way. It should simplify how you think about a behavior. It needs to allow for an almost infinite amount of use cases all without leaking its implementation details. React does all of these things, gracefully.
The proof is really in the types of applications and even tools that have been built using or on top of React.
- Gatsby is a free and open source framework based on React that helps developers build blazing fast websites and apps.
- Styled Components are “Visual primitives for the component age”.
- Next.js is a JavaScript framework that lets you build server-side rendered, static web applications using React.
These are just a few examples, but there’s a reason entire industries are being built upon React.
Community
An underrated aspect of the React ecosystem is the welcoming community around it. From educators and OSS devs to newsletters and conferences, there’s never a short supply of helpful developers willing to lend a hand.
The Team
Last but not least, Facebook continues to invest heavily in React’s development and in my opinion, the current React team continues to innovate and prioritize the right problems.
The React Ecosystem
The path to learning about React is more complicated than it might first appear. What makes learning about React difficult isn’t React itself, as React is just a UI library. Instead, the confusion comes from trying to put all the pieces of the React ecosystem together for the first time.
To even get a normal React app up and running you need the right combination of NPM, Babel, and Webpack. What about routing? Well, React doesn’t come with a router so you’ll need to add one. Wait, what about styling? Also, where does Redux fit into all of it? In this post, we’ll answer all these questions, and you’ll get a high level overview of how the most popular pieces of the React ecosystem fit together.
There’s no better place to start a post about the React ecosystem than with React itself.
React
As I mentioned earlier, React itself is just a library for building user interfaces, nothing more. If that’s all it is, then why is the setup for building a React application so complicated? The reality is you can build React apps with just an index.html if you wanted.
<!DOCTYPE html>
<html>
<head>
<title>React</title>
<script crossorigin src="https://unpkg.com/react@16.7/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16.7/umd/react-dom.development.js"></script>
<script src='https://unpkg.com/babel-standalone@6/babel.min.js'></script>
</head>
<body>
<div id='app'></div>
<script type='text/babel'>
function Hello ({ name }) {
return <h1>Hello, {name}</h1>
}
ReactDOM.render(
<Hello name='Tyler' />,
document.getElementById('app')
)
</script>
</body>
</html>
Is that the best idea long term? Probably not, but it’s important to know that it is entirely possible.
Now that that’s out of the way, eventually you’ll want a more “production ready” build setup, which leads us back to the same question, why is the setup for building a React application so complicated? There are a few different reasons for that and the best way to see why is with some code. Here’s a typical React file (don’t worry too much about the specifics, we’ll dive much deeper into it all of it later on).
import React from 'react'
export default function User ({ name, username }) {
const avatarURL = `https://github.com/${username}.png?size=200`
return (
<div>
<h1>{name}</h1>
<img
alt={`Avatar for ${username}`}
src={avatarURL}
/>
</div>
)
}
If we were to give this file to a modern web browser as is, one of two things would happen, either the browser would throw an error because we have what appears to be HTML located inside of our JavaScript file, or it would throw an error due to our import/export syntax. Both are important for building a modern React application and both shed light on why the setup is so complicated.
First, let’s tackle the weird HTML in JavaScript syntax, this brings us to our first explanation of why the setup is so complicated, Babel.
Babel
Technically Babel is a JavaScript compiler. The way I like to think about it is Babel is a code transformer. You put your code into one end of the Babel machine, and out the other end comes new code after it’s been transformed to your liking. Remember that weird HTML syntax inside of our JavaScript that you saw earlier? It’s called JSX. It’s React’s way to allow you to describe UI inside of your components. Your gut reaction might be to hate it, that’s fine because by the end of this course you’ll love it. We’re going to learn about the why of JSX in a later section, but for now, know that browsers (obviously) don’t support JSX natively. Before we ever give our React code (with JSX) to a browser, we first need to transform it into regular old JavaScript that the browser can understand , this sounds like the perfect scenario for our Babel machine. We’ll put our React code with JSX in one end of the machine, and out the other, we’ll get browser compatible JavaScript.
What’s also cool about Babel is it’s not limited to just making JSX -> JS transformations. In fact, you can make any transformation. The most common use case is using it to convert ECMAScript 2015+ code into a backward compatible version of JavaScript that current and older browsers or environments can understand.
Now that we know that we can use Babel to solve our JSX issue, the next issue we still have to face is the import/export syntax. This is where Webpack comes into play.
If you’re not familiar with JavaScript modules, read From IIFEs to CommonJS to ES6 Modules before continuing.
Webpack
Webpack is a module bundler. What it does is it examines your codebase, looks at all the imports and exports, then intelligently bundles all of your modules together into a single file that the browser can understand. What that means is instead of including all the scripts in your index.html file as we did above, you include the single bundle.js file the bundler creates for you.
How can that help us in our scenario? At this point, it should be pretty obvious. The output that Webpack gives us won’t include any import/export statements. This way the browser won’t choke on it.
We’ll dive into the implementation details of both Babel and Webpack in a later section. For now, you should understand at a high level why they exist and how they benefit us.
Routing
It may be surprising, but React itself doesn’t come with a router. The reason for that, again, is React is just a UI library. The benefit of this is you’re able to pick which router works best for you. The most popular Router for React is React Router.
We’ll dive much deeper into React Router later on, but for now, know that React Router’s whole job is to render specific components based on the current URL path of the user.
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</Router>
When a user navigates to the home page ( /
), React Router will render the Home
component. When they navigate to /about
, React Router will render About
. Finally, as you can probably guess, when they navigate to /topics
, React Router will render the Topics
component.
What’s interesting about React Router is you’ll notice the entire API is just components, this aligns nicely with React itself.
Styling
Styling in React is, for some reason, the most controversial part of the React ecosystem. There are typically two schools of thought, let’s call them Traditional and Untraditional (for the sake of remaining free of bias, for now).
Traditional
Traditionally, you style your React applications just like you would any other application. You have an index.css
file where all your styles go. You have classes and cascading. You could even use SASS or any other CSS pre-processor, but at the end of the day, there’s nothing inherently different about styling your React app than any other app you’ve built. All the benefits and downsides of standard CSS still apply.
Untraditional
In an untraditional way, you embrace React’s component model. If you’re already encapsulating the logic and UI in the component, why not include the styles as well? The term typically used is “CSS in JS.” With CSS in JS, you avoid all the typical properties of standard CSS. No more cascade, no more global namespace, instead, all of your styles for a component live in the component itself.
Revisiting our simple User
component from earlier, CSS in JS may look like this.
const styles = {
header: {
fontWeight: 400,
fontSize: 55,
color: '#a41c1c'
},
avatar: {
width: 50,
height: 50,
borderRadius: 25
}
}
export default function User ({ name, username }) {
const avatarURL = `https://github.com/${username}.png?size=200`
return (
<div>
<h1 style={styles.header}>{name}</h1>
<img
style={styles.avatar}
alt={`Avatar for ${username}`}
src={avatarURL}
/>
</div>
)
}
The most popular CSS in JS library in the React ecosystem is Styled Components. They take it one step further and allow you to define components which specify styling.
import React from 'react'
import styled from 'styled-components'
const Header = styled.h1`
font-weight: 400;
font-size: 55;
color: #a41c1c;
`
const Avatar = styled.img`
width: 50px;
height: 50px;
border-radius: 25px;
`
export default function User ({ name, username }) {
const avatarURL = `https://github.com/${username}.png?size=200`
return (
<div>
<Header>{name}</Header>
<Avatar
alt={`Avatar for ${username}`}
src={avatarURL}
/>
</div>
)
}
Redux
Redux markets itself as a “Predictable state container for JavaScript.” I like to think about it as an ecosystem for making state changes in your applications more predictable. Often Redux gets coupled with React, but that coupling is artificial. You can use Redux with any other view library, not just React.
Comparing the principles for how React manages state and how Redux manages state leads to some interesting insights about the two libraries. The whole philosophy of React is that you build individual components that can each manage their own state as well as describe their UI. You then compose those components together to get your app.
The philosophy of Redux is pretty different. Instead of having state spread out in different places, with Redux you stick all of your state in a single location called a Store. You then establish strict rules for how the state of your Store can change.
If Redux is the right tool for the job, it’s fantastic. If it’s the wrong tool for the job, it’s overkill. For some reason, over the last few years, the hype around Redux surmounted all rationality. With that hype came many misconceptions around the use of Redux and React together. Here’s my attempt to clear up those misconceptions before you get any further into the course. First, if you’re new to React, ignore Redux entirely until you feel comfortable with React. If you start mixing the two, you’ll get confused as to what’s React and what’s Redux. If someone says otherwise, they’re wrong. Second, there’s a good chance that you may never need Redux. There was a time before Redux existed where we all built complex React applications, and we were just fine. Third, no new features of React are making Redux obsolete. Again, I think of Redux as more of an ecosystem than anything else. New features to React can replace parts of that ecosystem, but I don’t believe it’ll ever replace the whole thing.
(Bonus) Imperative vs Declarative Programming
This is originally part of our React course. However, it’s applicable to us here as well. It starts off the same as the “React Higher-order Components” section but soon changes to be Render Props focused.
You’ve undoubtedly heard about imperative programming vs. declarative programming. You might have even searched for what those terms actually mean. Sadly, you probably encountered a definition similar to this
“You know, imperative programming is like how you do something, and declarative programming is more like what you do, or something.”
That definition makes perfect sense once you actually know the difference between imperative and declarative — but you don’t, which is why you asked the question in the first place. It’s like trying to answer “What came first, the chicken or the egg?” except everyone seems to think the chicken did, but you don’t even like eggs, and you’re confused. Combine this frustration with the bastardization of the actual word “declarative” to basically just mean “good” and all of a sudden your imposter syndrome is tap dancing on your confidence, and you realize you don’t even like programming that much. Don’t worry though, friend. I don’t know what a monad is, so hopefully this post will help you realize that declarative is more than just being “easy to reason about” and “good.”
The hard part about this topic is, as Merrick has observed, “It’s one of those things you have an intuition about but can’t seem to explain.” I’ve talked with many developers and what seems to help most is a combination of metaphors with actual code examples. So buckle up cause I’m about to #preach.
Let’s go back to the initial definition I made fun of
“Imperative programming is like how you do something, and declarative programming is more like what you do.”
There’s actually SOME good information hidden in here. Let’s first see the merit in this definition by taking it out of the context of programming and look at a “real life” example.
You decide that you’ve been spending too much time arguing about “JavaScript Fatigue”™ and your husband deserves a nice date. You choose to go to Red Lobster since you’ve been listening to a lot of Beyonce lately (). You arrive at Red Lobster, approach the front desk and say…
An imperative approach (HOW) : “I see that table located under the Gone Fishin’ sign is empty. My husband and I are going to walk over there and sit down.”
A declarative approach (WHAT) : “Table for two, please.”
The imperative approach is concerned with HOW you’re actually going to get a seat. You need to list out the steps to be able to show HOW you’re going to get a table. The declarative approach is more concerned with WHAT you want, a table for two.
“Ok.” — your brain
More metaphors!
I’m going to ask you a question. I want you to think of both an imperative response and a declarative response.
“I’m right next to Wal-Mart. How do I get to your house from here?”
Imperative response : Go out of the north exit of the parking lot and take a left. Get on I-15 North until you get to the 12th street exit. Take a right off the exit like you’re going to Ikea. Go straight and take a right at the first light. Continue through the next light then take your next left. My house is #298.
A declarative response : My address is 298 West Immutable Alley, Eden, Utah 84310
Regardless of how I get to your house, what really matters is the car I drive. Am I going to drive an imperative stick shift car or a declarative automatic car? Enough metaphors?
Before we dive into the code, it’s important to realize that many declarative approaches have some sort of imperative abstraction layer. Look at all of our examples:
- The declarative response to the Red Lobster employee (“table for two, please”) is assuming that the Red Lobster employee knows all of the imperative steps to get us to the table.
- Knowing the address assumes you have some sort of GPS that knows the imperative steps of how to get to your house.
- An automatic car has some sort of abstraction layer over shifting gears.
That was the realization that really made it click for me, so I’ll repeat it:
Many (if not all) declarative approaches have some sort of underlying imperative abstraction.
If that sentence makes sense, you’re doing great!
Now, we’re going to attempt to take the leap from metaphorical happy land to real-world code land. To make the jump more graceful, let’s look at some programming “languages” that are inherently declarative versus those which are more imperative by nature.
Imperative : C, C++, Java
Declarative : SQL, HTML
(Can Be) Mix : JavaScript, C#, Python
Think about your typical SQL or HTML example,
SELECT * FROM Users WHERE Country=’Mexico’;
<article>
<header>
<h1>Declarative Programming</h1>
<p>Sprinkle Declarative in your verbiage to sound smart</p>
</header>
</article>
By glancing at both examples, you have a clear understanding of what is going on. They’re both declarative. They’re concerned with WHAT you want to be done, rather than HOW you want it done.
You’re describing what you’re trying to achieve, without instructing how to do it. The implementation of selecting all of the users who live in Mexico has been abstracted from you. You’re not concerned with how the web browser is parsing your article and displaying it to the screen. Your WHAT is Mexican users or a new header and paragraph on your website.
So far so good. Let’s dive into more practical JavaScript examples.
I want you to pretend you’re now in a technical interview and I’m the interviewer. Open up your console and answer the following questions.
- Write a function called
double
which takes in an array of numbers and returns a new array after doubling every item in that array.double([1,2,3]) // [2,4,6]
- Write a function called
add
which takes in an array and returns the result of adding up every item in the array.add([1,2,3]) // 6
- Using jQuery (or vanilla JavaScript), add a
click
event handler to the element which has anid
ofbtn
. When clicked, toggle (add or remove) thehighlight
class as well as change the text toAdd Highlight
orRemove Highlight
depending on the current state of the element.
Let’s look at the most common approaches to these problems, which all happen to also be imperative approaches.
function double (arr) {
let results = []
for (let i = 0; i < arr.length; i++){
results.push(arr[i] * 2)
}
return results
}
function add (arr) {
let result = 0
for (let i = 0; i < arr.length; i++){
result += arr[i]
}
return result
}
$("#btn").click(function() {
$(this).toggleClass("highlight")
$(this).text() === 'Add Highlight'
? $(this).text('Remove Highlight')
: $(this).text('Add Highlight')
})
By examining what all three of these imperative examples have in common, we’ll be able to better identify what actually makes them imperative.
- The most obvious commonality is that they’re describing HOW to do something. In each example, we’re either explicitly iterating over an array or explicitly laying out steps for how to implement the functionality we want.
- This one might not be as obvious if you’re not used to thinking in the declarative or even more specifically functional way. In each example, we’re mutating some piece of state (If you’re unfamiliar with the term state, it’s basically information about something held in memory — which should sound a lot like variables). In the first two examples we create a variable called results, and then we continually modify it. In the third example, we don’t have any variables, but we still have state living in the DOM itself — we then modify that state in the DOM.
- This one is a bit subjective, but to me, the code above isn’t very readable. I can’t just glance at the code and understand what’s going on. My brain needs to step through the code just as an interpreter would while also taking into account the context in which the code lives(another negativity of mutable data).
All right, enough ing on the code. Let’s now take a look at some declarative examples. The goal is to fix all the problems from above. So each example needs to describe WHAT is happening, can’t mutate state, and should be readable at a glance.
function double (arr) {
return arr.map((item) => item * 2)
}
function add (arr) {
return arr.reduce((prev, current) => prev + current, 0)
}
<Btn
onToggleHighlight={this.handleToggleHighlight}
highlight={this.state.highlight}>
{this.state.buttonText}
</Btn>
Much better
Notice that in the first two examples we’re leveraging JavaScript’s built-in map
and reduce
methods. This goes back to what we’ve been talking about over and over in this article, the most declarative solutions are an abstraction over some imperative implementation.
In every example we’re describing WHAT we want to happen rather than HOW (we don’t know HOW map and reduce are implemented, we also probably don’t care). We’re not mutating any state. All of the mutations are abstracted inside of map
and reduce
. It’s also more readable (once you get used to map
and reduce
, of course).
Now, what about the last example? Well, I cheated a little bit and am using React — but note that all three imperative mistakes are still fixed. The real beauty of React is that you can create these declarative user interfaces. By looking at our Btn
component, I’m able to quickly understand what the UI is going to look like. Another benefit is instead of state living in the DOM, it lives in the React component itself.
Another less-spoken-of benefit to declarative code is that your program can be context-independent. This means that because your code is concerned with what the ultimate goal is— rather than the steps it takes to accomplish that goal — the same code can be used in different programs, and work just fine.
Look at all three of our examples above. We can consume both functions and component in any program we want. They’re program agnostic. This is hard to do with imperative code because often times, by definition, imperative code relies on the context of the current state.
One thing that I didn’t go too far into is how functional programming is a subset of declarative programming. If you haven’t already, I highly recommend getting more familiar with functional programming techniques in JavaScript. Start with .map
, .reduce
, .filter
and work your way up from there. Odds are there isn’t a lower hanging fruit to improve your codebase than making it more functional.
Here are some other definitions that I’ve found from around the web that may or may not be helpful.
Declarative programming is “the act of programming in languages that conform to the mental model of the developer rather than the operational model of the machine.”
Declarative Programming is programming with declarations, i.e., declarative sentences.
The declarative property is where there can exist only one possible set of statements that can express each specific modular semantic. The imperative property is the dual, where semantics are inconsistent under composition and/or can be expressed with variations of sets of statements.
Declarative languages contrast with imperative languages which specify explicit manipulation of the computer’s internal state ; or procedural languages which specify an explicit sequence of steps to follow.
In computer science, declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow.
I draw the line between declarative and non-declarative at whether you can trace the code as it runs. Regex is 100% declarative, as it’s untraceable while the pattern is being executed.
(Bonus) Composition vs Inheritance
This is originally part of our Advanced JavaScript course. However, it’s applicable to us here as well.
This post is designed to be read after you read JavaScript Inheritance and the Prototype Chain .
Previously we looked at how to accomplish inheritance in JavaScript using both ES5 and ES6. In our example, we abstracted the common features amongst every animal (name, energy, eat, sleep, and play) to an Animal
base class. Then, whenever we wanted to create an individual type of animal (Dog, Cat, etc.), we created a subclass for that type.
class Animal {
constructor(name, energy) {
this.name = name
this.energy = energy
}
eat(amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
sleep() {
console.log(`${this.name} is sleeping.`)
this.energy += length
}
play() {
console.log(`${this.name} is playing.`)
this.energy -= length
}
}
class Dog extends Animal {
constructor(name, energy, breed) {
super(name, energy)
this.breed = breed
}
bark() {
console.log('Woof Woof!')
this.energy -= .1
}
}
class Cat extends Animal {
constructor(name, energy, declawed) {
super(name, energy)
this.declawed = declawed
}
meow() {
console.log('Meow!')
this.energy -= .1
}
}
And without the code, we can visualize our class structure like this
Animal
name
energy
eat()
sleep()
play()
Dog
breed
bark()
Cat
declawed
meow()
This worked well as it allowed us to minimize code duplication and maximize code reuse.
Let’s take this a step further and pretend we’re building software for “Farm Fantasy” - a massively multiplayer online (MMO) role-playing game where you do the exact same thing a farmer does, except, you know, online and you pay to do it.
Now that we’re creating an MMO, we’re going to need to have users. We can update our class structure now to look like this
User
email
username
pets
friends
adopt()
befriend()
Animal
name
energy
eat()
sleep()
play()
Dog
breed
bark()
Cat
declawed
meow()
The examples above are textbook examples of classes and inheritance. Sadly, unlike in the classroom, real-world software development isn’t always so predictable.
Let’s say 6 months after building out our initial class structure, our project manager decides we need to change some things. Users love the app and the ability to pay to be a pretend farmer, but they want a more real-life experience. Right now, only instances of Animal
have the ability to eat
, sleep
, and play
. The users are demanding that they also have those same features.
Alright, no issue. We just need to adjust our class structure around a little bit.
... 🤔
I guess we could abstract the common properties to another parent class and have one more step of inheritance
FarmFantasy
name
play()
sleep()
eat()
User
email
username
pets
friends
adopt()
befriend()
Animal
energy
Dog
breed
bark()
Cat
declawed
meow()
That works, but it’s incredibly fragile. There’s even a name for this anti-pattern - God object.
And just like that, we see the biggest weakness with inheritance. With inheritance, you structure your classes around what they are , a User
, an Animal
, a Dog
, a Cat
- all of those words encapsulate a meaning centered around what those things are . The problem with that is a User
today will probably be different than a User
in 6 months. Inheritance makes us turn a blind eye to the inevitable fact that our class structure will most likely change in the future, and when it does, our tightly coupled inheritance structure is going to crumble.
The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. - Joe Armstrong. Creator of Erlang.
So if inheritance is such a problem, how do we get the same functionality while minimizing some of the downsides? Rather than thinking in terms of what things are , what if we think in terms of what things do ? Let’s take a Dog for example. A Dog is a sleeper, eater, player, and barker. A Cat is a sleeper, eater, player, and meower. A User is a sleeper, eater, player, adopter, and friender. Now let’s transform all of these verbs into functions.
const eater = () => ({})
const sleeper = () => ({})
const player = () => ({})
const barker = () => ({})
const meower = () => ({})
const adopter = () => ({})
const friender = () => ({})
Do you see where we’re going with this? Instead of having these methods defined (and coupled) to a particular class, if we abstract them into their own functions, we can now compose them together with any type that needs them.
Let’s take a closer look at one of our methods again, eat
.
eat(amount) {
console.log(`${this.name} is eating.`)
this.energy += amount
}
Notice that eat
logs to the console then increases the energy
property on the instance by the amount
argument. Now the question we need to answer is how we can operate on a specific instance from a one-off function? Well, what if we just pass it in when we invoke the function? Seems simple enough.
const eater = (state) => ({
eat(amount) {
console.log(`${state.name} is eating.`)
state.energy += amount
}
})
Now we can follow this same pattern for each one of our functions.
...
const sleeper = (state) => ({
sleep(length) {
console.log(`${state.name} is sleeping.`)
state.energy += length
}
})
const player = (state) => ({
play() {
console.log(`${state.name} is playing.`)
state.energy -= length
}
})
const barker = (state) => ({
bark() {
console.log('Woof Woof!')
state.energy -= .1
}
})
const meower = (state) => ({
meow() {
console.log('Meow!')
state.energy -= .1
}
})
const adopter = (state) => ({
adopt(pet) {
state.pets.push(pet)
}
})
const friender = (state) => ({
befriend(friend) {
state.friends.push(friend)
}
})
Now whenever a Dog, Cat, or User needs to add the ability to do any of the functions above, they merge the object they get from one of the functions onto their own object.
Let’s see what that looks like. We’ll start with a Dog. Earlier we defined a Dog by what it does, a Dog is a sleeper
, eater
, player
, and barker
.
function Dog (name, energy, breed) {
let dog = {
name,
energy,
breed,
}
return Object.assign(
dog,
eater(dog),
sleeper(dog),
player(dog),
barker(dog),
)
}
const leo = Dog('Leo', 10, 'Goldendoodle')
leo.eat(10) // Leo is eating
leo.bark() // Woof Woof!
Inside of Dog
, we create the “instance” using a plain old JavaScript object. Then we use Object.assign
to merge the dog’s state with all of the methods a dog should have - each defined by what a dog does , not what it is .
Now how would we create a Cat
class? Earlier we defined a Cat as a sleeper
, eater
, player
, and meower
.
function Cat (name, energy, declawed) {
let cat = {
name,
energy,
declawed,
}
return Object.assign(
cat,
eater(cat),
sleeper(cat),
player(cat),
meower(cat),
)
}
Now, what about a User
? Earlier we ran into issues when we needed to refactor our class structure so that users could also sleep
, eat
, and play
. Now that we’ve decoupled our functions from the class hierarchy, this is trivial to do.
function User (email, username) {
let user = {
email,
username,
pets: [],
friends: []
}
return Object.assign(
user,
eater(user),
sleeper(user),
player(user),
adopter(user),
friender(user),
)
}
To really test our theory, what if we wanted to give all dogs the ability to add friends as well. This wasn’t in our initial requirement, but with composition it’s pretty straight forward.
function Dog (name, energy, breed) {
let dog = {
name,
energy,
breed,
friends: []
}
return Object.assign(
dog,
eater(dog),
sleeper(dog),
player(dog),
barker(dog),
friender(dog),
)
}
By favoring composition over inheritance and thinking in terms of what things do rather than what things are , you free yourself of fragile and tightly coupled inheritance structures.
You may have noticed I’m using what we previously referred to as the “Functional Instantiation” pattern. This is mostly for preference since we’re not involving the prototype at all. If for some reason you really liked the this and new keyword, you could use the following pattern.
function Cat (name, energy, declawed) {
this.name = name
this.energy = energy
this.declawed = declawed
return Object.assign(
this,
eater(this),
sleeper(this),
player(this),
meower(this),
)
}
const charles = new Cat('Charles', 10, false)