Crafting Beautiful UIs in React Using Ant Design

Alex Taylor

Ant Design is a React UI library that has a plethora of easy-to-use components that are useful for building elegant user interfaces.

Created by Chinese conglomerate Alibaba, Ant Design is used by several big names: Alibaba (of course), Tencent, Baidu, and more. While Material-UI remains the most popular React UI library with over 40k stars on Github, Ant Design is currently at a close second, and is quickly closing-in the gap.

There’s also a mobile version of Ant Design, and you can learn more about it here.

Getting Started

In this tutorial, we’ll build a basic todo application to showcase a few of Ant Design’s components. Our first step will be to set up our boilerplate. I’ve done so using create-react-app.

Then we’ll need to add the antd dependency to the project:

$ yarn add antd

# or, using npm:
$ npm install antd --save

Before we start building our <Todo /> component, we’ll add a reference to it in the root component:

index.js

import React from "react";
import ReactDOM from "react-dom";
import Todo from "./todo";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <Todo />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Building the Todo Component

Now we can start building our <Todo /> component. Open a new file called todo.js with the following contents:

todo.js

import React from "react";

export default class Todo extends React.Component {
  render() {
    return (
      <div className="todoContainer">
        <h1>TODO App</h1>
      </div>
    );
  }
}

I’m going to edit the stylesheet so that the .todoContainer is nicely centered on the page.

styles.css

.App {
  font-family: sans-serif;
  text-align: center;
}

.todoContainer {
  width: 75%;
  margin-left: auto;
  margin-right: auto;
}

Cool! Now let’s add an input to the <Todo /> component. We’ll use the one provided by antd, making sure to also import the CSS file for the library.

todo.js

import React from "react";
import { Input } from "antd";

// Don't forget to include the CSS styles for antd!
import "antd/dist/antd.css";

export default class Todo extends React.Component {
  render() {
    return (
      <div className="todoContainer" />
        <h1>TODO App</h1>

        <Input
          placeholder="What needs to be done?"
        />
      </div>
    );
  }
}

Great, now we are rendering a text input box. However, it doesn’t do anything. We need our input to add its content to the component’s state so we can render a list of todos. We’ll use the onPressEnter prop on our <Input /> to detect when the user submits a new todo:

todo.js

// --- snip ---

export default class Todo extends React.Component {
  constructor() {
    super();

    // Initialize the state
    this.state = {
      todos: []
    };
  }

  handlePressEnter = e => {
    // Create a todo object containing its index and content
    const todo = {
      index: this.state.todos.length,
      content: e.target.value
    };

    // Add the todo to our array
    const newTodos = this.state.todos.concat(todo);

    this.setState({
      todos: newTodos
    });

    // Clear input
    e.target.value = "";
  };

  render() {
    return (
      <div className="todoContainer">
        <h1>TODO App</h1>

        <Input
          placeholder="What needs to be done?"
          onPressEnter={this.handlePressEnter}
        />
      </div>
    );
  }
}

Now handlePressEnter() will update the component’s state whenever the user inputs a new todo item, but we still have to render them.

For this purpose we can use Ant Design’s <List /> component. It takes in an array as its dataSource prop and renders it according to whatever function is passed to its renderItem prop.

todo.js

import React from "react";
import { Input, List } from "antd";

import "antd/dist/antd.css";

export default class Todo extends React.Component {
  // --- snip ---

  render() {
    return (
      <div className="todoContainer">
        <h1>TODO App</h1>

        <Input
          placeholder="What needs to be done?"
          onPressEnter={this.handlePressEnter}
        />

        <List
          {/* emptyText sets the text to display in an empty list */}
          locale={{ emptyText: "No todo items" }}
          dataSource={this.state.todos}
          renderItem={item => (
            <List.Item>{item.content}</List.Item>
          )}
        />
      </div>
    );
  }
}

Ta-da! Now we have our basic todo app working! However, there’s still more that we can do to make it better!

Getting Fancy

Our todo application is up and running. However, there is more that we need to add: most importantly, a remove button. We’ll also add a date picker, because why not?

To avoid making our <Todo /> overly complicated, I’m going to extract the <List.Item /> into its own separate component. I will also define a new method, removeTodo() to remove todo items given its index. I’ll then pass that method into our new component.

To serve as a remove button, we can use Ant’s <Icon /> component. Add it to the <List.Item />’s action prop, which takes an array of components. To see the full list of icons, see this page on the Ant Design website.

todo.js

import React from "react";
import { Input, List, Icon } from "antd";

import "antd/dist/antd.css";

export default class Todo extends React.Component {
  // --- snip ---

  removeTodo = index => {
    let newTodos = [...this.state.todos];

    // Remove element
    newTodos.splice(index, 1);

    // Decrement greater indexes
    for (let i = index; i < newTodos.length; i++) {
      newTodos[i].index -= 1;
    }

    // Update state
    this.setState({
      todos: newTodos
    });
  };

  render() {
    return (
      <div className="todoContainer">
        <h1>TODO App</h1>

        <Input
          placeholder="What needs to be done?"
          onPressEnter={this.handlePressEnter}
        />

        <List
          locale={{ emptyText: "No todo items" }}
          dataSource={this.state.todos}
          renderItem={item => (
            <TodoItem
              todo={item}
              removeTodo={this.removeTodo}
            />
          )}
        />
      </div>
    );
  }
}

class TodoItem extends React.Component {
  remove = () => {
    // Remove this TodoItem
    this.props.removeTodo(this.props.todo.index);
  };

  render() {
    return (
      <List.Item
        actions={[
          <Icon
            type="close-circle"
            theme="filled"
            onClick={this.remove}
          />
        ]}
      >
        {this.props.todo.content}
      </List.Item>
    );
  }
}

Great! Now all that’s left is to add Ant’s <DatePicker /> component. To do so, we’re going to update the handlePressEnter() method by adding date and dateString properties to the todo objects we’re storing in our state. Then we’ll need an additional method, setDate(), to update those properties.

todo.js

import React from "react";
import { Input, List, Icon, DatePicker } from "antd";

import "antd/dist/antd.css";

export default class Todo extends React.Component {
  // --- snip ---

  handlePressEnter = e => {
    // Create a todo object containing its index, content,
    // as well as an empty date
    const todo = {
      index: this.state.todos.length,
      content: e.target.value,
      date: null,
      dateString: ""
    };

    // Add the new todo to our array
    const newTodos = this.state.todos.concat(todo);

    this.setState({
      todos: newTodos
    });

    // Clear input
    e.target.value = "";
  };

  setDate = (index, date, dateString) => {
    // Set the date of the given todo
    let newTodos = [...this.state.todos];
    newTodos[index].date = date;
    newTodos[index].dateString = dateString;

    // Initialize the state
    this.setState({
      todos: newTodos
    });
  };

  render() {
    return (
      <div className="todoContainer">
        <h1>TODO App</h1>

        <Input
          placeholder="What needs to be done?"
          onPressEnter={this.handlePressEnter}
        />

        <List
          locale={{ emptyText: "No todo items" }}
          dataSource={this.state.todos}
          renderItem={item => (
            <TodoItem
              todo={item}
              removeTodo={this.removeTodo}
              setDate={this.setDate}
            />
          )}
        />
      </div>
    );
  }
}

class TodoItem extends React.Component {
  remove = () => {
    // Remove this TodoItem
    this.props.removeTodo(this.props.todo.index);
  };

  handleDateChange = (date, dateString) => {
    // Update the date when changed
    this.props.setDate(this.props.todo.index, date, dateString);
  }

  render() {
    return (
      <List.Item
        actions={[
          <DatePicker
            format="MM/DD/YYYY"
            onChange={this.handleDateChange}
            value={this.props.todo.date}
          />,
          <Icon
            type="close-circle"
            theme="filled"
            onClick={this.remove}
          />
        ]}
      >
        {this.props.todo.content}
      </List.Item>
    );
  }
}

And voilà! 🎉

We now have our fully functional todo app utilizing Ant Design! If you’d like to learn more about Ant, check out their docs.

You can find the full code for this post on CodeSandbox.

  Tweet It

🕵 Search Results

🔎 Searching...