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. Next.js and GraphQL and Gatsby and Redux 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.

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 fundamental React concepts like elements, components and JSX have 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?

#Are you ready to level up your Frontend game?

Okay, so say you want complete access to this course. How do you get it?

You’ve already got access to it! It’s $40/month $18/month absolutely free!

This course was originally part of Frontend Armory Pro, but due to the demonetization of this website, you can now access it for free — no strings attached.

So are you ready to level up your frontend game? Then what are you waiting for‽ Click the button below to dive into lesson one right now!


Start the first lesson – it's free!