React Fundamentals

Without the buzzwords – no Redux, no GraphQL, no CSS-in-JS.

Learn React's fundamentals with billionaires, fractal trees and live exercises. Get practice building a real-world app with form validation, asynchronous storage, and proper structure – and all without touching Redux, GraphQL or CSS-in-JS!

Get started

React has exploded in popularity — and for good reason! It makes building real-world apps a breeze.

The thing is, React comes with a boatload of buzzwords. Redux and GraphQL and Next and Gatsby and CSS-in-JS and how am I supposed to get anything done when keeping up with the ecosystem is a full time job in itself?

Forget the buzzwords.

It’s easy to grow tired of learning new tools every other week, so here’s a little secret: you can accomplish amazing things with nothing but the fundamentals.

#React first, ecosystem later

It’s true that React’s ecosystem is invaluable. In the right circumstances, tools like Redux, Styled Components and Next.js can give you a serious leg up. And what’s more, there’s a huge amount of material out there for full stack React — including my follow on course that walks you through building a real-world app with React, Firebase and Stripe.

But say that you’re just starting out.

Trying to learn React and its ecosystem at the same time can be confusing at the best of times. Not to mention that anything you learn about the ecosystem can quickly go out of date — while React itself has a well-deserved reputation for stability.

Here’s the thing: learning React by itself will give you the most solid foundation available. But what’s more, it’ll also let you get a darn lot done right now. That’s because with modern React, you don’t just get a library, you also get a configuration-free build system called Create React App — which we’ll be using in this course. And speaking of builds…

#What you’ll build

This course is designed to make you learn by doing. It includes a ton of live exercises, which you can complete comfortably within the browser. And while there are too many exercises to list them all here, there are two recurring projects that you’ll be gradually piecing together.

#The fractal tree

We’ll start by exploring React’s basics with this fractal tree. The code is live — try playing around with the numbers next to lean and sprout to change the animation, or try moving your mouse over the tree to see it respond!

index.js
FractalTreeBranch.js
FractalHelpers.js
import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom'
import FractalTreeBranch from './FractalTreeBranch'

// The height and width of the entire window
const { innerHeight, innerWidth } = window

const FractalTree = () => {
  let [time, setTime] = useState(() => Date.now())
  let [mousePosition, setMousePosition] = useState({
    x: innerWidth / 2,
    y: innerHeight / 2,
  })
  useEffect(() => {
    let requestId = window.requestAnimationFrame(() => {
      setTime(Date.now())
    })
    return () => {
      window.cancelAnimationFrame(requestId)
    }
  })
  let fromHorizontalCenter = (innerWidth / 2 - mousePosition.x) / innerWidth
  let fromVerticalCenter = (innerHeight / 2 - mousePosition.y) / innerHeight
  let lean = 0.03 * Math.sin(time / 2000) + fromHorizontalCenter / 4
  let sprout =
    0.3 +
    0.05 * Math.sin(time / 1300) +
    fromVerticalCenter / 5 -
    0.2 * Math.abs(0.5 - fromHorizontalCenter / 2)

  return (
    <div
      style={{
        position: 'absolute',
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        overflow: 'hidden',
      }}
      onMouseMove={event => {
        setMousePosition({
          x: event.clientX,
          y: event.clientY,
        })
      }}>
      <FractalTreeBranch lean={lean} size={150} sprout={sprout} />
    </div>
  )
}

ReactDOM.render(<FractalTree />, document.getElementById('root'))
Build In Progress

#The contact list

While fractal trees are fun, billionaires are better at paying the bills. That’s not actually why we’ll be building this contact list though — it’s more because it introduces useful things like forms, APIs and structure. Also, seeing your name in a list of billionaires is a lot of fun. Just try it by adding your name below!

index.js
Contacts.js
api.js
styles.css
index.html
import React from 'react'
import ReactDOM from 'react-dom'
import { createRecord, deleteRecord, getRecords, patchRecord } from './api'
import { Contact, ContactForm, ContactList } from './Contacts'

class App extends React.Component {
  constructor() {
    super()

    this.state = {
      contacts: [],
      contactsError: null,
      
      contactForm: {
        name: '',
        email: '',
      },
      contactFormErrors: null,
      contactFormId: null,
    }

    this.addContact = this.addContact.bind(this)
    this.cancelEditingContact = this.cancelEditingContact.bind(this)
    this.changeContactForm = this.changeContactForm.bind(this)
    this.deleteContact = this.deleteContact.bind(this)
    this.patchContact = this.patchContact.bind(this)
    this.refresh = this.refresh.bind(this)
    this.startEditingContact = this.startEditingContact.bind(this)
  }

  componentDidMount() {
    this.refresh()
  }

  render() {
    let content
    if (this.state.contactsError) {
      content = (
        <p>
          {this.state.contactsError}
        </p>
      )
    }
    else {
      content = this.state.contacts.map((contact, i) =>
        this.state.contactFormId === contact.id ? (
          <ContactForm
            key={i}
            value={this.state.contactForm}
            errors={this.state.contactFormErrors}
            onChange={this.changeContactForm}
            onClickCancel={this.cancelEditingContact}
            onSubmit={this.patchContact}
          />
        ) : (
          <Contact
            {...contact}
            key={i}
            error={
              this.state.deleteContactError === contact.id &&
              "Could not be deleted."
            }
            onClickEdit={this.startEditingContact}
            onClickDelete={this.deleteContact}
          />
        )
      )
    }
  
    return (
      <ContactList onClickRefresh={this.refresh}>
        {content}
        {!this.state.contactFormId && (
          <ContactForm
            errors={this.state.contactFormErrors}
            value={this.state.contactForm}
            onChange={this.changeContactForm}
            onSubmit={this.addContact}
          />
        )}
      </ContactList>
    )
  }
  
  changeContactForm(value) {
    this.setState({
      contactForm: value,
      contactFormErrors:
        silenceRectifiedErrors(this.state.contactFormErrors, validateContact(value))
    })
  }
  
  addContact() {
    let errors = validateContact(this.state.contactForm)
    if (errors) {
      this.setState({
        contactFormErrors: errors,
      })
    }
    else {
      createRecord(this.state.contactForm).then(
        (response) => {
          this.setState({
            contacts: this.state.contacts.concat(response.data),
            contactForm: {
              name: '',
              email: '',
            },
            contactFormErrors: null,
          })
        },
        () => {
          this.setState({
            contactFormErrors: {
              base: "Something went wrong while saving your contact :-(",
            }
          })
        }
      )
    }
  }

  patchContact() {
    let errors = validateContact(this.state.contactForm)
    
    if (errors) {
      this.setState({
        editingErrors: errors,
      })
    }
    else {
      let id = this.state.contactFormId
    
      patchRecord(id, this.state.contactForm).then(
        (response) => {
          // Make a clone of the stored contacts.
          let newContacts = this.state.contacts.slice(0)
          
          // Find the contact with the correct id
          let i = newContacts.findIndex(contact => contact.id === id)
          
          // Update the contact in this.state
          newContacts[i] = response.data
        
          this.setState({
            contacts: newContacts,
            contactForm: {
              name: '',
              email: '',
            },
            contactFormId: null,
            contactFormErrors: null,
          })
        },
        (error) => {
          this.setState({
            contactFormErrors: {
              base: "Something went wrong while saving your contact :-(",
            }
          })
        }
      )
    }
  }
  
  deleteContact(id) {
    deleteRecord(id).then(
      (response) => {
        this.setState({
          contacts: this.state.contacts.filter(contact => contact.id !== id),
          deleteContactError: null,
        })
      },
      (error) => {
        this.setState({
          deleteContactError: id,
        })
      }
    )
  }
  
  cancelEditingContact() {
    this.setState({
      contactForm: {},
      contactFormId: null,
      contactFormErrors: null,
    })
  }
  
  startEditingContact(id) {
    this.setState({
      contactFormId: id,
      contactForm: this.state.contacts.find(contact => contact.id === id),
    })
  }

  refresh() {
    getRecords().then(
      (response) => {
        this.setState({
          contacts: response.data,
          contactsError: null,
        })
      },
      (error) => {
        this.setState({
          contactsError: "Your contacts couldn't be loaded :-("
        })
      }
    ) 
  }
}

function validateContact(contact) {
  let errors = {}
  if (!contact.name) {
    errors.name = "You must enter a name."
  }
  if (!contact.email) {
    errors.email = "You must enter an email."
  }
  else if (!isEmailValid(contact.email)) {
    errors.email = "That doesn't look like a valid e-mail."
  }
  if (Object.keys(errors).length) {
    return errors
  }
}

function isEmailValid(value) {
  return value.indexOf('@') !== -1
}

function silenceRectifiedErrors(oldErrors, newErrors) {
  if (newErrors && oldErrors) {
    let errors = {}
    for (let key of Object.keys(newErrors)) {
      if (oldErrors[key]) {
        errors[key] = oldErrors[key]
      }
    }
    return Object.keys(errors).length ? errors : null
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
Build In Progress

#What will you learn?

You’ll learn everything you need to build kick-ass apps.

  • Props
  • Elements
  • Components
  • State
  • Events
  • Class components and lifecycle methods
  • Forms and validation
  • Working with apis
  • Structure
  • Basic hooks
  • Effects
  • Context coming soon

Along the way, you’ll also get detailed discussion on pain points including:

  • What’s the difference between elements, components and instances?
  • How does JSX actually work?
  • Why should I use key props with arrays?
  • When is it ok to use this.state?

#Learn together

Along with the coursework, you’ll also receive an invitation to the Frontend Armory Pro community — where you can discuss the source, the coursework, and the tastiest bacon recipes with like-minded developers. I’ll also be actively participating in this community and answering questions — so do say hi once you join! 👋

#Are you ready to level up your Frontend game?

Okay, so say you want complete access to:

  • The coursework
  • The community
  • The members-only updates, and
  • All other member-exclusive content on Frontend Armory Pro

How do you get it? Just join Frontend Armory Pro! It’s only $18/month, it gives you immediate access to everything, and you can still cancel at any time!

And honestly, this is a bargain for what you get — it’s less than the cost of a latte a week. Imagine how much time you’ll save with a solid foundation, or how much you’ll get from a good raise? Hell, even $180 is too cheap for what you get here, but that’s all you’ll pay for an entire year of access — a saving of 2 months free over the monthly price.

Are you ready to level up your frontend game? Then what are you waiting for‽ Click the button below and join Frontend Armory Pro now!


Unlock full access

   Try the first lesson