Teaching React to Novices

My team and I have been teaching React now for just over a year and we have learned a tremendous amount about how to lead beginners into such an abstract library and its supporting tools. Brenda Long recently rewrote the lesson plans to reduce the initial cognitive load on the student, and allows them to slowly build up their mental models of how components interact with each other.

I've also been spending time on how we can best prepare students for React by telling a story with all of the work they do beforehand. The React documentation, as is nearly always the case, is written by senior developers for other senior developers. Novice students cannot make sense of the highly abstract articles and examples that people put on the Web.

Just like we do with ASP.NET, Django, and Django REST framework, we need to break it down to a clear, focused walkthrough by writing our own documentation. Last night I spent several hours breaking down the process of building component-based applications that rely on clearly scoped state.

The next step is to redefine a good set of the examples in our course to focus more on these concepts and processes so that (hopefully) when students reach React, they simply have to learn a new syntax. Here's what I wrote in my journal for the story.

The Process

Step 1 - Initial Database State

The first step is to design the database structure and initialize the store. In the client side course, we use json-server so that the students can get a permanent, JSON-based data store up and running for their application with minimal effort.

step1

Step 2 - Application Initialization

Then the main logic of the application can be written using fetch calls to initialize the state of the application with just enough data for the initial view to be populated. The students will design 1 -> n functions to convert state objects to HTML to be rendered in the browser.

step2

Step 3 - Application State Transition

Once the initial fetch is complete, that means that there has been an application state transition.

step3

Step 4 - State Rendered as HTML

On any application state transition, the new state must be rendered as HTML to the user.

step4

Step 5 - Waiting for User Actions

Now that the initial applicaton state has been rendered to the user, the application waits for the user to initiate a new state transition in the database by either creating, deleting, or updating some data. When the database state transition is successful, the application loads that new state.

step5

Step 6 - New State Rendered as HTML

On any application state transition, the new state must be rendered as HTML to the user.

step6

Repeat 5 and 6

Now the application code just cycles between steps 5 and 6 as the user interacts with the system.

In VanillaJS

React is a library that provides wonderful abstractions to render HTML components (JSX) and manage the state transitions (Context API), but this process describes any web application, and it can all be accomplished in VanillaJS.

AnimalProvider.js

export let animals = []

const setAnimals = animalArray => {
    animals = animalArray.splice(0)
}

export const getAnimals = () => {
    return fetch("http://localhost:8000/animals")
        .then(response => response.json())
        .then(setAnimals)
}

export const deleteAnimal = id => {
    return fetch(`http://localhost:8000/animals/${id}`, {
        method: "DELETE"
    })
}

Animal.js

const Animal = () => {
    return {
        render: animal => {
            return `
                <section id="animal--${animal.id}" class="animal">
                    <h2>${animal.name}</h2>
                    <button id="deleteAnimal--${animal.id}">Delete</button>
                </section>
            `
        }
    }
}

export default Animal

AnimalList.js

import { animals, deleteAnimal } from "./AnimalProvider.js"
import Animal from "./Animal.js"

const animalContainer = document.querySelector("#animals")

// Event to handle clicking the delete button
animalContainer.addEventListener("click", evt => {

    // Was the delete button pressed?
    if (evt.target.id.startsWith("deleteAnimal--")) {
        
        // Get animal's `id` property
        const animalId = evt.target.id.split("--")[1]
        
        /* 
            1. Perform database state transition 
            2. Then perform application state transition
            3. Then render new application state
        */
        deleteAnimal(animalId)
            .then(getAnimals)
            .then(AnimalList().render)
    }
})

const AnimalList = () => {
    return {
        render: () => {
            animalContainer.innerHTML = ""

            for (const animal of animals) {
                animalContainer.innerHTML += Animal().render(animal)
            }
        }
    }
}

export default AnimalList

main.js

import { getAnimals } from "./AnimalProvider.js"
import AnimalList from "./AnimalList.js"

getAnimals().then( AnimalList().render )

animals.json

  1. npm i -g json-server
  2. json-server -w animals.js -p 8000
{
    "animals": [
        {
            "id": 1,
            "name": "Gypsy",
            "breed": "Schnauzer"
        },
        {
            "id": 2,
            "name": "Rascal",
            "breed": "Schnauzer"
        },
        {
            "id": 3,
            "name": "Angus",
            "breed": "Australian Shepherd"
        }
    ]
}

That code is a good, simple approximation of what React is doing to define components, respond to state trasitions, and rendering any application state as HTML.