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.
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.
Step 3 - Application State Transition
Once the initial fetch
is complete, that means that there has been an application state transition.
Step 4 - State Rendered as HTML
On any application state transition, the new state must be rendered as HTML to the user.
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.
Step 6 - New State Rendered as HTML
On any application state transition, the new state must be rendered as HTML to the user.
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
npm i -g json-server
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.