React

Timothy J. Aveni

Slides available at tja.io/hackgt5/slides

What is React?

React is a declarative framework for building user interfaces in JavaScript.

Vanilla JavaScript

The DOM lets you manipulate page content with JavaScript.


hello, world


const paragraphElement = document.getElementById('content');

paragraphElement.innerHTML = 'goodbye, then';

The DOM is powerful

It can...

// create elements, const image = document.createElement('img');
// modify attributes, image.src = 'https://hack.gt/logo.png';
// change CSS, image.style.border = '3px solid black';
// and restructure the page. document.body.appendChild(image);

Anything you can imagine changing about the page's HTML or CSS can be changed with the DOM.

Events

The DOM lets you register functions that listen for events on the web page.


image.onclick = (event) => {
    alert(1);
};
						

With these basics, we should be able to make pretty much anything.

We just got our first client: a hip new restaurant!

The restaurant has a basic website, but the menu is out of date.

yeah ok it's ugly. but it works!



					

Here's the problem: our client has a breakfast menu now!

Instead of having two separate menu pages, let's add JavaScript buttons to flip between the breakfast and lunch menu.

<div id="container">
<button id="breakfastButton">Show breakfast menu</button> <button id="lunchButton">Show lunch menu</button>
<div id="menu"> <div id="item1">Churro: $1.20</div> <div id="item2">Beef burrito: $4.00</div> <div id="item3">Cheese quesadilla: $2.50</div> </div> </div>
<script src="index.js"></script>
const breakfastButton = document.getElementById('breakfastButton'); const lunchButton = document.getElementById('lunchButton'); const item1 = document.getElementById('item1'); const item2 = document.getElementById('item2'); const item3 = document.getElementById('item3');
breakfastButton.onclick = (event) => { item1.innerHTML = 'Bacon and egg burrito: $3.50'; item2.innerHTML = 'Potato and cheese burrito: $2.50'; item3.innerHTML = 'Breakfast salad: $2.00'; }; lunchButton.onclick = (event) => { item1.innerHTML = 'Churro: $1.20'; item2.innerHTML = 'Beef burrito: $4.00'; item3.innerHTML = 'Cheese quesadilla: $2.50'; };

Cool, we did it! The client has a dynamic JavaScript™ menu.

The client is happy with our work, but after a while, they want us to make a change.

It's a pretty simple change: since the client has vegetarian customers, we should offer the user the ability to highlight which foods are vegetarian.

Now, the user can check or uncheck the veggie checkbox to get some more information about the menu.

<div id="container">
    <button id="breakfastButton">Show breakfast menu</button>
    <button id="lunchButton">Show lunch menu</button>
<label> <input type="checkbox" id="vegCheck"> Highlight vegetarian options </label>
<div id="menu"> <div id="item1">Churro: $1.20</div> <div id="item2">Beef burrito: $4.00</div> <div id="item3">Cheese quesadilla: $2.50</div> </div> </div> <script src="index.js"></script>
const breakfastButton
    = document.getElementById('breakfastButton');
const lunchButton
    = document.getElementById('lunchButton');

const item1 = document.getElementById('item1');
const item2 = document.getElementById('item2');
const item3 = document.getElementById('item3');

let currentMenu = 'lunch';
breakfastButton.onclick = (event) => {
currentMenu = 'breakfast';
item1.innerHTML = 'Bacon and egg burrito: $3.50';
item2.innerHTML = 'Potato and cheese burrito: $2.50';
item3.innerHTML = 'Breakfast salad: $2.00';
}; lunchButton.onclick = (event) => {
currentMenu = 'lunch';
item1.innerHTML = 'Churro: $1.20';
item2.innerHTML = 'Beef burrito: $4.00';
item3.innerHTML = 'Cheese quesadilla: $2.50';
};
const vegCheck = document.getElementById('vegCheck');

vegCheck.onchange = (event) => {
    if (vegCheck.checked) {
        if (currentMenu === 'breakfast') {
            item2.classList.add('highlight');
            item3.classList.add('highlight');
        } else if (currentMenu === 'lunch') {
            item1.classList.add('highlight');
            item3.classList.add('highlight');
        }
    } else {
        item1.classList.remove('highlight');
        item2.classList.remove('highlight');
        item3.classList.remove('highlight');
    }
};

Beautiful! We've made the client happy again.

A week passes.

The client calls us.

The client is furious.

The client sends us this screenshot, from a customer:

Beef burritos are NOT vegetarian.

Did you spot the bug?

As web applications get more and more complex, this kind of error gets harder to avoid.

Here's the problem: for every transition between two program states, you're responsible for the code that handles that transition.

currentMenu = 'breakfast',
veg = false
currentMenu = 'breakfast',
veg = true
currentMenu = 'lunch',
veg = false
currentMenu = 'lunch',
veg = true

Let's talk theory for a second.

With four distinct program states, we're responsible for up to 4 × 3 transitions.

If we add a new state, now we're responsible for 5 × 4 transitions.

And then another... That's n × (n - 1) transitions! The transition space increases quadratically with respect to state space.

That makes for pretty buggy software.

Nuke-and-start-over

One way to solve this problem is to rethink how we handle state transitions.

Right now, our code handles an event by mutating the DOM so that the page looks how we want it to.

We could simplify this by just deleting everything when the state changes and redrawing from scratch.

This isn't such a bad idea: this is how video games work.

We'll make the HTML simpler, since all the rendering will get done with JavaScript.


<div id="container">
    <button id="breakfastButton">Show breakfast menu</button>
    <button id="lunchButton">Show lunch menu</button>
    <label>
        <input type="checkbox" id="vegCheck"> Highlight vegetarian options
    </label>

<div id="menu"></div>
</div> <script src="index.js"></script>
const breakfastButton =
    document.getElementById('breakfastButton');
const lunchButton = document.getElementById('lunchButton');
const vegCheck = document.getElementById('vegCheck');

const menuContainer = document.getElementById('menu');

let currentMenu = 'lunch'; let veg = false;
breakfastButton.onclick = (event) => {
currentMenu = 'breakfast';
render();
}; lunchButton.onclick = (event) => {
currentMenu = 'lunch';
render();
}; vegCheck.onchange = (event) => {
veg = vegCheck.checked;
render();
};
const render = () => {
menuContainer.innerHTML = '';
const item1 = document.createElement('div'); const item2 = document.createElement('div'); const item3 = document.createElement('div'); if (currentMenu === 'breakfast') { item1.innerHTML = 'Bacon and egg burrito: $3.50'; item2.innerHTML = 'Potato and cheese burrito: $2.50'; item3.innerHTML = 'Breakfast salad: $2.00'; if (veg) { item2.classList.add('highlight'); item3.classList.add('highlight'); } } else if (currentMenu === 'lunch') { item1.innerHTML = 'Churro: $1.20'; item2.innerHTML = 'Beef burrito: $4.00'; item3.innerHTML = 'Cheese quesadilla: $2.50'; if (veg) { item1.classList.add('highlight'); item3.classList.add('highlight'); } }
menuContainer.appendChild(item1);
menuContainer.appendChild(item2);
menuContainer.appendChild(item3);
};
render();

Nuke-and-start-over

It works!

Back to the theory

Why is this easier to code?

We insert an intermediate state — the "nuked" state — where nothing is rendered.

To get from one state to another, we go through the empty state.

Getting to the intermediate state is trivial, so we only need to write code that renders each state from scratch.

Problems

What are the problems with the nuke-and-start-over approach?

Efficiency!

  • The DOM is meant to handle mutations. It's not a video game engine. It's bad at re-rendering everything for every state change.
  • Doing this for your entire web app is infeasible.

The DOM is stateful.

  • There's information stored in the webpage that gets lost if you delete everything.
  • Some examples are scroll position, scroll momentum, and text field focus.

React! (finally)

React lets us write code with the nuke-and-start-over mindset.

We write code that takes in program state and renders from scratch.

Rather than rendering directly to the page's DOM, our render function renders to a buffer in React called the "virtual DOM".

React looks at the differences between the virtual DOM before and after the state change, and it makes only the changes necessary.


// Virtual DOM (before)

<div id="menu">
    <div id="item1" class="highlight">
Churro: $1.20
</div> <div id="item2">
Beef burrito: $4.00
</div> <div id="item3" class="highlight">
Cheese quesadilla: $2.50
</div> </div>

// Virtual DOM (after)

<div id="menu">
    <div id="item1">
Bacon and egg burrito: $3.50
</div> <div id="item2" class="highlight">
Potato and cheese burrito: $2.50
</div> <div id="item3" class="highlight">
Breakfast salad: $2.00
</div> </div>

item1.classList.remove('highlight');
item2.classList.add('highlight');
					

item1.innerHTML = 'Bacon and egg burrito: $3.50';
item2.innerHTML = 'Potato and cheese burrito: $2.50';
item3.innerHTML = 'Breakfast salad: $2.00';
					

The best of both worlds

React lets you focus on how a particular program state should look, rather than how to transition from one state to another.

React code is declarative, not imperative.

A powerful engine diffs the "before" and "after" markup behind the scenes and translates the changes into a set of DOM mutations.

React is efficient and doesn't destroy DOM state, but it has all the benefits of the nuke-and-start-over approach.

Let's get started

get out your laptops kiddos, we're gonna write some CODE together

We'll be working on codesandbox.io. You can work on our React app right in your browser.

Hello, world

Let's have a look at the JavaScript!

class App extends React.Component {
    render() {
        return (
            <div className="App">
                Hello, world.
            </div>
        );
    }
}

excuse me what

what is that MONSTROSITY

what kind of JAVASCRIPT has <div> tags in it????

JSX

That, my pupil, is JSX.

JSX is an extension to JavaScript syntax that lets you include markup right in your JavaScript.

To use it, you need a JSX-to-vanilla-JavaScript translation engine, like Babel.

Babel is part of the sandbox we're using for this workshop, so you don't have to worry about it for now. In the future, you can set up Babel and the rest of your development environment using the create-react-app package on npm.

JSX

Here's what that translation looks like:

// JSX input:

<div className="App">
    <p>Hello, <strong>world</strong>.</p>
</div>
// Babel output:

React.createElement(
    "div",
    { className: "App" },
    React.createElement(
        "p",
        null,
        "Hello, ",
        React.createElement(
            "strong",
            null,
            "world"
        ),
        "."
    ),
);

The App component

class App extends React.Component {
    render() {
        return (
            <div className="App">
                Hello, world.
            </div>
        );
    }
}

We create a class extending React.Component. This tells React that we want this class to use React's rendering engine.

Every component needs a render() method. This method must return a string, some renderable JSX, or null if it shouldn't render anything.

JSX expressions

render() {
    const name = 'tim';
    const listElements = [
        <li>apples</li>,
        <li>bananas</li>,
        <li>oranges</li>,
    ];

    return (
        <div className="App">
            <p>Hello, <strong>world</strong>.</p>
            <p>2 + 3 is {2 + 3}.</p>
            <p>My name is {name.toUpperCase()}.</p>
            <ul>{listElements}</ul>
        </div>
    );
}

JSX can contain:

  • HTML tags (with one small tweak)
  • React components
  • JavaScript expressions
  • Arrays (of any of these)

Todo list

Today, we'll make a simple todo-list app in React.

You'll learn how to manage program state and handle user input.

Components

We're not limited to just one App component. We can add and nest components however we like.

To do this, we just make more classes like the App component.

One rule: component names need to start with a capital letter. That's how React tells between components and HTML tags.

Let's separate our app into a Header and a TodoList component.

// App.js

import React from 'react';
import Header from './Header';
import TodoList from './TodoList';

class App extends React.Component {
    render() {
        return (
            <div className="App">
                <Header />
                <TodoList />
            </div>
        );
    }
}

export default App;
// Header.js

import React from 'react';

class Header extends React.Component {
    render() {
        return (
            <div className="Header">
                <h1>Todos</h1>
            </div>
        );
    }
}

export default Header;
// TodoList.js

import React from 'react';

class TodoList extends React.Component {
    render() {
        return (
            <div className="TodoList">
                <div className="Todo">
                    Buy apples
                </div>
                <div className="Todo">
                    Start AVL homework
                </div>
                <div className="Todo">
                    Win HackGT 5
                </div>
            </div>
        );
    }
}

export default TodoList;

Props

To avoid repetition, it'd be nice if we could extract each todo into its own <Todo /> component.

We need to be able to pass information to the Todo component. Otherwise, every instance of the component will look the same.

In HTML, elements can have attributes and children.

<a href="https://tim.works/">
    my resume!
</a>

Props

React uses props to handle both attributes and children.

Within a React component, the this.props object contains every attribute present at the creation site. this.props.children contains an array of everything passed as a child.

<MyComponent title="Hello!">
    <SomeChild />
    <SomeOtherChild />
</MyComponent>
 
// Inside this instance of MyComponent:

this.props.title // === 'Hello!'
this.props.children // === [<SomeChild />, <SomeOtherChild />]
							

Props

Now we can wrap each todo item in a <Todo /> component.

We'll use props to tell the component what text to render.

// from TodoList.js

<div className="TodoList">
    <Todo text="Buy apples" />
    <Todo text="Start AVL homework" />
    <Todo text="Win HackGT 5" />
</div>


// from Todo.js

render() {
    return (
        <div className="Todo">
            {this.props.text}
        </div>
    );
}

When these props change, React re-renders the components efficiently.

Arrays

React lets us render arrays of components.

This can help us reduce our boilerplate even further.

// from TodoList.js

render() {
    const todos = [
        'Buy apples',
        'Start AVL homework',
        'Win HackGT 5',
    ];

    const todoComponents = [];
    for (let i = 0; i < todos.length; i++) {
        todoComponents.push(<Todo key={i} text={todos[i]} />);
    }

    return (
        <div className="TodoList">
            {todoComponents}
        </div>
    );
}

State

Within a component, you can't change your props. You get those from your parent.

What you can change is component state.

State is stored as a variable inside the class, and it's accessed using this.state.

State is a special, React-controlled object. You should never change state directly. Treat state as fully immutable.

Here's how we might store our Todos inside component state.

// from TodoList.js

class TodoList extends React.Component {
    state = {
        todos: [
            'Buy apples',
            'Start AVL homework',
            'Win HackGT 5',
        ],
    };

    render() {
        const todoComponents = [];
        for (let i = 0; i < this.state.todos.length; i++) {
            todoComponents.push(<Todo key={i} text={this.state.todos[i]} />);
        }

        return (
            <div className="TodoList">
                {todoComponents}
            </div>
        );
    }
}

This doesn't look very interesting. We just moved our "todos" variable outside of the render function.

But React gives us a this.setState() function that lets us update state and tells React to re-render our components efficiently.

state = {
    a: 4,
    b: 8,
    c: [9, 14],
}

this.setState({
    a: 7,
    c: [1, 3],
});
// updated state:

state = {
    a: 7,
    b: 8,
    c: [1, 3],
}



Let's add some code to let us check off todo list items.

We'll add a button next to each todo that lets us delete that todo.

Remember that we can never mutate state directly.

When a button is clicked, we'll make a copy of the old todos array, remove the associated todo, then set the new array in the state.

// from TodoList.js

const todoComponents = [];
for (let i = 0; i < this.state.todos.length; i++) {
    const deleteFunction = () => {
        // make a copy of the todos array.
        const newTodos = this.state.todos.slice();
        // we change the COPY, not the original.
        newTodos.splice(i, 1);
        this.setState({ todos: newTodos });
    };

    todoComponents.push(
        <div>
            <button className="deleteButton" onClick={deleteFunction}>X</button>
            <Todo key={i} text={this.state.todos[i]} />
        </div>
    );
}

The delete button probably belongs in the Todo component, not in the TodoList component.

But how would that work? We can't put the todos array in the Todo component, because that component only renders one item.

How can we update the TodoList component's state from a button in the Todo component?

Prop callbacks

The answer is: we don't. Instead, we write a function in TodoList that handles the "delete" event, and then we give Todo that function as a prop.

The Todo component handles the button press and calls the function from its props, and that event bubbles up to TodoList where it can be handled.

// from Todo.js

<div>
    <button onClick={this.props.onDelete}>X</button>
    <div className="Todo">
        {this.props.text}
    </div>
</div>
// from TodoList.js

todoComponents.push(
    <Todo
        key={i}
        text={this.state.todos[i]}
        onDelete={deleteFunction} />
);

Adding todos

Let's add a button to the Header component to add a todo.

We have a similar problem as before: how do we update the state in TodoList from the Header component?

Prop callbacks only let us move data further up in the component hierarchy. We can't move information up from Header into App, and then back down into TodoList.

Lifting state up

This happens pretty often in React applications.

The solution is to lift state up.

Component state should be high enough in the hierarchy that data only flows downwards, but no higher.

In our case, it's time to bring the todos array into the App component.

// from App.js

class App extends React.Component {
    state = {
        todos: [
            'Buy apples',
            'Start AVL homework',
            'Win HackGT 5',
        ],
    };

    render() {
        return (
            <div className="App">
                <Header />
                <TodoList
                    todos={this.state.todos}
                    onDeleteTodo={this.onDeleteTodo} />
            </div>
        );
    }

    onDeleteTodo = (i) => {
        // make a copy of the todos array.
        const newTodos = this.state.todos.slice();
        // we change the COPY, not the original.
        newTodos.splice(i, 1);
        this.setState({ todos: newTodos });
    };
}
// from TodoList.js

class TodoList extends React.Component {
    render() {
        const todoComponents = [];
        for (let i = 0; i < this.props.todos.length; i++) {
            const deleteFunction = () => {
                this.props.onDeleteTodo(i);
            };

            todoComponents.push(
                <Todo
                    key={i}
                    text={this.props.todos[i]}
                    onDelete={deleteFunction} />
            );
        }

        return (
            <div className="TodoList">
                {todoComponents}
            </div>
        );
    }
}

Now that the state is high enough, let's make an "add todo" button.

// from App.js

<div className="App">
    <Header
        onAddTodo={this.onAddTodo} />
    <TodoList
        todos={this.state.todos}
        onDeleteTodo={this.onDeleteTodo} />
</div>

// ...

onAddTodo = (text) => {
    if (text === '') return;

    const newTodos = this.state.todos.slice();
    newTodos.push(text);
    this.setState({ todos: newTodos });
};

// from Header.js

class Header extends React.Component {
    render() {
        return (
            <div className="Header">
                <h1>Todos</h1>
                <button
                    className="addButton"
                    onClick={this.onAddTodo}>
                    Add todo
                </button>
            </div>
        );
    }

    onAddTodo = () => {
        this.props.onAddTodo('Call mom');
    };
}

User input

Form components are a little tricky to get right.

<input type="text" />

If we make an <input /> tag like in normal HTML, we don't have any way to access the value of the text field.

We can tell React to render the result of any JavaScript expression inside the <input /> tag, just like with other HTML tags. We use the value attribute to accomplish this.

User input

<input type="text" value={this.state.text} />

Now the text field will sets its value to this.state.text, updating the text field whenever the component's state changes.

But the text field isn't configured to update this.state when the user changes the text, so the text field isn't editable.

To use a form control in React, we let React component state be the single source of truth for this component.

const updateText = (event) => {
    this.setState({ text: event.target.value });
};

<input type="text" value={this.state.text} onChange={updateText} />

We make a controlled component by binding the value prop on the <input /> tag to the component state.

Then, we add an onChange handler to the component so that we can update component state whenever the field is updated.

class Header extends React.Component {
    state = {
        editingTodo: 'Call mom',
    };

    render() {
        return (
            <div className="Header">
                <h1>Todos</h1>
                <input
                    value={this.state.editingTodo}
                    onChange={this.onEditTodo} />
                <button
                    className="addButton"
                    onClick={this.onAddTodo}>
                    Add todo
                </button>
            </div>
        );
    }

    onEditTodo = (event) => {
        this.setState({ editingTodo: event.target.value });
    };

    onAddTodo = () => {
        this.props.onAddTodo(this.state.editingTodo);
        this.setState({ editingTodo: '' });
    };
}

Further reading

The full React docs are at reactjs.org.

The main topic we didn't cover today is lifecycle hooks.

Data management getting hairy? Try Redux!

Want to use React to make a mobile app? Try React Native!

What about virtual reality? React VR is a thing!

Thanks

I'm always available if you have any questions. The best way to reach me is on Facebook Messenger: m.me/timothy.j.aveni

Go call your mom!