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.

We are going to cover following topics:

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...

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);
JavaScript Objects cannot be resolved using JSX.

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

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.

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.

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.

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.

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.

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.

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.

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.

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.

A good rule of thumb is that elements inside the map() call need 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

  1. Use camelcase for all event listeners.
  2. event handlers will be always functions.

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

Uncaught TypeError: Cannot read property 'setState' of undefined

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.

results matching ""

    No results matching ""