Transforming An Existing React Front End Using KendoReact Components

KendoReact components come in four different flavors covering all modern front-end frameworks – jQuery, Angular, React and Vue. They offer widgets that can be used right out of the box, while also offering endless customizability to fit all existing use-cases.

Here we will see one such example of migrating an existing React front end from a multitude of libraries to a unified system using KendoReact components. This allows the streamlining and unification of the codebase, easing future development tasks.

What are the benefits of KendoReact?

There are numerous benefits of migrating to KendoReact. The first one is replacing all the different libraries and external packages with a unified framework offering everything in one package. And KendoReact does that by bringing a selection of components that are easy to use in all use-cases – from creating modern layouts to building data-rich applications.

The components offered by KendoReact make application building easier, faster and more efficient. They are composable and precisely configurable to give developers the ability to work with them just as they would any other React component and – of course – they support both controlled and uncontrolled states.

Finally using KendoReact offers a stable framework that gets regular updates and is always on top of new changes in both the React and JavaScript worlds, while bringing easy maintainability for future-proofing.

The migration in details

We will focus on three specific cases – one involving the KendoReact Grid component. The Grid allows us to create a unified interface for displaying, sorting and filtering tabled data. The second case showcases several of the layout and dialog components. The Drawer, Window and AppBar components are ready-to-use to create a user interface, fulfilling all the expectations for a modern application. The final is an example of the rich Forms support included in KendoReact.

Users table

Here in the old interface, we were using a table along with a custom filter component. By introducing the KendoReact Grid component we are able to do away with the custom filter and use the included functionality to replace it. Implementing sorting is also painless, as it is a built-in capability of the Grid.

user-ss-old

Old interface

user-ss (1)

New interface

And here is the code for the above interface. It is a single Grid which has all the needed functionality of the original example, but it’s all packaged into one – including filters, sorting and other actions needed to replace the original interface.

Each data column is specified by the GridColumn component. The actions at the top of the grid are specified by the GridToolbar component.

Scroll to the end of this article to see what the code looks like.

Layout

The AppBar component will replace the existing navbar at the top of all pages. It allows for easy unification of the brand identity and navigation items that are present throughout the web app. The Drawer component is used as side navigation which gives a clean interface to navigation items that are used less often and thus do not need to be visible at all times.

drawer-ss-old

Old interface

drawer-ss

New interface

Scroll to the end of this article to see what the code looks like.

Form in Window

Finally, we will showcase the Form and Window components. Here they are used as a pop-up interface for adding and editing existing Users. The Form offers out-of-the-box support for creating complex fields for all types of inputs, along with custom ones.

useredit-ss-old

Old interface

useredit-ss New interface

Scroll to the end of this article to see what the code looks like.

In summary

KendoReact is a great choice for all types of front-end use-cases – from simple presentation pages to extensive data-rich web applications. The library also comes in three other flavors, covering all modern front-end libraries – jQuery, Angular, and Vue. The use of KendoReact in our specific case allows us to transition from a scattering of front-end libraries and components to a unified component library which eases development tasks as well as allowing a unified and fully functional user interface.

 

This article was written by Kalin Koychev, KendoReact guru and Web Developer at Proxiad

 


Users table:

<Grid
  data={orderBy(
    filterBy(this.state.mappedUsers, this.state.filter),
    this.state.sort
  )}
  scrollable="none"
  filterable
  sortable={true}
  sort={this.state.sort}
  onSortChange={(e) => {
    this.setState({
      sort: e.sort,
    });
  }}
  filter={this.state.filter}
  onFilterChange={(e) => {
    this.setState({
      filter: e.filter,
    });
  }}
>
  <GridToolbar>
    <ChipList
      selection="single"
      data={chipData}
      onDataChange={this.handleChipListChange}
      chip={(props) => <Chip onClick={this.handleChipListChange} {...props} />}
    />
    <div>
      <Button icon="close" look="flat" onClick={this.handleFilterReset}>
        Clear Filters
      </Button>
      <ConfigurationPropertiesContext.Consumer>
        {({ internalUserManagement }) =>
          internalUserManagement && (
            <Button
              icon="plus-outline"
              look="flat"
              onClick={() => this.handleOpenModal(USER_CREATE_MODE)}
            >
              Add new user
            </Button>
          )
        }
      </ConfigurationPropertiesContext.Consumer>
    </div>
  </GridToolbar>
  <GridColumn
    title="Name"
    field="fullName"
    width="240px"
    cell={(props) => (
      <td>
        <Avatar
          shape="rounded"
          style={{
            marginRight: ".75rem",
            width: "40px",
            height: "40px",
          }}
        >
          <img
            src={UserService.getUserProfilePictureUrlByEmail(
              props.dataItem.email
            )}
          />
        </Avatar>
        {props.dataItem.fullName}
      </td>
    )}
  />
  <GridColumn field="email" title="e-mail" />
  <GridColumn
    field="mappedRoles"
    title="Role"
    filterCell={dropdownFilterCell(
      this.state.roles.map((role) => role.name),
      "Any"
    )}
    cell={(props) => <td>{props.dataItem[props.field]}</td>}
  />
  <GridColumn
    field="managerName"
    title="Manager"
    filterCell={dropdownFilterCell(
      this.state.managers.map(
        (manager) => `${manager.firstName} ${manager.lastName}`
      ),
      "Any manager"
    )}
  />
  <GridColumn
    title="Actions"
    width="120px"
    filterable={false}
    sortable={false}
    cell={(props) => {
      const user = props.dataItem;
      const principal = this.props.user;
      return (
        <td>
          <Tooltip openDelay={100} anchorElement="target" position="top">
            <Icon
              name="edit"
              title="Edit User"
              onClick={() => this.handleOpenModal(USER_EDIT_MODE, user)}
            />
            <ConfigurationPropertiesContext.Consumer>
              {({ internalUserManagement }) =>
                internalUserManagement &&
                principal.id !== user.id && (
                  <Icon
                    name="delete"
                    title="Delete User"
                    onClick={() => this.handleOpenModal(USER_DELETE_MODE, user)}
                  />
                )
              }
            </ConfigurationPropertiesContext.Consumer>
            <RunAsBtn user={props.dataItem} principal={principal} />
          </Tooltip>
        </td>
      );
    }}
  />
</Grid>
  • data property is used to pass all the data to the Grid
  • orderBy and filterBy are used to manage the displaying of the data
  • sortablesort onSortChange are properties needed for the sorting of the Grid
  • filterablefilter and onFilterChange are properties needed for the filtering of the Grid
  • GridToolbar allows for adding a toolbar containing different actions to the top of the Grid. In this case, we have added a ChipList used for additional filters outside of the existing columns. There are also two buttons – one to clear all filters – and another with a custom action – opening a window for adding new users.
  • GridColumn components, which specify each column of the Grid. Some only need a field and title property that tell the Grid which data field to use and what the column title would be. Others use the cell property to render a custom view in the table cell for the items in that column
  • filterCell is used to pass a custom filtering component for certain columns.

Layout:

First, we will showcase the AppDrawer which replaces the top navigation bar.

<AppBar className="row">
  <AppBarSection>
    <Link to="/" className="navbar-brand">
      <img src="/images/logo.png" alt="Proxiad" width="120" height="33" />
    </Link>
  </AppBarSection>

  <AppBarSection>
    <nav className="navbar-nav">
      {AuthenticationService.hasPermission(user, PERMISSION_SELF_ASSESS) && (
        <NavLink exact to="/mygoals" activeClassName="selected">
          My Goals
        </NavLink>
      )}
      {AuthenticationService.hasPermission(user, PERMISSION_SELF_ASSESS) && (
        <NavLink exact to="/appraisal" activeClassName="selected">
          Appraisal
        </NavLink>
      )}
      {AuthenticationService.hasPermission(user, PERMISSION_SELF_ASSESS) && (
        <NavLink exact to="/appraisal/history" activeClassName="selected">
          History
        </NavLink>
      )}
      {AuthenticationService.hasPermission(
        user,
        PERMISSION_ASSESS_EMPLOYEES
      ) && (
        <NavLink exact to="/manager" activeClassName="selected">
          Manager
        </NavLink>
      )}
      {AuthenticationService.hasPermission(
        user,
        PERMISSION_MANAGE_ORGANIZATIONAL_GOALS
      ) && (
        <NavLink
          exact
          to="/goals/template/organizational"
          activeClassName="selected"
        >
          Organizational Goals
        </NavLink>
      )}
      {AuthenticationService.hasPermission(
        user,
        PERMISSION_MANAGE_GOAL_TEMPLATES
      ) && (
        <NavLink exact to="/goals/template" activeClassName="selected">
          Goals Template
        </NavLink>
      )}
      {AuthenticationService.hasPermission(user, PERMISSION_MANAGE_PROCESS) && (
        <NavLink exact to="/processes" activeClassName="selected">
          Processes
        </NavLink>
      )}
    </nav>
  </AppBarSection>

  <AppBarSpacer />

  {(AuthenticationService.hasPermission(user, PERMISSION_MANAGE_USERS) ||
    AuthenticationService.hasPermission(user, PERMISSION_MANAGE_ROLES) ||
    AuthenticationService.hasPermission(user, PERMISSION_MANAGE_TEAMS)) && (
    <AppBarSection className="actions">
      <ButtonGroup>
        <DropDownButton
          ref={settingsDropdown}
          icon="cog"
          look="flat"
          onItemClick={(event) => history.push(event.item.to)}
        >
          {AuthenticationService.hasPermission(
            user,
            PERMISSION_MANAGE_USERS
          ) && (
            <DropDownButtonItem
              text="Users"
              iconClass="fa fa-users-cog fa-fw"
              to="/admin/users"
            />
          )}
          {AuthenticationService.hasPermission(
            user,
            PERMISSION_MANAGE_ROLES
          ) && (
            <DropDownButtonItem
              text="Roles"
              iconClass="fa fa-cogs fa-fw"
              to="/admin/roles"
            />
          )}
          {AuthenticationService.hasPermission(
            user,
            PERMISSION_MANAGE_TEAMS
          ) && (
            <DropDownButtonItem
              text="Teams"
              iconClass="fa fa-users fa-fw"
              to="/admin/teams"
            />
          )}
        </DropDownButton>
        <Button
          icon="arrow-60-down"
          look="flat"
          onClick={() => {
            if (!document.querySelector(".k-popup .k-list"))
              settingsDropdown.current.mainButton.click();
          }}
        />
      </ButtonGroup>
    </AppBarSection>
  )}

  {user && (
    <AppBarSection>
      <ButtonGroup>
        <Button
          imageUrl={userProfilePicture}
          look="flat"
          onClick={() => setShowUserProfile(!showUserProfile)}
        />
        <Button
          icon="arrow-60-down"
          look="flat"
          onClick={() => setShowUserProfile(!showUserProfile)}
        />
      </ButtonGroup>
    </AppBarSection>
  )}
</AppBar>
  • The AppBar offers an easy-to-use navigation component, which can contain any custom configurations. It is made up of AppBarSection and AppBarSpacer components. Each section separates different items inside the AppBar, while the AppBarSpacer adds extra separation between sections.

The second part of the above interface is the Drawer component, which replaces the side navigation.

<Drawer
expanded={showUserProfile}
position={"end"}
mode={"overlay"}
onOverlayClick={() => setShowUserProfile(false)}
onSelect={onSelect}
items={items}
item={(props) => {
return (
<DrawerItem {...props}>
{props.user && (
<div>
<Avatar type="image" size="medium" shape="circle">
<img src={userProfilePicture} alt="User avatar" />
</Avatar>
{user.firstName} {user.lastName} <br />
{user.title} <br />
{user.email} <br />
{`${user.roles.map((r) => getRoleName(r)).join(" / ")}`} <br />
</div>
)}
{!props.user && <div>{props.text}</div>}
{props.versiondata && (
<div>
{props.versiondata.git
? `Ver.: ${props.versiondata.build.version} / Rev.${props.versiondata.git.commit.id} / ${props.versiondata.git.commit.time}`
: `Ver.: ${props.versiondata.build.version}`}
</div>
)}
</DrawerItem>
);
}}
>
<DrawerNavigation></DrawerNavigation>
</Drawer>
  • Drawer is used for side navigation
  • expanded property sets the display of the drawer
  • position and mode control the display style
  • onOverlayClick controls the behavior when clicking outside of the drawer navigation
  • onSelect controls the functionality of clicking an item in the drawer
  • items is the total items to be displayed in the drawer
  • item allows for custom rendering, which in this case is used to display different items, as in our use-case we have a few differently displayed items from the others – it returns a DrawerItem component for each item.

Form in window:

And here is the code – first for the form itself, which uses the Form (aliased as KendoForm) and FormElement components.

<KendoForm
  onSubmit={handleSubmit}
  render={(formRenderProps) => (
    <FormElement>
      <fieldset>
        <Field name={"firstName"} component={Input} label={"First Name"} />
        <Field name={"lastName"} component={Input} label={"Last Name"} />
      </fieldset>
      <Field name={"title"} component={Input} label={"Position Title"} />
      <fieldset>
        <Field name={"username"} component={Input} label={"Username"} />
        <Field name={"email"} component={Input} label={"Email"} />
      </fieldset>
      <fieldset>
        <Field name={"password"} component={Input} label={"Password"} />
        <Field
          name={"confirmPassword"}
          component={Input}
          label={"Confirm Password"}
          disabled={true}
        />
      </fieldset>
      <DropDownList label="Manager"></DropDownList>
      <DropDownList label="Teams"></DropDownList>
      <KendoLabel>Roles</KendoLabel>
      <fieldset>
        {hcRoles.map((role, i) => (
          <KendoLabel style={{ marginBottom: "5px" }} key={i}>
            <Switch />
            {role}
          </KendoLabel>
        ))}
      </fieldset>
      <div>
        <fieldset style={{ display: "flex", flexWrap: "wrap" }}>
          <KendoLabel style={{ marginRight: "5px" }}>
            <Switch defaultChecked={true} />
            Active
          </KendoLabel>
          <KendoLabel>
            <Switch />
            Limited visibility
          </KendoLabel>
        </fieldset>
        <fieldset style={{ display: "flex", flexWrap: "wrap" }}>
          <KendoButton style={{ marginRight: "5px" }}>Cancel</KendoButton>
          <KendoButton
            type={"submit"}
            primary={true}
            onClick={formRenderProps.onSubmit}
          >
            Submit
          </KendoButton>
        </fieldset>
      </div>
    </FormElement>
  )}
/>
  • The form containing all different form fields. The Field component is used and it’s passed the type of field as a component prop.

And the code for the pop-up which employs the Window component:

<Window
  title={"Add new user"}
  onClose={this.handleCloseModal}
  initialTop={0}
  initialHeight={660}
  initialWidth={
    window.screen.width > 750
      ? window.screen.width / 2
      : window.screen.width - 50
  }
>
  {(this.state.mode === USER_CREATE_MODE ||
    this.state.mode === USER_EDIT_MODE) && (
    <EditUserForm
      user={this.state.selectedUser}
      handleClose={this.handleCloseModal}
      handleFormSubmit={this.handleSubmit}
      users={this.state.users}
      roles={this.state.roles}
      teams={this.state.teams}
    />
  )}
</Window>
  • Window is a component that creates a pop-up window. The content of the component is rendered inside the displayed window, in this case EditUserForm, which is the custom form.