<info 340/>

Interactive React

Joel Ross
Winter 2020

Plan for Today

  • Stage 3 Requirements

  • Interactive React Demo

    • Review: Components & Props
    • Events
    • State
    • Lifecycle [if time]

Requirements:

  • Built in React (uses Components, prop, state)
  • Same level of functionality as Stage 2 (1 complete feature)
    • > we're aiming to give Stage 2 feedback over weekend 
  • Use an external component library (examples next week)
    • e.g., "bootstrap for react", "font-awesome for react"
  • Well-designed user experience (Stage 1-esque work)
    • Accessible, responsive, etc
    • Be sure to include feedback on user actions!
  • Deployed (to Firebase Hosting; next week)

Project: Stage 3

Objective: redo your Stage 2 in React!

Questions on anything?

React Review

class App extends Component {

  render() {
    return (
      <main>
        <MessageBanner message="I'm such a prop!" />
      </main>    
    );
  }
}

class MessageBanner extends Component {

  render() {
  
    let msg = this.props.message.toUpperCase();
 
    return <p className="banner">{msg}</p>;
  }
}

ReactDOM.render(<App />, 
                document.getElementById('root'));

define components

render method returns displayed DOM

JSX: shortcut for creating elements!

pass "props" as attributes

access passed-in props

include inline expressions

render element

React Events

We add user interaction in React the same way as the DOM and jQuery: by listening for events and executing callback functions when they occur. 

class MyButton extends Component {

  //method to call when clicked (name is arbitrary)
  doSomething(evt) {
     console.log('clicky clicky');
  }

  render() {
    //make a button with an `onClick` attribute!
    //this "registers" the listener and sets the callback
    return (
      <button onClick={this.doSomething}>
        Click me!
      </button>
    );
  }
}

special React prop

can only put listeners on HTML elements, not Components!

Retaining this

Remember that callbacks are not called on anything, so in order to access this you need to...

... bind() the this value to the function in the constructor

class MyButton extends Component {
  constructor(props) {
    super(props);

    this.doSomething = this.doSomething.bind(this);
  }
  
  doSomething(evt) {
     console.log(this.props.dataValue);
  }

  render() {
    return (
      <button onClick={this.doSomething}>
        Click me!
      </button>
    );
  }
}

Component constructor takes in props

Must call superclass's constructor too!

Create "bound" version
of function and reassign

Retaining this

Remember that callbacks are not called on anything, so in order to access this you need to...

... "wrap" the method to call in an arrow function

class MyButton extends Component {
  //no constructor work necessary!

  doSomething(evt) {
     console.log(this.props.dataValue);
  }

  render() {
    return (
      <button onClick={(evt) => this.doSomething(evt)}>
        Click me!
      </button>
    );
  }
}

Creates a function (with `this`) that can call the method (with `this`)

Retaining this

class MyButton extends Component {
  //no constructor work necessary!

  //assign the arrow function to a variable (and pass that)
  doSomething = (evt) => {
     console.log(this.props.dataValue);
  }

  render() {
    return (
      <button onClick={this.doSomething}>
        Click me!
      </button>
    );
  }
}

Remember that callbacks are not called on anything, so in order to access this you need to...

... use a public class field.

React Props

A Component's props are information received from the "outside" that describe that component.

props are a Component's configuration, its options if you may. They are received from above and immutable as far as the Component receiving them is concerned. A Component cannot change its props, but it is responsible for putting together the props of its child Components.

In addition to the props, React components can also track their internal state. This keeps track of information about the Component that may change due to user interaction.

React State

State is reserved only for interactivity, that is, data that changes over time

Some examples of state data:

  1. The sorted order of child components
  2. Timers or dynamic content
  3. Which model data are shown!

Initialize a Components state in its constructor, and then access it when view is rendered.

Using State

class CountingButton extends React.Component {

  constructor(props) {
     super(props)

     this.state = {count: 0}
  }

  handleClick = () => {
     console.log("You clicked me!");

  }

  render() {
    return (
       <button onClick={this.handleClick}>
         Clicked {this.state.count} times
       </button>
    );
  }
}

Assign value to `state` instance variable. Must be an object.

Access state. If it's not used in render(), it shouldn't be state!

Changing State

React state needs to be changed asynchronously (for speed). Modify this.state object using the component's setState() method. Calling this method will update the state (when ready) and automatically re-render the Component (via render()).

class CountingButton extends React.Component {
  constructor(props) {
     super(props)
     this.state = {count: 0}
  }

  handleClick = () => {
     let stateChanges = {count: 1}
     this.setState(stateChanges); //change the state
  }

  render() {
    return (
       <button onClick={this.handleClick} >
         Clicked {this.state.count} times
       </button>
    );
  }
}

Use method to change state and re-render (will call `render()`)

Parameter object only needs values that have changed (will "merge")

setState() is Asynchronous

The "updated" state value will only be available when the component is re-rendered (so from the render() function).

class CountingButton extends React.Component {
  constructor(props) {
     super(props)
     this.state = {count: 0}
  }

  handleClick = () => {
     let stateChanges = {count: 1}
     this.setState(stateChanges); //change the state
     console.log(this.state.count) // still 0! hasn't changed yet
  }

  render() {
    return (
       <button onClick={this.handleClick} >
         Clicked {this.state.count} times
       </button>
    );
  }
}

setState() is Asynchronous

Pass a callback function to setState() if you want to modify based on current state.

class CountingButton extends React.Component {
  constructor(props) {
     super(props)
     this.state = {count: 0}
  }

  handleClick = () => {
     this.setState((currState, currProps) => {
        let stateChanges = {count: currState.count + 1}
        return stateChanges; //return the value to "set"
     });
  }

  render() {
    return (
       <button onClick={this.handleClick} >
         Clicked {this.state.count} times
       </button>
    );
  }
}

Lifting Up State

If multiple components rely on the same data (variable), you should "lift up" that state to a shared parent, who can pass the information back down as props.

ChildA

ChildB

Parent

Has Data

Needs Data

prop = state;

prop = state;

Has Data (prop)

Has Data (prop)

Passing Callbacks

Often when an event occurs, you want the parent component to do something. The parent passes in a callback function as a prop for the child to execute!
Use arrow functions to bind the this.

class App extends Component {
  handleClick = (thing) => {
    console.log("You clicked on", thing);
    //can call `this.setState()` here!
  }

  render() {
    return <MyButton callback={this.handleClick} text={"Hello"} />
  }
}

class MyButton extends Component {
  handleClick = (event) => {
    //call given callback, passing in given text
    this.props.callback(this.props.text);    
  }

  render() {
    return <button onClick={this.handleClick}>{this.props.text}</button>
  }
}

React Inputs

To access the value in an <input>, save that value in the state (and update it onChange). This is called a controlled form.

class MyInput extends React.Component {
  constructor(props) {
     super(props)
     this.state = {value: ''}
  }

  handleChange = (event) => {
     let newValue = event.target.value
     this.setState({value:newValue});
  }

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

use DOM event to refer to the element

Making React Interactive

  1. Start with a "static" (non-interactive) version, with appropriate Components

  2. Identify variables that need to be state

  3. Put state in the "lowest" common ancestor for Components that need it

  4. Pass state information to child Components as props

  5. Pass callback functions as props to child Components so they can modify the parent state.

React Hooks are a recent (v16.8, Oct 2018) alternative to defining classes and life-cycle methods. We will not be using them in this class, as they're less clear (more "black magic"). Stick with classes and class methods.

React Hooks

function CountingButton() {
  //Declare the state variable `count` and a setter for it
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Clicked {count} times
      </button>
    </div>
  );
}

Loading Data

To use fetch() in React (which is compiled via webpack & Node), you will need to install and import the polyfill.

# Install polyfill (on command line)
npm install --save whatwg-fetch
//import polyfill (in JavaScript)
import 'whatwg-fetch'; //loading "globally"

Component Lifecycle

The Component class has methods that are called at each stage of the component's "Lifecycle". Override these methods to perform an action at that point.

class MyComponent extends Component {
  constructor(props){
    super(props)
    //called at Compnent instantiation (not on screen yet)
    //initialize state here
  }

  componentDidMount() {
    //called when put on screen (visible, after render())
    //do AJAX requests here!
  }
  
  componentDidUpdate(revProps, prevState, snapshot) {
    //called when changing. Less common
  }

  componentWillUnmount() {
    //called when removed from screen
    //do asynchronous cleanup here
  }
}

AJAX & React

Send an AJAX request from the componentDidMount() method; the .then() callback should update the state when new data arrives!

class MyComponent extends Componet {
  constructor(props){
    super(props);
    this.state = {data: []}; //initialize as empty!
  }

  componentDidMount() {    
    fetch(dataUri) //send AJAX request
      .then((res) => res.json())
      .then((data) => {
        let processedData = data.filter().map() //process the data
        this.setState({data: processedData}) //change the state!
      })
  }

  render() {
    //convert data into DOM
    //Note that this works even before data is loaded (when empty!)
    let dataItems = this.state.data.map((item) => {
      return <li>{item.value}</li>; //get item from data
    })
    return (<ul>{dataItems}</ul>); //render the list
  }
}

Action Items!

  • Finish Problem Set 08

    • And any others!

  • Project: Start on this!

    • Get set up; make a few components, etc.

    • Let us know if you're changing groups

  • Reading: Chapter 16-17

 

Next time: React lifecycle, libraries, etc

info340wi20-react-interactive

By Joel Ross

info340wi20-react-interactive

  • 502