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. Looking for the old React (without the buzzwords) course?

It’s still available here.

#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 Fork 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

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 Fork 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?

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!



Try the first lesson