You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

28 KiB

Intro to React

Lesson Objectives

  1. Define what React Is
  2. Describe how react differs from most other front-end frameworks
  3. Create a basic component
  4. Assign JSX to a variable
  5. Embed an expression into JSX
  6. Create multi-line JSX variables
  7. Create a custom component
  8. Customize an instance of a component with properties
  9. Handle user events
  10. Conditionally render HTML
  11. Create multiple elements from an array
  12. Pass properties to children
  13. Customize a component by using the tag's content
  14. Change a component's state through interaction
  15. Create references to the tags in a component
  16. Update child component properties with state values
  17. Call parent component methods
  18. Make AJAX requests within React
  19. Handle component lifecycle events
  20. Style a component

Define what React Is

React is a front-end framework which is designed to make building a Single Page JavaScript Application easier

Describe how react differs from most other front-end frameworks

  • Most other front-end frameworks attempt to separate data from presentation in some kind of an MVC structure
    • For example
      • One set files for HTML (View)
      • One set of files for your Controllers
      • One set of files for your Models (data)
    • Great for teams where developers specialize on one technology (CSS or JavaScript)
  • React puts all the Views, Controllers, and Models for one small section of the application together into one file
    • Great for teams where one developer handles just a small section of the application and that's it

Create a basic component

HTML

Let's set up the HTML

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.2/umd/react.production.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.2/umd/react-dom.production.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
        <script type="text/babel" src="js/app.js"></script>
    </head>
    <body>
        <main></main>
    </body>
</html>

Let's talk about all those script tags

  • react.js
    • The core React functionality
    • React can be used for applications that aren't in the browser
      • so it's separated out from code that deals with the browser
  • react-dom.js
    • This allows the core React js to work with the browser's DOM
  • browser.min.js (babel-core)
    • Babel is a transpiler that will translate ES6 code to ES5
    • it also handles turning JSX into regular JS

JS

  • Let's create a component which is just an H1 tag
  • We'll insert it into the main tag of our html
ReactDOM.render(
  <h1 className="foo">Hello, world!</h1>,
  document.querySelector('main')
);
  • This code will find the main tag and render an h1 tag inside of it
    • NOTE we can't set class with the class attribute anymore
      • We have to use className so it doesn't get confused
      • All other attributes should work normally
  • React mixes HTML with JavaScript (JSX)
    • Notice that the HTML does not have quotes or backticks around it use python -m SimpleHTTPServer to start a basic server

Assign JSX to a variable

JSX just renders into regular JavaScript, so we can assign it to variable:

const myJSX = <h1>Hello, World!</h1>

ReactDOM.render(
  myJSX,
  document.querySelector('main')
);

use python -m SimpleHTTPServer to start a basic server for our code

Embed an expression into JSX

We can create variables and insert them into our JSX:

const user = "Matt";
const myJSX = <h1>Hello, {user}!</h1>

ReactDOM.render(
  myJSX,
  document.querySelector('main')
);

We can even execute functions inside the {}

const formatUser = function(user){
    return user + "!!"
}
const user = "Matt";
const myJSX = <h1>Hello, {formatUser(user)}</h1>

ReactDOM.render(
  myJSX,
  document.querySelector('main')
);

Create multi-line JSX variables

JSX can be split onto multiple lines:

const formatUser = function(user){
    return user + "!!"
}
const user = "Matt";
const myJSX =
    <section>
        <h1>Hello, {formatUser(user)}</h1>
        Welcome to the app
    </section>

ReactDOM.render(
  myJSX,
  document.querySelector('main')
);

Create a custom component

With JSX, we can create our own tags!!

class Heading extends React.Component {
    render() {
        return <h1>Hello, World!</h1>;
    }
}

ReactDOM.render(
    <Heading></Heading>,
    document.querySelector('main')
);
  • This mimics what is going on with Web Components
  • The idea is that we can write our own custom tags, which are much more semantic, and they will render as regular HTML

Now that our Heading code has been encapsulated as component, it is easy to reuse:

class Heading extends React.Component {
    render() {
        return <h1>Hello, World!</h1>;
    }
}

ReactDOM.render(
    <section>
        <Heading></Heading>
        <Heading></Heading>
    </section>,
    document.querySelector('main')
);

NOTE: There must be one single top level element for JSX, you can't have two siblings be at the top of that component

Customize an instance of a component with properties

We can customize each instance of a component with properties

class Heading extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}!</h1>;
    }
}

ReactDOM.render(
    <section>
        <Heading name="Matt"></Heading>
        <Heading name="Helen"></Heading>
    </section>,
    document.querySelector('main')
);

Handle user events

We can handle user events (clicks, hover, etc) by defining event handlers and registering them right on the element:

const sayHello = function() {
    alert("oh hai");
}

class Heading extends React.Component {
    render() {
        return <h1 onClick={sayHello}>Hello, {this.props.name}!</h1>;
    }
}

ReactDOM.render(
    <section>
        <Heading name="Matt"></Heading>
        <Heading name="Helen"></Heading>
    </section>,
    document.querySelector('main')
);

It's a little more component-y if we make these functions part of the component, though:

class Heading extends React.Component {
    sayHello() {
        alert("oh hai");
    }
    render() {
        return <h1 onClick={this.sayHello}>Hello, {this.props.name}!</h1>;
    }
}

ReactDOM.render(
    <section>
        <Heading name="Matt"></Heading>
        <Heading name="Helen"></Heading>
    </section>,
    document.querySelector('main')
);

What if we want to get at properties of the component?

class Heading extends React.Component {
    sayHello() {
        console.log(this); //undefined?!?
    }
    render() {
        return <h1 onClick={this.sayHello}>Hello, {this.props.name}!</h1>;
    }
}

ReactDOM.render(
    <section>
        <Heading name="Matt"></Heading>
        <Heading name="Helen"></Heading>
    </section>,
    document.querySelector('main')
);

Normally, these event handlers don't have this bound correctly. We have to manually do this:

class Heading extends React.Component {
    constructor(props) {
        super(props);
        this.sayHello = this.sayHello.bind(this);
    }
    sayHello() {
        console.log(this.props);
        alert("My name is " + this.props.name);
    }
    render() {
        return <h1 onClick={this.sayHello}>Hello, {this.props.name}!</h1>;
    }
}

ReactDOM.render(
    <section>
        <Heading name="Matt"></Heading>
        <Heading name="Helen"></Heading>
    </section>,
    document.querySelector('main')
);

Conditionally render HTML

Depending on some condition, you may want to render different HTML:

class Heading extends React.Component {
    render() {
        if(this.props.userType === 'admin'){
            return <h1>Hello!  You are an admin</h1>;
        } else {
            return <h1>Hello!</h1>;
        }
    }
}

ReactDOM.render(
    <section>
        <Heading userType="admin"></Heading>
        <Heading></Heading>
    </section>,
    document.querySelector('main')
);

We've got some redundant code here, though. We can use an inline ternary operator if we'd like:

class Heading extends React.Component {
    render() {
        return <h1>
            Hello! {(this.props.userType==='admin')?'You are an admin':''}
        </h1>;
    }
}

ReactDOM.render(
    <section>
        <Heading userType="admin"></Heading>
        <Heading></Heading>
    </section>,
    document.querySelector('main')
);

You can also render JSX, conditionally:

class Heading extends React.Component {
    render() {
        return <div>
            <h1>Hello!</h1>
            {
                (this.props.userType==='admin')?
                (
                    <em>You are an admin</em>
                ):
                null
            }
        </div>;
    }
}

ReactDOM.render(
    <section>
        <Heading userType="admin"></Heading>
        <Heading></Heading>
    </section>,
    document.querySelector('main')
);

Create multiple elements from an array

If we have an array of values, we can generate JSX elements from it:

const nums = [1,5,8,10];
class List extends React.Component {
    render() {
        return <ul>
            {nums.map((num) => (
                <li>{num}</li>
            ))}
        </ul>;
    }
}

ReactDOM.render(
    <List></List>,
    document.querySelector('main')
);

Each element that's being generated, must have a unique key to help React identify which elements have been changed, added, or removed:

const nums = [1,5,8,10];
class List extends React.Component {
    render() {
        return <ul>
            {nums.map((num, index) => (
                <li key={index}>{num}</li>
            ))}
        </ul>;
    }
}

ReactDOM.render(
    <List></List>,
    document.querySelector('main')
);

Pass properties to children

You can have components within components:

const nums = [1,5,8,10];

class ListItem extends React.Component {
    render(){
        return <li>This is a list item</li>
    }
}

class List extends React.Component {
    render() {
        return <ul>
            {nums.map((num, index) => (
                <ListItem key={index}></ListItem>
            ))}
        </ul>;
    }
}

ReactDOM.render(
    <List></List>,
    document.querySelector('main')
);

To have the number show up, we simply create a property on the ListItem component

const nums = [1,5,8,10];

class ListItem extends React.Component {
    render(){
        return <li>This is a list item: {this.props.number}</li>
    }
}

class List extends React.Component {
    render() {
        return <ul>
            {nums.map((num, index) => (
                <ListItem number={num} key={index}></ListItem>
            ))}
        </ul>;
    }
}

ReactDOM.render(
    <List></List>,
    document.querySelector('main')
);

Customize a component by using the tag's content

If using the component's tag's attribute doesn't seem semantic, you can use the content of the tag.

class Person extends React.Component {
    render() {
        return <div>
            The name of the person is {this.props.children}
        </div>;
    }
}

ReactDOM.render(
    <section>
        <Person>Bob</Person>
        <Person>Sally</Person>
    </section>,
    document.querySelector('main')
);

You can further customize the content with tags and additional HTML/JSX:

class Person extends React.Component {
    render() {
        return <div>
            The name of the person is {this.props.children}
        </div>;
    }
}

ReactDOM.render(
    <section>
        <Person>
            <em>Bob</em>.  He is awesome.
        </Person>
        <Person>Sally.  All hail Sally</Person>
    </section>,
    document.querySelector('main')
);

Change a component's state through interaction

We want to interact with a component, and have it visually change. To do this, we use the component's "state"

  1. Create a basic form:

    class Auth extends React.Component {
        render(){
            return <form>
                <input type="text" placeholder="Your Name" />
                <input type="submit" value="Log In" />
            </form>;
        }
    }
    
    ReactDOM.render(
        <Auth></Auth>,
        document.querySelector('main')
    );
    
  2. In the component's constructor function, initialize the state object:

    constructor(props){
        super(props)
        this.state = { username: "Not logged In" }
    }
    
  3. Display the component's state's username property:

    render(){
        return <form>
            {this.state.username}<br/>
            <input type="text" placeholder="Your Name" />
            <input type="submit" value="Log In" />
        </form>;
    }
    
  4. Create an event handler for changing the user name field:

    handleChangeName(event){
        console.log('changed user name');
    }
    
  5. Set up handleChangeName so that it can access instance properties:

    constructor(props){
        super(props)
        this.handleChangeName = this.handleChangeName.bind(this);        
        this.state = { username: "Not logged In" }
    }
    
  6. When the input field changes, call handleChangeName:

    render(){
        return <form>
            {this.state.username}<br/>
            <input
                onChange={this.handleChangeName}
                type="text"
                placeholder="Your Name" />
            <input type="submit" value="Log In" />
        </form>;
    }
    
  7. Have handleChangeName update the component's state:

    handleChangeName(event){
        this.setState({username:event.target.value});
    }
    

Create references to the tags in a component

The previous section for updating state can often be overly complex when dealing with multiple form elements, especially when all you need is a reference to the form elements' values when submitting the form

  1. Change input to have a reference, instead of an event handler:

    <input
        ref="username"
        type="text"
        placeholder="Your Name" />
    
  2. Replace handleChangeName with a form submission handler that references the text input

    <form onSubmit={this.handleFormSubmit}>
        {this.state.username}
        <input 
                ref="username"
                type="text"
                placeholder="Your Name"/>
        <input type="submit" value="Log In"/>
    </form>
    
    handleFormSubmit(event){
        event.preventDefault();
        this.setState({username:this.refs.username.value});
    }
    
  3. Update constructor with correct event handler

    constructor(props){
        super(props)
        this.handleFormSubmit = this.handleFormSubmit.bind(this);
        this.state = { username: "Not logged In" }
    }
    

Update child component properties with state values

We can pass state values in as properties of child components

  1. Create a Greeting component

    class Greeting extends React.Component {
        render(){
            return <h1>Welcome {this.props.name}</h1>
        }
    }
    
  2. Use Greeting component in Auth component

    render(){
        return <form onSubmit={this.handleFormSubmit}>
            <Greeting name={this.state.username}></Greeting>
            <input
                ref="username"
                type="text"
                placeholder="Your Name" />
            <input type="submit" value="Log In" />
        </form>;
    }
    

Call parent component methods

Sometimes you want to pass information to a parent component

  1. Delete Greeting component class

  2. Delete Greeting component instance within Auth JSX

  3. You will no longer need state within Auth component

    constructor(props){
        super(props);
        this.handleFormSubmit = this.handleFormSubmit.bind(this);
    }
    handleFormSubmit(event){
        event.preventDefault();
    }
    
  4. Create a Heading around the Auth Section

    class Heading extends React.Component {
        render(){
            return <header>
                <h1>Welcome</h1>
                <Auth></Auth>
            </header>
        }
    }
    
    ReactDOM.render(
        <Heading></Heading>,
        document.querySelector('main')
    );
    
  5. Create constructor for Heading with state properties for username

    class Heading extends React.Component {
        constructor(props){
            super(props);
            this.state = { username: null }
        }
        render(){
            return <header>
                <h1>Welcome {this.state.username}</h1>
                <Auth></Auth>
            </header>
        }
    }
    
  6. Create a method for Heading that will update the state

    updateUsername(newName){
        this.setState({username: newName});
    }    
    
  7. Bind updateUsername in constructor

    constructor(props){
        super(props);
        this.state = { username: null }
        this.updateUsername = this.updateUsername.bind(this);
    }
    
  8. Pass this function in as a property to Auth

    render(){
        return <header>
            <h1>Welcome {this.state.username}</h1>
            <Auth onLogin={this.updateUsername}></Auth>
        </header>
    }    
    
  9. When the Auth component handles the form submission, call that property/function

    handleFormSubmit(event){
        event.preventDefault();
        this.props.onLogin(this.refs.username.value);
    }    
    

Make AJAX requests within React

React doesn't have any built-in functionality to handle AJAX. Either use jQuery, fetch, or some other library to handle this

  1. Set up a form that logs value of input on submit:

    class OMDBQueryForm extends React.Component {
        constructor(props){
            super(props);
            this.queryOMDB = this.queryOMDB.bind(this);
        }
        queryOMDB(event){
            event.preventDefault();
            console.log(this.refs.title.value);
        }
        render(){
            return <form onSubmit={this.queryOMDB}>
                <input
                    ref="title"
                    type="text"
                    placeholder="Movie Title" />
                <input type="submit" value="Find Movie Info" />
            </form>
        }
    }
    
    ReactDOM.render(
        <OMDBQueryForm></OMDBQueryForm>,
        document.querySelector('main')
    );    
    
  2. When submitting, make request to OMBD

    queryOMDB(event){
        event.preventDefault();
        fetch('http://www.omdbapi.com/?apikey=53aa2cd6&t=' + this.refs.title.value).then(function(response){
            response.json().then(function(data){
                console.log(data);
            });
        });
    }    
    
  3. Create a component to handle movie data

    class MovieInfo extends React.Component {
        render(){
            return <ul>
                <li>Title:</li>
                <li>Director:</li>
                <li>Actors:</li>
                <li>Year:</li>
                <li>Rated:</li>
            </ul>
        }
    }    
    
  4. Add it to OMDBQueryForm

    render(){
        return <form onSubmit={this.queryOMDB}>
            <input
                ref="title"
                type="text"
                placeholder="Movie Title" />
            <input type="submit" value="Find Movie Info" />
            <MovieInfo></MovieInfo>
        </form>
    }
    
  5. Set up state for found movie

    constructor(props){
        super(props);
        this.queryOMDB = this.queryOMDB.bind(this);
        this.state = { foundMovie: null }
    }    
    
  6. this inside the fetch callback is not the instantiated component. Let's use arrow functions to fix this

    queryOMDB(event){
        event.preventDefault();
        fetch('http://www.omdbapi.com/?t=' + this.refs.title.value).then((response) => {
            response.json().then((data) => {
                console.log(this);
            });
        });
    }
    
  7. Now we can set the state of the form appropriately

    queryOMDB(event){
        event.preventDefault();
        fetch('http://www.omdbapi.com/?t=' + this.refs.title.value).then((response) => {
            response.json().then((data) => {
                this.setState({foundMovie: data});
            });
        });
    }
    
  8. And pass on the variable to the MovieInfo component

    <MovieInfo data={this.state.foundMovie}></MovieInfo>
    
  9. Within the MovieInfo component, we can display the info appropriately

    render(){
        return <ul>
            <li>Title: {this.props.data.Title}</li>
            <li>Director: {this.props.data.Director}</li>
            <li>Actors: {this.props.data.Actors}</li>
            <li>Year: {this.props.data.Year}</li>
            <li>Rated: {this.props.data.Rated}</li>
        </ul>
    }
    
  10. We'll get an error on page load because this.props.data isn't defined initially. Let's display the component conditionally

    render(){
        return <form onSubmit={this.queryOMDB}>
            <input
                ref="title"
                type="text"
                placeholder="Movie Title" />
            <input type="submit" value="Find Movie Info" />
            {
                (this.state.foundMovie !== null)?
                    <MovieInfo data={this.state.foundMovie}></MovieInfo>
                :
                    null
            }
        </form>
    }
    

Handle component lifecycle events

A component has specific moments in its life:

  1. It is created
  2. It is updated
  3. It is destroyed

We can perform actions during each of these moments. Let's create an app that just counts down starting at 100.

  1. Create the setup code:

    class Counter extends React.Component {
        render(){
            return <section>
                The value:
            </section>
        }
    }
    
    ReactDOM.render(
        <Counter></Counter>,
        document.querySelector('main')
    );    
    
  2. Create a state property for the count

    class Counter extends React.Component {
        constructor(props){
            super(props)
            this.state = { count: 100 }
        }
        render(){
            return <section>
                The value: {this.state.count}
            </section>
        }
    }
    
  3. Once the component has loaded, we want to call a function every 1000 milliseconds:

    componentDidMount(){
        window.setInterval(
            function(){
                console.log('tick');
            },
            1000
        )
    }
    
  4. The problem is that this is not correctly bound

    componentDidMount(){
        window.setInterval(
            function(){
                console.log(this);
            },
            1000
        )
    }    
    
  5. Let's use an arrow function

    componentDidMount(){
        window.setInterval(
            () => {
                console.log(this);
            },
            1000
        )
    }    
    
  6. Now we can decrement the state property

    componentDidMount(){
        window.setInterval(
            () => {
                this.setState({ count: (this.state.count - 1) });
            },
            1000
        )
    }
    
  7. Let's create a container that will bring the counter into existence:

    class Container extends React.Component {
        render(){
            return <Counter></Counter>
        }
    }
    
    ReactDOM.render(
        <Container></Container>,
        document.querySelector('main')
    );    
    
  8. Now were going to create a button that will create the counter and create a state property which will determine whether or not to show the counter

    class Container extends React.Component {
        constructor(props){
            super(props)
            this.state = { buttonExists: false }
        }
        render(){
            return <section>
                <button>Toggle Counter</button>
                {
                    (this.state.buttonExists)?
                        <Counter></Counter>
                    :
                        null
                }
            </section>
        }
    }    
    
  9. When the button is pressed, it toggles the counter's existence

    class Container extends React.Component {
        constructor(props){
            super(props)
            this.state = { buttonExists: false }
            this.toggleButton = this.toggleButton.bind(this); //bind function
        }
        toggleButton(){ //create function
            this.setState({buttonExists: !this.state.buttonExists});
        }
        render(){ //add onClick listener
            return <section>
                <button onClick={this.toggleButton}>Toggle Counter</button>
                {
                    (this.state.buttonExists)?
                        <Counter></Counter>
                    :
                        null
                }
            </section>
        }
    }    
    
  10. You'll notice we get a warning because the interval still exists, even though the component does not. Let's create a pointer so that the interval can be cleared

    componentDidMount(){
        this.timerID = window.setInterval(
            () => {
                this.setState({ count: (this.state.count - 1) });
            },
            1000
        )
    }
    
  11. And now clear the interval when the component is destroyed

    componentWillUnmount(){
        clearInterval(this.timerID);
    }    
    
  12. Lastly, if some prop/state property is updated on the component, we can perform an action.

    componentDidUpdate(previousProps, previousState){
        console.log('This component has changed.  The previous count was: ' + previousState.count);
    }    
    

Style a component

You can use CSS as normal, but if you want to stick with React's component based architecture, you can use JavaScript to style a component without leaving the file

let basicStyles = {
    background:'red',
    borderWidth:'2px',
    borderStyle:'solid',
    borderColor:'black'
};

class AwesomeSection extends React.Component {
    render(){
        return <section style={basicStyles}>
            sure is awesome
        </section>
    }
}

ReactDOM.render(
    <AwesomeSection></AwesomeSection>,
    document.querySelector('main')
);

You can extend your styles with Object.assign()

let basicStyles = {
    background:'red',
    borderWidth:'2px',
    borderStyle:'solid',
    borderColor:'black'
};

let awesomeStyles = Object.assign({}, basicStyles, { padding: '10px'})

class AwesomeSection extends React.Component {
    render(){
        return <section style={awesomeStyles}>
            sure is awesome
        </section>
    }
}

You could also use composition to mimic inheritance:

let basicStyles = {
    background:'red',
    borderWidth:'2px',
    borderStyle:'solid',
    borderColor:'black'
};

class BasicSection extends React.Component {
    render(){
        return <section style={Object.assign({}, basicStyles, this.props.style)}>
            {this.props.children}
        </section>
    }
}

let awesomeStyles = { padding: '10px'};

class AwesomeSection extends React.Component {
    render(){
        return <BasicSection style={awesomeStyles}>
            sure is awesome
        </BasicSection>
    }
}

ReactDOM.render(
    <AwesomeSection></AwesomeSection>,
    document.querySelector('main')
);