Object-Oriented Software Engineering

This is an archived version of https://www.jhu-oose.com that I (Leandro Facchinetti) developed when teaching the course in the Fall of 2019. Some of the links may be broken.

Lecture 6: Implementation · React

Video 🔒

Code: Fetch the rock-paper-scissors branch of TODOOSE.

Mounting

You go from regular HTML to React with:

ReactDOM.render(«React HTML-like tags», «An element from the existing HTML»);

for example:

ReactDOM.render(<Game/>, document.querySelector("#game"));

You can do this several times with different elements from the existing HTML.

JSX

We aren’t writing on pure JavaScript, but on an extension of JavaScript called JSX. The difference is that in JSX we can write HTML interspersed with JavaScript.

Going from JavaScript to HTML

Just use an HTML tag somewhere in the JavaScript, for example:

const aButton = <button>I’m a button</button>;

Going from HTML back to JavaScript

Delimit the JavaScript with {}, for example:

const thirdButton = <button>I’m button number {1 + 2}</button>;

In the example above, the <button>___</button> means “go to HTML,” as mentioned above. And the {1 + 2} means “go back to JavaScript.” The result is that the 1 + 2 is computed in JavaScript and the thirdButton will read I’m button number 3.

React Components

A React component is a new kind of HTML tag.

To tell a React component apart from an HTML tag, look at the first letter: a React component starts with a capital letter (for example, Game), while an HTML tag starts with a lowercase letter (for example, button).

A React component may include HTML tags or other React components.

A React component may be defined with a function or a class that inherits from React.Component and defines a method called render(). For example:

function AReactComponentDefinedWithAFunction() {
    return <button>I’m a React Component</button>;
}

class AnotherReactComponentDefinedWithAClass extends React.Component {
    render() {
        return <button>I’m another React Component</button>;
    }
}

To use a React component, refer to it as if it were an HTML tag, for example, <AReactComponentDefinedWithAFunction/>.

A React component must return exactly one tag. If you need to return multiple tags, wrap them in another tag, for example:

// ⚠️  This doesn’t work!
function MyTwoButtons() {
    return (
        <button>I’m a button 1</button>
        <button>And I’m a button 2</button>
    );
}

// 👍  But this does!
function MyTwoButtons() {
    return (
        <div>
            <button>I’m a button 1</button>
            <button>And I’m a button 2</button>
        </div>
    );
}

The parenthesis after the return keyword is mandatory because of JavaScript’s Automatic Semicolon Insertion (ASI).

Props

Props are arguments passed to the component, similar to how you can pass arguments when calling a function.

When a component is defined with a function, the props are the only function argument, for example:

function MyButton(props) {
    return <button>I’m button number {props.number}</button>
}

When a component is defined with a class, the props are available as an object attribute, for example:

class MyButton extends React.Component {
    render() {
        return <button>I’m button number {this.props.number}</button>
    }
}

To pass props to the component when using it, use HTML-like properties, for example:

const thirdButton = <MyButton number={1 + 2}/>;

Props must never be changed from within the component, for example:

// ⚠️  Don’t change props
function MyButton(props) {
    props.number = 4; // ⚠️
    return <button>I’m button number {props.number}</button>
}

Props flow from the parent component to its children, never the other way around.

State

State is like mutable object attributes. For example, in Java you may have:

class Person {
    private String name = "Roboose";
    private int age = 28;

    // Constructors, getters, setters, and so forth...
}

and you can use setters to change name and age, for example:

person.setAge(29);

An equivalent React component reads like:

class Person extends React.Component {
    constructor(props) {
        super(props);
        this.state = { name: "Roboose", age: 28 };
    }

    render() {
        // ...
    }
}

and you can use setState() to update the state, for example:

person.setState({ age: 29 });

You must never change the state directly:

// ⚠️  Don’t change the state directly
person.state.age = 29;

That’s because React must re-render the component when the state is updated. React can’t detect direct changes to the state, but it can detect changes to the state made through setState().

Under the hood, what’s happening is that React is calling render() on the component anytime its state changes. Also, it’s smart enough to only re-render the children that changed, for example, if Person includes a child component Name that doesn’t depend on age, then the setState({ age: 29 }) above will not re-render Name.

👉  The main feature in React, and the reason why we use it, is this capability of detecting changes and only re-rendering what’s necessary. Without React we’d need to find what data goes where and update everything by hand—and if we missed something things would go out of sync.

Events

Events are things like the user clicking on a button, data arriving from the server, a certain amount of time having passed, and so forth.

We handle events in React in a similar way we’d handle them in HTML, for example:

function MyButton() {
    return (
        <button onClick={
          () => { alert("You clicked the button"); }
        }>
            I’m a React Component
        </button>
    );
}

State Lifting

A child component can’t change the state of its parent, but sometimes that’s what we’d like to do, for example:

class Person extends React.Component {
    constructor(props) {
        super(props);
        this.state = { name: "Roboose", age: 28 };
    }

    render() {
        return <BirthdayButton/>
    }
}

function BirthdayButton() {
    return (
        <button onClick={
          () => { alert("I’d like to update the age of person"); }
        }>
            Birthday
        </button>
    );
}

The most common technique to do this is to pass a prop to the child that is a function to update the state, for example:

class Person extends React.Component {
    constructor(props) {
        super(props);
        this.state = { name: "Roboose", age: 28 };
    }

    render() {
        return (
            <BirthdayButton onClick={
                () => {
                  this.setState({ age: this.state.age + 1 });
                } 
            }/>
        );
    }
}

function BirthdayButton(props) {
    return (
        <button onClick={ () => { props.onClick(); } }>
            Birthday
        </button>
    );
}