Ext JS to React: Button

   JavaScript
Ext JS to React: Button

This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.

Buttons are one of the more elemental components used in an application. Most of us have likely never constructed an Ext JS application without them. You may have a button to trigger adding a new user to a grid. Or perhaps a “submit” button to send a form up to your server. Buttons can stand alone, have a partnering menu for additional UI, or be grouped together in a connected relationship. Let’s take a look at each of these use cases in the sections below.

Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.

Simple React Button

Buttons can be a single element having just text and/or an icon. The React approach uses new component syntax along with JSX, but is structurally somewhat similar to Ext JS. Below is a simple button component that we’ll define at src/Button.js. A click handler can be passed in via an onClick prop:

import React from 'react';

export default props => <button onClick={props.onClick}>Add User</button>;

In the following example, we’ll create a div with a “top-toolbar” container holding a single “Add User” button:

import React, { Component } from 'react';
import Button from './Button';
import './App.css';

class App extends Component {
  render () {
    return (
      <div className="top-toolbar">
        <Button onClick={this.handleAddUser}>Add User</Button>
      </div>
    );
  }
  handleAddUser = () => {
    // handle button click
    console.log('add user');
  };
}

export default App;

Some CSS to round out our example:

.top-toolbar {
  padding: 8px 12px;
  background: #3d76b5;
}


Ext JS to React: Button, Simple



Here, using pure HTML, we define a view with a child toolbar that contains the button. The button also has an onClick handler that executes a method on the main view.

You can review the sample code on the Git repo.

Adding an Icon to Button in React

To configure an Ext JS button with an icon, you’d add the iconCls config to the button:

tbar: [{
    text: 'Add User',
    iconCls: 'x-fa fa-user-plus',
    handler: 'onAddUser'
}]

React doesn’t contain an icon set. You’ll have to define your own or use one a pre-build solution such as FontAwesome. Luckily, FontAwesome has a React module available to lend a hand. First, install the necessary modules:

npm i --save @fortawesome/fontawesome
npm i --save @fortawesome/react-fontawesome
npm i --save @fortawesome/fontawesome-free-solid

Next, we can create our button using FontAwesome icons:

import React, { Component } from 'react';
import FontAwesome from '@fortawesome/react-fontawesome';
import { faUserPlus } from '@fortawesome/fontawesome-free-solid';
import './App.css';

class App extends Component {
  render () {
    const style = {
      marginRight: '12px' // inline style example
    };

    return (
      <div className="top-toolbar">
        <button onClick={this.handleAddUser}>
          <FontAwesome icon={faUserPlus} style={style} />
          Add User
        </button>
      </div>
    );
  }
  handleAddUser = () => {
    // handle button click
    console.log('add user');
  }
}

export default App;


Ext JS to React: Button, Icon



You can review the sample code on the Git repo.

React Button with a Child Menu

Buttons can be more than just text and an icon. It’s common for buttons to have a menu attached to allow the user to select from different options. In Ext JS, you can do something like:

tbar: [{
    text: 'User Controls',
    iconCls: 'x-fa fa-user-plus',
    menu: [{
        text: 'Add User',
        handler: 'onAddUser'
    }, '-', {
        text: 'Remove User',
        handler: 'onRemoveUser'
    }]
}]

With React, you can create a menu component and position it with a positioning library like react-popper. However, you may also opt to use a pre-built solution such as semantic-ui-react. Semantic UI is a larger component library that has, amongst other things, a set of button controls we can put to good use here. Let’s take a look at the Semantic UI button / menu combo in the following example:

// install the following packages
// npm install semantic-ui-react
// npm install semantic-ui-css

import React, { Component } from 'react';
import { Dropdown } from 'semantic-ui-react';
import 'semantic-ui-css/semantic.min.css';
import './App.css';

const { Divider, Item, Menu } = Dropdown;

class App extends Component {
  render () {
    return (
      <div className="top-toolbar">
        <Dropdown
          text="User Controls"
          icon="add user"
          className="icon"
          floating labeled button
        >
          <Menu>
            <Item onClick={this.handleAddUser}>Add User</Item>
            <Divider />
            <Item onClick={this.handleRemoveUser}>Remove User</Item>
          </Menu>
        </Dropdown>
      </div>
    );
  }
  
  handleAddUser = () => {
    // handle menu item click
    console.log('add user')
  }
  handleRemoveUser = () => {
    // handle menu item click
    console.log('remove user');
  }
}

export default App;


Ext JS to React: Buttons, Advanced



Writing your own button and menu views is certainly a viable option, but Semantic UI shows us there’s some great pre-built solutions in the React ecosystem you may want to check out when considering a move from Ext JS. The kendo-react-ui and react-bootstrap libraries also have similar controls and are definitely worth investigating.

You can review the sample code on the Git repo.

React Segmented Button

Another popular use of buttons is to group them and show them as pressed or not pressed. To coordinate the pressed state we’ll define a SegmentedButton component as a container with any number of button child nodes.

import React, {Component} from 'react';
import './SegmentedButton.css';

class SegmentedButton extends Component {
  static defaultProps = {
    pressed: [],
    allowDepress: false
  }
  state = {
    pressed: this.props.pressed
  }
  componentWillReceiveProps ({ pressed }) {
    this.setState({ pressed });
  }
  render() {
    const { pressed } = this.state;
    const { onClick } = this.props;
    return (
      <div
        className="button-group"
        onClick={this.onClick.bind(this, onClick)}
      >
        {/* loop over the child buttons and decorate pressed
        buttons with the className of "btn-pressed" */}
        {this.props.children.map(btn => {
          const { props } = btn;
          const { value } = props;
          const className = pressed.includes(value) ? 'btn-pressed' : '';
          
          // use cloneElement to apply our own
          // props over the button's props
          return React.cloneElement(btn, {
            ...props,
            key: value,
            className
          });
        })}
      </div>
    );
  }
  onClick (handler, e) {
    // call the onClick handler if one was passed in
    if (handler && typeof handler === 'function') {
      handler.call(this, e);
    }
    const { allowDepress } = this.props;
    const { target: btn } = e;
    const { tagName } = btn;
    let pressedState = this.state.pressed.slice(0);
    
    if (tagName === 'BUTTON') {
      const { value } = btn;
      const valIndex = pressedState.indexOf(value);
      const isPressed = valIndex !== -1;
      // toggle the "pressed" button state
      if (isPressed) {
        if (allowDepress || (!allowDepress && pressedState.length > 1)) {
          pressedState = pressedState.filter(item => item !== value);
        }
      } else {
        pressedState = [...pressedState, value];
      }
      this.setState({
        pressed: pressedState 
      });
    }
  }
}

export default SegmentedButton;

Segmented Button Class Explained

Our class’s defaultProps, state, and componentWillReceiveProps members set the state of the component based on passed props or user interaction with the component. The render method creates a container for the buttons passed in and applies the “pressed” className to any buttons whose values are found in this.state.pressed. Finally, we have the onClick handler that sets the “pressed” state of the button. Additionally, it will call any onClick handler passed to SegmentedButton so that our click handler doesn’t stomp any onClick prop passed in on the component instance.

Creating an instance of our SegmentedButton is then very straightforward. The allowDepress, pressed array, and onClick handler props are all optionally available.

import React from 'react';
import SegmentedButton from './SegmentedButton';
import './App.css';

const App = () => {
  return (
    <SegmentedButton>
      <button value="one">One</button>
      <button value="two">Two</button>
      <button value="three">Three</button>
    </SegmentedButton>
  );
}

export default App;

Segmented Button CSS

Below is the {appRoot}/src/SegmentedButton.css used to style the segmented buttons:

.button-group {
  display: inline-block;
}
.button-group button {
  background-color: #3094bb;
  border: 1px solid #166e90;
  color: white;
  padding: 8px 22px;
  cursor: pointer;
  border-radius: 0;
  font-size: 14px;
}
.button-group > button:not(:last-child) {
  border-right: none;
}
.button-group > button:first-of-type {
  border-top-left-radius: 3px;
  border-bottom-left-radius: 3px;
}.button-group > button:last-child {
  border-top-right-radius: 3px;
  border-bottom-right-radius: 3px;
}
.button-group button:hover {
  background-color: #40a4cc;
}
.btn-pressed {
  background-color: #267696 !important;
}

Ext JS to React: Button, Segmented



You can review the sample code on the Git repo.

Conclusion

Buttons are often one of the more underappreciated components in our grab bag of views used all over our applications. At first blush you might think “yes, a button… I get it.” But with a little creativity, we see how buttons can play well together to control / reveal the state of a view in a segmented configuration. It can open a menu or other context popup, revealing fresh real estate for the user to engage. In this article, we’ve revealed a few of the ways you can recreate various Ext JS button types in React. However, there’s certainly more implementations that could be explored: cycle button, split button, etc. Perhaps you’ve found an existing third-party solution yourself you’d like to share? Please, feel free to post your own successes and findings in the comments below!


Like What You See?

Got any questions?


>