27 KiB
Intro to React
Lesson Objectives
- Define what React Is
- Describe how react differs from most other front-end frameworks
- Create a basic component
- Assign JSX to a variable
- Embed an expression into JSX
- Create multi-line JSX variables
- Create a custom component
- Customize an instance of a component with properties
- Handle user events
- Conditionally render HTML
- Create multiple elements from an array
- Pass properties to children
- Customize a component by using the tag's content
- Change a component's state through interaction
- Create references to the tags in a component
- Update child component properties with state values
- Call parent component methods
- Make AJAX requests within React
- Handle component lifecycle events
- 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)
- For example
- 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
- Let's create a component which is just an H1 tag
- We'll insert it into the
maintag of our html
ReactDOM.render(
<h1 className="foo">Hello, world!</h1>,
document.querySelector('main')
);
- This code will find the main tag and render an
h1tag inside of it- NOTE we can't set class with the
classattribute anymore- We have to use className so it doesn't get confused
- All other attributes should work normally
- NOTE we can't set class with the
- React mixes HTML with JavaScript (JSX)
- Notice that the HTML does not have quotes or backticks around it
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/15.3.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.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
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')
);
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); //null?!?
}
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"
-
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') ); -
In the component's constructor function, initialize the state object:
constructor(props){ super(props) this.state = { username: "Not logged In" } } -
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>; } -
Create an event handler for changing the user name field:
handleChangeName(event){ console.log('changed user name'); } -
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" } } -
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>; } -
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
-
Change input to have a reference, instead of an event handler:
<input ref="username" type="text" placeholder="Your Name" /> -
Replace handleChangeName with a form submission handler that references the text input
handleFormSubmit(event){ event.preventDefault(); this.setState({username:this.refs.username.value}); } -
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
-
Create a Greeting component
class Greeting extends React.Component { render(){ return <h1>Welcome {this.props.name}</h1> } } -
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
-
Delete Greeting component class
-
Delete Greeting component instance within Auth JSX
-
You will no longer need state within Auth component
constructor(props){ super(props); this.handleFormSubmit = this.handleFormSubmit.bind(this); } handleFormSubmit(event){ event.preventDefault(); } -
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') ); -
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> } } -
Create a method for Heading that will update the state
updateUsername(newName){ this.setState({username: newName}); } -
Bind updateUsername in constructor
constructor(props){ super(props); this.state = { username: null } this.updateUsername = this.updateUsername.bind(this); } -
Pass this function in as a property to Auth
render(){ return <header> <h1>Welcome {this.state.username}</h1> <Auth onLogin={this.updateUsername}></Auth> </header> } -
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
-
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') ); -
When submitting, make request to OMBD
queryOMDB(event){ event.preventDefault(); fetch('http://www.omdbapi.com/?t=' + this.refs.title.value).then(function(response){ response.json().then(function(data){ console.log(data); }); }); } -
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> } } -
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> } -
Set up state for found movie
constructor(props){ super(props); this.queryOMDB = this.queryOMDB.bind(this); this.state = { foundMovie: null } } -
thisinside the fetch callback is not the instantiated component. Let's use arrow functions to fix thisqueryOMDB(event){ event.preventDefault(); fetch('http://www.omdbapi.com/?t=' + this.refs.title.value).then((response) => { response.json().then((data) => { console.log(this); }); }); } -
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}); }); }); } -
And pass on the variable to the MovieInfo component
<MovieInfo data={this.state.foundMovie}></MovieInfo> -
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> } -
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:
- It is created
- It is updated
- It is destroyed
We can perform actions during each of these moments. Let's create an app that just counts down starting at 100.
-
Create the setup code:
class Counter extends React.Component { render(){ return <section> The value: </section> } } ReactDOM.render( <Counter></Counter>, document.querySelector('main') ); -
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> } } -
Once the component has loaded, we want to call a function every 1000 milliseconds:
componentDidMount(){ window.setInterval( function(){ console.log('tick'); }, 1000 ) } -
The problem is that
thisis not correctly boundcomponentDidMount(){ window.setInterval( function(){ console.log(this); }, 1000 ) } -
Let's use an arrow function
componentDidMount(){ window.setInterval( () => { console.log(this); }, 1000 ) } -
Now we can decrement the state property
componentDidMount(){ window.setInterval( () => { this.setState({ count: (this.state.count - 1) }); }, 1000 ) } -
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') ); -
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> } } -
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> } } -
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 ) } -
And now clear the interval when the component is destroyed
componentWillUnmount(){ clearInterval(this.timerID); } -
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')
);