We have seen how to create components using both functions and classes. We need to make our components more dynamic, that means our components should be able to resolve expressions, conditionally render elements or components, loop through a list of objects and display them on UI etc... We can do all of them using JSX.
Web applications are only interesting when they are dynamic. Any UI library will always provide a way to pass data around the system and React's idea is via the Props and State.
Most of the examples to demonstrate JSX uses Functional Components. One thing to remember is all the attributes passed to components will be accessed through parameter called props for Functional components and this.props for Class components. You can learn more about Props from here.
React
We are going to cover following topics:
- Resolving Expressions
- Dynamic JSX Tags
- JSX Children
- Conditional Rendering
- Rendering Lists
- Event Handling
Resolving Expressions
Expressions are resolved using single curly braces in JSX. They can be used to define classes, styles, inner html, dynamic string concatination etc...
var container1 = document.getElementById('container-1');
const a = 10;
const b = 10;
const styles = {
backgroundColor: 'green',
color: 'white'
};
const alertClass = 'alert-success';
// Resolving Mathematical expressions.
const Maths = (props) => {
return (
<div>
<h6>Resolving Mathematical Expressions</h6>
<p>A = {props.a}</p>
<p>B = {props.b}</p>
<p>Sum = {props.a + props.b}, Subtraction = {props.a - props.b},
Multiplication = {props.a * props.b}, Division = {props.a / props.b}</p>
</div>
)
}
//Styles are passed as objects. All the style properties are camel-cased
const Styles = (props) => {
return (
<div style={props.styles} className="container"><h3>Added Styles for this component</h3></div>
)
}
// class is renamed to className and className accepts string, so we are using ES6 back-ticks to resolve string.
const DynClass = (props) => {
return (
<div className={`alert ${props.alertClass}`}><h3>Added Dynamic Alert class to component</h3></div>
)
}
// Default value of prop if not provided is boolean true.
const DefaultPropVal = (props) => {
if(props.defaultVal) {
return (
<div>
<h6>Default Prop value found</h6>
</div>
)
}
return (
<div><h6>No default Prop found</h6></div>
)
}
const expressionsComponent = (
<div>
<Maths a={a} b={b} />
<Styles styles={styles} />
<DynClass alertClass= {alertClass} />
<DefaultPropVal defaultVal />
</div>
)
ReactDOM.render(expressionsComponent, container1);
Dynamic JSX tags
Article is published by Robert Pankowecki
For many React developers using JSX it is not clear how to make a dynamic JSX tag. Meaning that instead of hardcoding whether it is input
or textarea
or div
or span
(or anything else) we would like to keep it in a variable.
Let’s have a look.
const input = this.props.long ?
<textarea onChange={this.props.onChange} className="make-this-pretty"
id="important-input" name="important-input" /> :
<input onChange={this.props.onChange} className="make-this-pretty"
id="important-input" name="important-input" />;
As you can see there is a lot of duplication here. The only difference is that sometimes we want to use input and sometimes textarea. How can we do it? Quite simply it turns out.
const Tag = this.props.long ? "textarea" : "input"
const input = <Tag onChange={this.props.onChange} className="make-this-pretty"
id="important-input" />;
However it is required that your variable starts with uppercase instead of lowercase. Lowercase JSX tag names are directly compiled as strings. You can see the result of JSX compilation on the right column:
JSX Children
Anything between opening and closing tags is called children in JSX and is accessed through props.children in functional component and this.props.children in Class components.
<Wish>
John Doe
</Wish>
Children is accessed through props.children
in a component. So in WishComponent
John Doe can be accessed through props.children
var container2 = document.getElementById('container-2');
const Wish = (props) => {
return (
<h2>Hello {props.children} !!</h2>
)
}
ReactDOM.render(<Wish>John Doe</Wish>, container2);
Children is not restricted to only strings, but can contain other html tags and even components. Anything between opening and closing tags is assigned to props.children
. Let's see another example where we have combination of html tags and components.
var container3 = document.getElementById('container-3');
const ProfilePic = (props) => {
const profileImg = { width: '100px', height: '100px' };
return (
<div className="col-sm-4" style={profileImg}>
<img src={props.imageSrc} />
</div>
)
}
const ProfileDetails = (props) => {
return (
<div className="col-sm-8">
<div className="form-control">
<label>Name:</label>
{props.name}
</div>
<div className="form-control">
<label>Age:</label>
{props.age}
</div>
</div>
)
}
const Profile = (props) => {
console.log(props.children);
return (
<div className="row">
{props.children}
</div>
)
}
ReactDOM.render(
<Profile>
<h4 className="text-center">Profile</h4>
<ProfilePic imageSrc="http://www.hbc333.com/data/out/190/47199326-profile-pictures.png"/>
<ProfileDetails name="John Doe" age="27"/>
</Profile>, container3);
Conditional Rendering
Conditional statements are quite common in our daily programming tasks. Let's see how we can do conditional rendering using JSX.
If-Else
There are couple of ways to write If-else statements in JSX.
1) Assigining JSX block to variable and returning it.
var container4 = document.getElementById('container-4');
const CanVote = (props) => {
let message;
let voteClass;
if(props.age >= 18) {
message = <h4>You are eligible for voting</h4>
voteClass = 'alert alert-success';
}else {
message = <h4>You are not eligible for voting</h4>
voteClass = 'alert alert-danger';
}
return (
<div className={voteClass}>
<h3>Hi {props.user} !!</h3>
{message}
</div>
)
}
ReactDOM.render(<CanVote user="John Doe" age="18" />, container4);
2) Moving your conditional code into a function and calling it. This approach will keep your component render method clean.
var container5 = document.getElementById('container-5');
class CanVote extends React.Component {
getMessage() {
if(this.props.age > 0) {
return <h4>You are eligible for voting</h4>
}
return <h4>You are not eligible for voting</h4>
}
render() {
let voteClass;
voteClass = this.props.age >= 18 ? 'alert alert-success': 'alert alert-danger';
return (
<div className={voteClass}>
<h3>Hi {this.props.user} !!</h3>
{this.getMessage()}
</div>
)
}
}
ReactDOM.render(<CanVote user="John Doe" age="18" />, container5);
Inline If-Else
Using the ternary operator to decide what to render. Prefer ternary only for smaller conditions, using it for larger code blocks makes your code less readable.
var container6 = document.getElementById('container-6');
class SayHello extends React.Component {
render() {
return (
this.props.user ? <h4>Hello {this.props.user}, How are you ?</h4>: <h4>Sorry, What is your name ?</h4>
)
}
}
ReactDOM.render(<SayHello user="John Doe"/>, container6);
Logical If with &&
We can conditionally include element or component using logical And(&&) operator. It is equivalent to If condition.
var container7 = document.getElementById('container-7');
const names = ["John Doe", "Mary", "Oliver"];
const TotalNames = (props) => {
return (
<div>
{ names.length > 0 && <h4>You have {names.length} in Total !!</h4>}
</div>
)
}
ReactDOM.render(<TotalNames />, container7);
Rendering Lists
Looping and dispalying data is a very common while building applications. Lists, Tables, Dropdowns are few components where lists are used most frequently.
In normal html we display list as below.
<ul>
<li>John</li>
<li>Robert</li>
<li>Jim</li>
</ul>
when transpiled to React elements it looks like
React.createElement(
"ul",
null,
[
React.createElement("li", null, "John"),
React.createElement("li", null, "Robert"),
React.createElement("li", null, "Jim")
]
);
However, our list of users cannot be hardcoded into a component, we should be able to iterate over the array of users and transform them to React elements and then add the list as children to ul
element. How we are going to do it?
React doesn't have any methods to manipulate and render data like in Angular. Instead, it uses Javascript functions for these kind of tasks. Till now, we have seen React will evaluate any Javascript expression you provide it using single curly braces, and it also includes arrays. We use Javascript's map method to iterate over our array and create a transformed output.
We have to transform our users list to list of React elements and loop through them. Wait that's not going to be easy because we are not using React elements to write our code, we are using JSX so transformation boils down to something like below.
var container8 = document.getElementById('container-8');
class UsersList extends React.Component {
render() {
const users = ['John', 'Robert', 'Jim'];
return (
<ul>
{
users.map((user, index) => {
return <li>{user}</li>
})
}
</ul>
)
}
}
ReactDOM.render(<UsersList />, container8);
Let's quickly recap what's going on
1) UsersList component retured an unordered list with javascript expression in curly braces as its children.
2) JavaScript expression uses a map function on array which returns a transformed output "{user}"
as a list.
OK !! So this is what we expected, our array transformed to list of JSX elements. This not only applies to list but also to tables, dropdowns etc...
When transpiled to React elements it looks like
class UsersList extends React.Component {
render() {
const users = ['John', 'Robert', 'Jim'];
return React.createElement(
'ul',
null,
users.map((user, index) => {
return React.createElement(
'li',
null,
user
);
})
);
}
}
ReactDOM.render(React.createElement(UsersList, null), container8);
Lets go with another example where we use table.
var container9 = document.getElementById('container-9');
const users = [
{ name: 'John', age: 20, hobbies: ['Cricket', 'Running'] },
{ name: 'Robert', age: 12, hobbies: ['Video Games', 'Tennis'] },
{ name: 'Mary', age: 36, hobbies: ['Tennis', 'Yoga'] }
];
const Header = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Hobbies</th>
</tr>
</thead>
)
}
class UsersList extends React.Component {
render() {
return (
<table>
<Header />
<tbody>
{
this.props.users.map((user, index) => {
let hobbies = '';
for(let i=0; i<user.hobbies.length; i++) {
hobbies = hobbies + user.hobbies[i]
if(i < (user.hobbies.length - 1)) {
hobbies = hobbies + ', ';
}
}
return (
<tr>
<td>{user.name}</td>
<td>{user.age}</td>
<td>{hobbies}</td>
</tr>
)
})
}
</tbody>
</table>
)
}
}
ReactDOM.render(<UsersList users={users}/>, container9);
If your code for trasforming element becomes too long. It's always a good idea to keep all of that in another component. Let's change the example above to include User
component.
var container10 = document.getElementById('container-10');
const users = [
{ name: 'John', age: 20, hobbies: ['Cricket', 'Running'] },
{ name: 'Robert', age: 12, hobbies: ['Video Games', 'Tennis'] },
{ name: 'Mary', age: 36, hobbies: ['Tennis', 'Yoga'] }
];
const THeader = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Hobbies</th>
</tr>
</thead>
)
}
const User = (props) => {
let hobbies = '';
for(let i=0; i<props.user.hobbies.length; i++) {
hobbies = hobbies + props.user.hobbies[i]
if(i < (props.user.hobbies.length - 1)) {
hobbies = hobbies + ', ';
}
}
return (
<tr>
<td>{props.user.name}</td>
<td>{props.user.age}</td>
<td>{hobbies}</td>
</tr>
)
}
class UsersList extends React.Component {
render() {
return (
<table>
<THeader />
<tbody>
{
this.props.users.map((user, index) => {
return <User user={user}/>
})
}
</tbody>
</table>
)
}
}
ReactDOM.render(<UsersList users={users}/>, container10);
Wow!! we have finally completed how to render the list. That's great, but we are left with one more issue. If you open debugging tools and see the console. You end up with seeing an error
THe error is asking you to provide a prop called "key".
A "key" is a special string attribute you need to include when creating lists of elements. Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity. The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys.
So above examples need to add key attribute to their returned elements in map to avoid error.
// Example - 1
<ul>
{
users.map((user, index) => {
return <li key={index}>{user}</li>
})
}
</ul>
//Example - 3
<table>
<THeader />
<tbody>
{
this.props.users.map((user, index) => {
return <User user={user} key={index}/>
})
}
</tbody>
</table>
Event Handling
Event Handling is one of the important aspects of UI development. In React handling events are similar to DOM. There are only a couple of differences
- Use camelcase for all event listeners.
- event handlers will be always functions.
var container11 = document.getElementById('container-11');
const AlertButton = () => {
const pressed = () => {
alert('Alert Button Pressed');
}
return <div className="btn btn-lg btn-danger" onClick={pressed}>Alert Button</div>
}
ReactDOM.render(<AlertButton />, container11);
While using Class components, we generally write class methods which are accessible using this keyword. However component's context is not accessible inside the method, i.e. you cannot access this.state, this.props, this.setState etc... inside your method.
class HighlightText extends React.Component {
constructor(props) {
super(props);
this.state = {
highlightClass: 'alertl-default'
}
}
highlightText() {
this.setState({
highlightClass: 'alert-success'
});
}
originalText() {
this.setState({
highlightClass: 'alert-default'
});
}
render() {
return <div className={`alert ${this.state.highlightClass}`}
onMouseEnter={this.highlightText}
onMouseLeave={this.highlightText} >Text Highlighted</div>
}
}
ReactDOM.render(<HighlightText />, container12);
The expectation is to highlight the text when mouse entered and bring the text to normal color when mouse left the text. However, this is not going to work and you will get the following error
var container12 = document.getElementById('container-12');
This is because component's this is not bound to the highlightText()
or originalText()
methods. In order to make this work we simply bind the method definition to "this" in the constructor.
class HighlightText extends React.Component {
constructor(props) {
super(props);
this.state = {
highlightClass: 'panel-default'
}
this.highlightText = this.highlightText.bind(this);
this.originalText = this.originalText.bind(this);
}
highlightText() {
this.setState({
highlightClass: 'alert-success'
});
}
originalText() {
this.setState({
highlightClass: 'alert-default'
});
}
render() {
return <div className={`alert ${this.state.highlightClass}`}
onMouseEnter={this.highlightText}
onMouseLeave={this.originalText} >Text Highlighted</div>
}
}
ReactDOM.render(<HighlightText />, container12);
However if you doesn't like every method to use bind for every method in constructor. There are two ways to do it
- Using Property Initializer Syntax which allows directly to define properties in a class. In this case there is no need to use bind in constructor and above methods can be directly initialized in the class itself.
const highlightText = () => {
this.setState({
highlightClass: 'alert-success'
});
}
const originalText = () => {
this.setState({
highlightClass: 'alert-default'
});
}
- Using inline arrow functions to call the methods. However this kind of approach is not recommended.
<div className={`alert ${this.state.highlightClass}`}
onMouseEnter={() => this.highlightText()}
onMouseLeave={() => this.originalText()} >Text Highlighted</div>
The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the property initializer syntax, to avoid this sort of performance problem.
I would recommending going through Supported Events for list of all supported events in reactjs.