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; }
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;
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;
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; }
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!
Mitchell Simoens
Related Posts
-
Ext JS to React: Migration to Open Source
Worried about Migrating from Ext JS? Modus has the Answers Ideraโs acquisition of Sencha has…
-
Ext JS to React: FAQ
This is part of the Ext JS to React blog series. React is Facebook's breakout…