React Fundamentals III
Kay Ashaolu
Lets go back to our clock example
- Remember how we executed the tick() function every second
- We used the setInterval function provided by the browser JavaScript engine to execute the function every second
- We forced React to render the element over and over again using the ReactDOM.render function
Tick Function Example
import React from "react";
import ReactDOM from "react-dom";
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);
This isn't terribly efficient
- If we had to tell each component when to render, we would run into some problems
- What if one component's data relates to another components data?
- What if we want to have more than one Clock on the page?
- This is why we want to start to encapsulate this clock so that it can be used throughout our application
- Encapsulation method: create a Clock component i.e. a JavaScript class
Clock React Component
import React from "react";
import ReactDOM from "react-dom";
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
function tick() {
ReactDOM.render(
<div>
<Clock date={new Date()} />
<Clock date={new Date()} />
</div>,
document.getElementById('root')
);
}
setInterval(tick, 1000);
Note what is happening
- We moved the render logic of the Clock to its own JavaScript class
- By doing this, we were able to create two clock instances in the tick() function
- We are still using ReactDOM.render to forcibly rerender both Clock instances
Note what is happening
- Even though we do have two "Clock components", they are synchronized
- This is because each Clock is sourcing its date/time info to {this.props.date.toLocaleTimeString()}
- That will be the same for every Clock instance
What if you want more
- What if you wanted to have a clock for a different time zone?
- Then each clock instance would have to have some unique data about itself (e.g. it's current time, its time zone)
- What we would want is that each clock has its own state
Clock Example
import React from "react";
import ReactDOM from "react-dom";
class Clock extends React.Component {
constructor(props) {
super(props);
let clock_date = new Date()
clock_date.setHours(clock_date.getHours() + parseInt(this.props.offset));
this.state = {date: clock_date};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()} with a {this.props.offset} offset</h2>
</div>
);
}
}
ReactDOM.render(
( <div>
<Clock offset ="+3"/>
<Clock offset="-3" />
</div>
),
document.getElementById('root')
);
Introducing state
- Notice that we have more going on in the constructor
- We are passing a property "offset" that is being used to adjust the current time
- We are saving the current time in each Clock's state: now we have a mutable value tied to each clock
Notice Clock isn't ticking
- We don't have any code that's actually changing the state with key "date"
- Remember that we were using the setInterval function to change the date every second
- What we now want to do is set these intervals to be created by the Clock itself
- Next, we want to utilize React's feature of re-rendering a component when the component's state changes
Lifecycle Methods
- We want to be able to be efficient with the computing resources we use
- If we are done with a resource, freeing up that resource becomes very important, especially if you have several components in the same page
- There are special functions that are executed by React during special times
Lifecycle Methods
- We want to set up a setInterval timer whenever the Clock is fully created and rendered to the DOM for the first time. This is called mounting
- We also want to clear said timer whenever a specfic Clock component is removed from the DOM for any reason. This is called "unmounting"
- componentDidMount() does the first, componentWillUnmount() does the second
Lifecycle Example
import React from "react";
import ReactDOM from "react-dom";
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: this.getCurrentDate()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
getCurrentDate() {
let new_date = new Date();
new_date.setHours(new_date.getHours() + parseInt(this.props.offset));
return new_date;
}
Lifecycle Example
tick() {
this.setState({
date: this.getCurrentDate()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()} with a {this.props.offset} offset</h2>
</div>
);
}
}
ReactDOM.render(
( <div>
<Clock offset ="+3"/>
<Clock offset="-3" />
</div>
),
document.getElementById('root')
);
Lifecycle Example
- Note that with a single call to ReactDOM we were able to create Clocks that tick
- This is because we are utilizing the second way to tell React to rerender a component: changing it's state
- We do this in the tick() function
Lifecycle Example
- Note that each clock is creating its own setInterval timer using the componentDidMount() function and clearing the timer with the componentWillUnmount() function
- Note also that it was able to set a class variable to the timerID (i.e. this.timerId)
- Since the id of the timer that's executing the tick function is not important to the rendering of the component, we don't need it in the state
- Try to put only the minimum number of variables in the state since if any of the state variables change it rerenders the component
Lifecycle Example
- Note that in the tick() function, we set the state using the function this.setState() instead of setting the state directly
- This is intentional and good practice: outside of the constructor you should change the state of a component using these functions
- Among other advantages this enables React to know to rerender that component