React Continued

CSCI-UA.0480-008

A Little Bit of Refresher

What's react again?

On its own React is a library for generating the user interface of an application.

  • think of it as the view in an MVC app
  • it provides an API for creating and rendering reusable view components
    • including state management
    • …and event handling

Running a React App

What did we use to demonstrate our small React examples?

We used Codepen (jsbin is also an option). However… we had to do a little bit of configuration first.

  • for codepen:
    • set Babel as the JavaScript preprocessor
    • add the react libaray as external JavaScript
  • for jsbin:
    • use Add Library to add react
    • select JSX (React) in the JavaScript drop down

Creating an Element

ReactElements are elements in a virtual DOM. Here's what creating a ReactElement looks like…


React.createElement('h1', {className: 'hello'}, 'Hi There!'); 

createElement has 3 parameters:

  • first parameter… element that you want to create as a string
  • second parameter… its attributes (note that class is className!)
  • third parameter… its text content
  • it'll return a ReactElement object

Rendering

Changes in the virtual DOM are combined together and applied to the actual DOM in a way that minimizes DOM modification. Here's what rendering a ReactElement looks like.


ReactDOM.render(
    React.createElement('h1', {className: 'hello'}, 'Hi there!'), 
	document.body
);

render has two arguments

  • first parameter… a ReactElement
  • second parameter… an insertion point (where to add element as a child - can be a regular HTMLElement!)

Let's give it a try.

Another Way

Sooo… there was another unusual way of creating ReactElements. What was it?

Using JSX, we could…


ReactDOM.render(
	

Hi there!

, document.body );

Another Way Explained….

Why did that look so unusual?


ReactDOM.render(
	

Hi there!

, document.body );

Hey - there's markup in my JavaScript. It's an unquoted string! What!?

JSX

So, what's JSX again?

JSX is an extension to JavaScript syntax that allows XML-like syntax without

  • it's essentially a JavaScript preprocessor
    • it takes in JavaScript with JSX syntax
    • and compiles JSX to plain vanilla JavaScript
  • its usage is optional; you could just use plain JavaScript with react
  • also… browsers don't quite understand JSX (maybe yet?)

JSX Continued

So that means… this JSX


Hi there!

…is equivalent to this vanilla JavaScript


React.createElement('h1', {className: 'hello'}, 'Hi there!'), 

They both produce a ReactElement!

Components

You can bundle elements together into a single component. Here's an example.


const MyComponent = React.createClass({
  render: function() {
    return (
      <div> <h1>A Message</h1>{this.props.message}</div>
    );
  }
});


ReactDOM.render(
  <MyComponent message="Hi there!" />,
  document.body
);

Components and Props

To make a component, use React.createClass, which takes an object as a parameter or use ES6 classes.

  • the object must have a function called render (it'll generate elements)
  • note that a component variable must start with uppercase
  • once you have a component, you can pass it to render using JSX, with the variable name as the tag name
  • you can access attributes defined in JSX via this.props in your component

Say Hi or Bye!

Let's try to create a component that…

  • displays "hi" if the attribute, greet is true
  • otherwise, it'll display "bye" instead


For example… rendering…


<MyComponent greet={true} />,

Gives us


hi

Changing greet to false would give us bye instead.

Say Hi or Bye!


const MyComponent = React.createClass({
  render: function() {
    const msg = this.props.greet ? 'hi' : 'bye';
    return (
      <h1>{msg}</h1>
    )
  }
});

ReactDOM.render(
	<MyComponent greet={true} />, 
	document.body
);

Events

To add an event handler in JSX… add an inline attribute (wait, what!?). For example, click events would be represented by onClick:


const MyButton = React.createClass({
  onButtonClick: function(evt) {
    alert("Clicked!");
  },

  render: function() {
    return <div onClick={this.onButtonClick}>Press This Button</div>;
  }
});

ReactDOM.render(
  <MyButton />,
  document.body
)

Events Continued

Conventions and notes when handling events

  1. events are named by their camelCase version; see the whole list here
  2. the value of the event is the actual function, not a string or a function call
    • that means the value can be an arrow function
  3. you must define an event on an element, not on a component
    • (react doesn't know which element in the component you want the click event to be attached to!)
  4. it's a common convention to use a method defined in your component as the handler (but this gets tricky at times; we'll see why a little later!)

create-react-app

If the stuff we're starting to do is too complicated to debug with codepen, another easy way of working with react is using create-react-app

  • it's an npm module that generates a frontend only react application
  • it'll serve the application on port 3000 (no express is involved)


To use it:

  • install globally by: npm install -g create-react-app
  • create a new project by: create-react-app projectname
  • edit App.js and App.css in src
  • to run app: npm start
  • browser may open immediately and refresh on filesystem changes!

Development Options So Far

Now we have 3 options for developing super simple react apps

  1. create-react-app
  2. codepen
  3. jsbin


The above options are in-order of easiest to debug. However, note that:

  • if you want to integrate your create-react-app with express or some other framework
  • …or actually deploy your app
  • you'll have to set up some build infrastructure (for example, webpack + babble… which we may go over in the last class)

State

state is internal data controlled by the component (contrast this with props, which are controlled by whatever renders the component).

To initialize state properties when your component is created:

  1. define a getInitialState within your component definition…
  2. and return the desired state properties as property value pairs within an object. →



{ // within a component definition
  getInitialState: function() {
    return {
      prop: val,
    }
  }
}

Note that we'll take a look at setting initial state with ES6 classes a little later!

Handling State

Once your application has state, you can manipulate it by reading or setting it

  1. To read state….
    
    this.state.propertyName
    
  2. To set state:
    
    this.setState({stateName: stateValue});
    


When setting state, you must pass an object.

The object passed in specifies the new values of properties.

Using State… an Example

Let's define a couple of state variables, and put their values in a list when the component is rendered.


const MyComponent = React.createClass({
  render: function() {
    return (
      
  • {this.state.var1}
  • {this.state.var2}
) }, getInitialState: function() { return { var1: 'this is the first variable', var2: 'and a second variable' } } });

State With ES6 Classes

Things are slightly different if you're using ES6 Classes to define your components

  • instead of defining getInitialState, create a constructor that sets the state property to an object:
  • also, you'll have to watch out for how this works (createClass handles it for you, but ES6 classes do not)

From getInitialState to constructor

Instead of defining getInitialState

Create a constructor (optionally defining a props parameter), call super and assign an object to this.state


constructor() {
  super();
  this.state = {
    title: 'A React Component'
  };
}

Pass in props to your constructor if you want access to "attributes" (this.props isn't available within the constructor yet, so props can be accessed via parameter):


constructor(props) { ... }

createClass and this

Let's check out an event example with createClass. There's something a little suspicious about the code - what's weird about it?


const MyButton = React.createClass({
  getInitialState: function() {
    return { msg: 'Clicked!!!' };
  },

  handleClick: function(evt) { alert(this.state.msg); },

  render: function() {
    return <div onClick={this.handleClick}>Press Me!</div>;
  }
});
  • this should refer to the global object, but it doesn't; everything works just fine!
  • fortunately, when we use createClass, this is automatically set to the instance of the created ReactElement for us!

Events, State, ES6 Classes

Unfortunately, ES6 classes do not set this for us like createClass does. Consequently, the code below, which looks equivalent, will not work!


class MyButton extends React.Component {
  constructor() {
    super();
    this.state = { msg: 'Clicked!!!' };
  }

  // Y U NO WORK??????
  handleClick(evt) { alert(this.state.msg); }

  render() {
    return <div onClick={this.handleClick}>Press Me!</div>;
  }
}

Fixing this

Let's modify the following lines so that this is bound to the instance rather than the global object.


handleClick(evt) { alert(this.state.msg); }

render() {
  return <div onClick={this.handleClick}>Press Me!</div>;
}
  • use good 'ol bind!
    • <div onClick={this.handleClick.bind(this)}>
  • wrap handleClick in an arrow function to capture render's this:
    • <div onClick={() => {this.handleClick()}}>

A Challenge

Using events and state, create a component that:

  • renders a div with a class of number
  • the number starts at 0
  • every time you click on the div, the number increases


number click

A Solution

Start off with some boiler plate…


const MyComponent = React.createClass({
	// render, getInitialState and event handler
	// goes here...
});

ReactDOM.render(
	<MyComponent />, 
	document.body
);

A Solution (Continued)

Within your component definition:


  getInitialState: function() {
    return {
      count: 0,
    }
  }

  handleClick: function() {
    this.setState({count: this.state.count + 1});
  }

  render: function() {
    return (
      <div className="number" 
	  onClick={this.handleClick}>{this.state.count}</div>
    )
  }

What About These Components?

Now let's try adding 3 counters to our render…


ReactDOM.render(
  <div><Counter /><Counter /><Counter /></div>,
    document.body
);

Oh, Also, an ES6 Class Version


class Clicker extends React.Component {
  constructor() {
    super();
    this.state = {count: 0};
  }
  
  render() {
    return <div onClick={() => {this.handleClick()}}>

{this.state.count}

</div>; } handleClick() { this.setState({count: this.state.count + 1}); } }

More Complex Components

When you're actually writing real components

  • you'll often find that you'll be creating components that have nested components interacting with each other
  • … for example, a button and a text field that set some text in the containing element/component


The common pattern for this is to:

  • move state out of the child components
  • all of the state will go in the parent
  • the parent will orchestrate interactions
  • …this will be done by the parent setting props on its children
    • such as event handler functions
    • …and any other attributes

Communication Between Components

From the react docs: When you want to

  1. aggregate data from multiple children
  2. … or have two child components communicate with each other


Move the state upwards so that it lives in the parent component:

  • The parent can then pass the state back down to the children via props
  • so that the child components are always in sync with each other and with the parent.

Ceding Control to Parent

In this example, the child component doesn't define its own click handler, but instead receives one from its parent via props.


class Parent extends React.Component {
  // this handleClick will be used by the child!
  handleClick() {
    alert('cliiiiick me');
  }
  
  render() {
    // pass handlClick to child as a prop
    return <Child onClick={this.handleClick} />
  }
}

class Child extends React.Component {
  render() {
    return (<div onClick={this.props.onClick}>
      PRESS ME</div>);
  }
}

ReactDOM.render(<Parent />, document.body);

Counting Revisited

Let's try rewriting our counter so that the state is moved up to a parent element.

Start with our parent class…


class Parent extends React.Component {
}

Within that parent class, define a constructor to create initial state.


  constructor() {
    super();
    this.state = {
      count:0
    }
  }

Parent Class Continued: handleClick

Now let's define handleClick

  • handleClick sets the state like it did previously
  • but note that in order to do set state, it needs access to this!

  handleClick() {
    this.setState({count: this.state.count + 1});
  }

Parent Class Continued: render

render creates a child element and passes down a click handler and a count as props


  render() {
    // note that we have to bind this so that we have 
    // access to the  this of the instance that render is 
    // called on(and consequently this.state!
    const handler = this.handleClick.bind(this);
    return (
      <Child onClick={handler} val={this.state.count} />
    );
  }
  • use bind to bind context of handleClick to instance
  • alternatively, you can inline and arrow function as the value of onClick in JSX:
    • onClick={() => {this.handleClick()}}
    • note that if you're using an arrow function, call the click handler!

A Slightly More Efficient Version

If you're worried about making too many function objects, you can set this.handleClick to the bound version in the constructor rather than each time render is called


  constructor() {
    // add this line...
    this.handleClick = this.handleClick.bind(this);
}

  render() {
    // change back to using this.handleClick
    return (
      <Child onClick={this.handleClick} val={this.state.count} />
    );
  }

Child Component

Finally, for the child component, use props to define its click handler and its text content.


class Child extends React.Component {
  render() {
    return (
      <div onClick={this.props.onClick}>
        {this.props.val}
      </div>);
  }
}

Of course, call render:


ReactDOM.render(<Parent />, document.body);

Two Clickers!

Your event handler may require an argument. For example, it's a way to specify which child component was clicked without the child explicitly


// passing args to handle click, fixing arity, computer property names
class Parent extends React.Component {
}

Again, start with a constructor, but now we have state for two click boxes!


  constructor() {
    super();
    this.state = {
      box1:0,
      box2:0
    }
  }

Two Clickers Continued

Notice that our handleClick function will take an argument, the name of the box:


  handleClick(name) {
    // setState will be called based on this name!
    // this is using shorthand syntax for dynamic keys:
    this.setState({[name]: this.state[name] + 1});
  }
  

Lastly, when we define render, note that we're passing in different arguments to the call to handleClick.


  render() {
    return (
<Child onClick={() => {this.handleClick('box1')}} val={this.state.box1} /> <Child onClick={() => {this.handleClick('box2')}} val={this.state.box2} />
); }

Child

And, of course, our Child component code:


class Child extends React.Component {
  render() {
    return <div onClick={this.props.onClick}>{this.props.val}</div>;
  }
}


Rendering …


ReactDOM.render(, document.body);

Another Example

Let's change our original clicker so that:

  • the button is a nested component
  • the count is shown in the button
  • the count is shown outside of the button as well



Parent Count: 4
Child Count: 4

Parent: ClickCounter

This component is the parent; you can see that it:

  • controls what the child component will do on click by handing down the onClick function via props
  • and sets props on its child by using the values from its own state

class ClickCounter extends React.Component {
  constructor() {
    super();
    this.state = { count: 0 };
  }
  handleClick(arg) {
    alert(arg + ' ' + this.state.count);
    this.setState( { count: this.state.count + 1} );
  }
  render() {
    // wrap onclick callback in arrow function to handle this
    return 

Parent Count: {this.state.count}

<Clicker count={this.state.count} onClick={() => {this.handleClick("clicked!")}} />
; } }

Child: Clicker

Aaaand here's our child implementation


class Clicker extends React.Component {
  render() {
    return <div onClick={() => {this.props.onClick()}}>Child Count:{this.props.count}</div>;
  }
}

Of course… rendering:


ReactDOM.render(
  ,
  document.body
)

A Challenge

Let's try building this component…

  • it contains 3 boxes with numbers that increment on click
  • the last box clicked will be green, and its index will be displayed


last clicked

Input Elements and onChange

Form input elements have their own state (for example, when you type into the text field, that element's state is what you typed

  1. however, we may want one of React component's state to be the "single source of truth"
  2. so we can override an element's state with the React component's notion of state
  3. this allows us to use the component's state for working with form data rather than querying the form elements directly


In next few slides, the demo will will be using an input element with an onChange attribute.

  • onChange is an event that occurs whenever a change in an element is made
  • for example, typing in a text field

Text Input Demo

Let's create an h3 that has its content controlled from a text input field. The h3's text value will change as data is typed in the text field.

First, let's create our h3 as a component


class Message extends React.Component {
    render() {
        let value = this.props.value;
        if(value === '') {
            value = "Type Something PLZ ^^^";
        }
        return 

{value}

} }

onChange, State

Then we'll deal with onChange and state. In a component called InputDemo…


constructor() {
    super();
    this.state = {message: ''};
    this.handleChange = this.handleChange.bind(this);
}

handleChange(evt) {
    this.setState({message: evt.target.value}); 
}

render() {
    return (
        
Message: <input value={this.state.message} onChange={this.handleChange} type="text" /> <Message value={this.state.message} />
); }

Functional Components

If a component only deals with props, you can use a function instead of an ES6 style class or createClass


function MyComponent(props) {
    return 

{props.message}

}

Instead of:


class MyComponent extends React.Component {
    render() {
        return 

{this.props.message}

} }

componentDidMount

Implement the method componentDidMount to run code after a component is mounted to the actual DOM.

Why is this useful? The React Component docs specify that this is where background requests should be placed:

  • componentDidMount is meant for mutating the existing DOM
  • which likely what will happen after receiving a response