Loading...
In this lesson we will learn about React Components, how we can build our UI, attach events, set the state of the component, pass data through properties and grab data from form elements. Understanding how React components work is one of the base lessons you will have to learn in order to program with react. Lets start...
As mentioned in previous articles, the atoms that build a react application are objects called react elements that are created from the method: React.createElement. combining those atoms together you will get the molecule that builds a react application which is a react component. A react component is a group of react elements that construct a UI block. A react component encapsulates the visual block as well as the logic of the component. Programmers used to think that combining view syntax with js code is bad practice but actually the view part and the logic behind it are so much entangled one within the other in terms of events and responding to state change, that actually combining the view and the logic is easier just as long as you maintain the rule of seperation of concerns. Seperation of concerns means that our component address a well defined problem and can be later reused to solve that problem wherever it accours in our app, it is a concept the promotes reuse of our code, and frankly when looking at a view it's (most of the time) quite intuitive to seperate your app into those blocks. With React components you build your page blocks like the header, footer, login form, etc. The components you build will be connected to the app by passing properties, and the component will manage a private state. Lets start by bootstrapping a simple app.
One type of react component we can create is called function component, cause it is a simple function that returns react elements. We will start by creating a function component that displays an hello world message on the screen. Create an index.html file with the following code:
<html>
<head>
<title>
React Components
</title>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.js"></script>
</head>
<body>
<div id="react-content">
</div>
<script type="text/babel" src="./react-components-tutorial.js">
</script>
</body>
</html>
Notice that we are keeping things simple and we will not start the react app with the create-react-app package. For this lesson it is simply to load the react and react dom scripts from cdn, as well as babel standalone to transpile the script react-component-tutorial.js. Create the script react-component-tutorial.js with the following code:
function Hello(props) {
return (
<div>
<h1>
<span>
{props.message}
</span>
</h1>
<h2>
{props.children}
</h2>
</div>
)
}
ReactDOM.render(
<Hello message="hello world">message through children</Hello>,
document.getElementById('react-content')
);
as you can see our first component is a function, so one way to create a component is by using a function that return react element. That function will get the properties passed as attributes when we created the component. If a component is represented by a function, that function has to be capitalize. The children are passed in the properties as well, the children can be a string or a react element or react component. React will know to rerender the component when the properties change. It's important to note that the properties are read only and must not be modified. We are a bit more limited with those kind of components so lets move to another way to create react components...
another way to create a react component is by creating a class that extends React.Component. in the same file lets create a component called App that will wrap our hello component.
function Hello(props) {
return (
<div>
<h1>
<span>
{props.message}
</span>
</h1>
<h2>
{props.children}
</h2>
</div>
)
}
class App extends React.Component {
render() {
return (
<div>
<h1>App title - {this.props.message}</h1>
<Hello message="hello world">message through children</Hello>
</div>
)
}
}
ReactDOM.render(
<App message="message to app component" />,
document.getElementById('react-content')
);
So a react component can be a class that extends React.Component. The props will be attached to the instance this. How the component is looked is defined in the render method. We can embed in components other components so in our app we are placing the Hello function component we created earlier. The component will redraw itself and call the render method when the properties change.
React maintains a private state of the component, changing the state will cause react to rerender the component. lets create a state with a counter, we will add an interval timer and increate the counter every second. To change the state we call this.setState method.
class App extends React.Component {
state = {
counter: 0
}
componentDidMount() {
setInterval(() => {
this.setState((prevState) =>({counter: prevState.counter + 1}))
}, 1000);
}
render() {
return (
<div>
<h1>App title - {this.state.counter}</h1>
<Hello message="hello world">message through children</Hello>
</div>
)
}
}
we can set the state in the class or in the constructor by assigning a value to it.
We can read the state with this.state.counter
Changing the state is done with the this.setState, and should not be directly modified.
React may optimize and batch multiple setState to one.
Two ways to use setState:
1 - just call it with a new state
2 - place an argument that will be called with the first argument with the previous state, and the second argument with the properties.
You should not rely on the value of the state when calculating the next step cause they can be modified async, so if you do need the previous state you should use the function method and use the state passed to that function.
When setState is called react will merge the object you return with the state, so you can return only the things you want to change in the state.
Note that calling setState does not mean that after the call the state will change, react will change the state async, you can place a callback as the second argument of setState that will be called when the state change.
Our next goal is to create a form with a text input to enter the name, based on that name we will display an hello message. Create another component class called HelloForm:
class HelloForm extends React.Component {
state = {
partialName: '',
name: null
}
setName = (e) => {
this.setState((prevState) => {
return {
name: prevState.partialName
}
})
e.preventDefault();
}
setPartialName = (e) => {
this.setState({
partialName: e.target.value
})
e.preventDefault();
}
render() {
return (<div>
{ this.state.name ? <h1>my name is: {this.state.name}</h1> : <h1>No name</h1>}
<form onSubmit={this.setName}>
<input type="text" value={this.state.partialName} onChange={this.setPartialName} />
</form>
</div>);
}
}
We attach events similar to how we attach it on HTML only the event are camel cased. With the text input we created something refered to as controled component. We set the value from the state and attach a change event to set that state. this prevents the internal state of the dom component and gives our component the control, and the component state as the source of it. It is also gurentees that whenever we need the value in our component we can grab it from the state, and perform easier validation. Usually you will want a controlled component, this will gurentee there are no surprised and the control is not changed somehow by the user and completly controled by you, that said sometimes we want a controller that is managed by the DOM and not by us, or it becomes tidious to connect a listener to every event where you can change the control value. For that case we can grab the value of an input by using uncontrolled component and attaching a ref attribute to that input. The above code can work with uncontrolled component like this:
class HelloForm extends React.Component {
state = {
partialName: '',
name: null
}
setName = (e) => {
// this.setState((prevState) => {
// return {
// name: prevState.partialName
// }
// })
this.setState({
name: this.input.value
})
e.preventDefault();
}
setPartialName = (e) => {
this.setState({
partialName: e.target.value
})
e.preventDefault();
}
render() {
return (<div>
{ this.state.name ? <h1>my name is: {this.state.name}</h1> : <h1>No name</h1>}
<form onSubmit={this.setName}>
{/* <input type="text" value={this.state.partialName} onChange={this.setPartialName} /> */}
<input type="text" ref={input => this.input = input} />
</form>
</div>);
}
}
the ref attribute gets a function that will be called with an argument equals to the DOM element. We assign the dom element to the component instance.
Our component will rerender every time we call setState, even if the state did not change. For example:
class NotReallyChanging extends React.Component {
state = {
counter: 0
}
componentDidMount() {
setInterval(() => {
this.setState({});
}, 1000)
}
render() {
console.log('render NotReallyChanging');
return <h1>My counter will never change - {this.state.counter}</h1>
}
}
This component will have no changes yet the render method will be called every setInterval. There is a lifecycle hook called shouldComponentUpdate that can optimize this performance problem. this hook by default returns true. If returns true the component will rerender, false it will not. Try to avoid placing heavy computation there since it will be called a lot and effect performance, so there are times where it might be better to just place a condition before calling setState. shouldComponentUpdate will get as arguments in the function the nextState and the nextProps where we can compare them with the current ones that are in the this. Lets modify our code to only render the NotReallyChanging if the counter is different.
class NotReallyChanging extends React.Component {
state = {
counter: 0
}
componentDidMount() {
setInterval(() => {
this.setState({});
}, 1000)
}
shouldComponentUpdate(nextState, nextProps) {
return nextState.counter && nextState.counter !== this.state.counter;
}
render() {
console.log('render NotReallyChanging');
return <h1>My counter will never change - {this.state.counter}</h1>
}
}
Not that the nextState does no contain counter cause we are sending an empty object, We are getting the nextState before the merge of the states so it does not mean that the next state will actually equal nextState rather it will be a shallow merge between this.state and nextState. Also note that not only the component that called setState will render, also the children of that component will rerender as well.
Another type of component we can create is called PureComponent. PureComponent are like regular components but in PureComponents react gives us a basic implementation of shouldComponentUpdate. The basic implementation performs shallow comparison between the state and nextState, and the props and nextProps. Shallow comparison means it will go over all the enumerable keys and compare them with ===. This means that on complex state we cannot use PureComponents. Lets create an example of PureComponent:
class MyPureComponent extends React.PureComponent {
state = {
counter: 0
}
componentDidMount() {
setInterval(() => {
console.log('timer');
this.setState({
counter: 0
})
}, 1000);
}
render() {
console.log('pure component render');
return <h1>this should not rerender if counter is the same - {this.state.counter}</h1>
}
}
the state in this component is really simple and only contains a counter number. This component should not rerender if the state is the same - since it will remain identical. We can choose pure component for this component. It's important to note that PureComponent will not render even in cases where the parent renders. You can change the interval to change the counter and you will notice it will rerender once to reflect the change and then remain the same. What happens if we have a more complex state:
class MyPureComponent extends React.PureComponent {
state = {
obj: {
counter: 0
}
}
componentDidMount() {
setInterval(() => {
this.setState((prevState) => {
prevState.obj.counter = prevState.obj.counter + 1;
return {
obj: prevState.obj
}
});
}, 1000)
}
render() {
console.log('pure component render');
return <h1>this should not rerender if counter is the same - {this.state.obj.counter}</h1>
}
}
In this case our state contains an object where we change the counter property of that object every second. The shallow compare does not translate it as a change and will not rerender the change. So a PureComponent will not change when parent changes, will not change if state and props remain the same (shallow compare). If possible we will always prefer PureComponent since there is performance benefits but if we want our component to rerender when parents rerender regardless of properties passed, or if the state is complex and effect children as well we will not choose PureComponents.
During the examples here we used some lifecycle hooks but lets summerise the commonly used and what we usually put there.
called first, we can optionally implement it. The constructor is getting the component properties. The first line should be to call the super and pass the properties. We can initialize the state here, or perform simple sync initializations.
a function that will determine how the component looks like, can return react elements, null, string, arrays.
called once after the first render, good place for more complex initialization like query a server.
return boolean value if rerender should be called on this change.
called after update is reflected, can be used for post update action as well as querying the server. You can also call setState but have to condition it or there will be an infinite loop.
this lesson covered function stateless components, react components, and pure components. We talked about the state of the component and properties and learn when the components rerender and their lifecycle hook. Our next lesson will be about the create-react-app package.