React Typescript example Project with Axios and Web API

In this tutorial, I will show you how to build a React Typescript example Project with Axios consume Web API, display and modify data with Router & Bootstrap.

Related Posts:
React JWT Authentication (without Redux) example
React Redux: JWT Authentication example
React Typescript File Upload example

– Hooks version: React Hooks Typescript example Project with Axios and Web API
React Custom Hook in Typescript example

Serverless with Firebase:
React Typescript Firebase CRUD with Realtime Database
React Typescript Firestore CRUD example with Cloud Firestore

Overview of React Typescript example Project

We will build a React Tutorial Application with Axios and Web API in that:

  • Each Tutorial has id, title, description, published status.
  • We can create, retrieve, update, delete Tutorials.
  • There is a Search bar for finding Tutorials by title.

Here are screenshots of our React CRUD Application.

– Create a Tutorial:

react-typescript-project-axios-crud-create-tutorial

– Retrieve all Tutorials:

react-typescript-project-axios-crud-retrieve-tutorial

– Click on Edit button to update an item:

react-typescript-project-axios-crud-retrieve-one-tutorial

On this Page, you can:

  • change status to Published using Publish button
  • delete the item using Delete button
  • update the item details with Update button

react-typescript-project-axios-crud-update-tutorial

– Search Tutorials by title:

react-typescript-project-axios-crud-search-tutorial

This React Client consumes the following Web API:

Methods Urls Actions
POST /api/tutorials create new Tutorial
GET /api/tutorials retrieve all Tutorials
GET /api/tutorials/:id retrieve a Tutorial by :id
PUT /api/tutorials/:id update a Tutorial by :id
DELETE /api/tutorials/:id delete a Tutorial by :id
DELETE /api/tutorials delete all Tutorials
GET /api/tutorials?title=[keyword] find all Tutorials which title contains keyword

You can find step by step to build a Server like this in one of these posts:
Express, Sequelize & MySQL
Express, Sequelize & PostgreSQL
Express, Sequelize & SQL Server
Express & MongoDb
Spring Boot & MySQL
Spring Boot & PostgreSQL
Spring Boot & MongoDB
Spring Boot & SQL Server
Spring Boot & H2
Spring Boot & Cassandra
Spring Boot & Oracle
Django & MySQL
Django & PostgreSQL
Django & MongoDB

React Typescript App Component Diagram with Router & Axios

Now look at the React components that we’re gonna implement:

react-typescript-project-axios-crud-components

– The App component is a container with React Router. It has navbar that links to routes paths.

TutorialsList component gets and displays Tutorials.
Tutorial component has form for editing Tutorial’s details based on :id.
AddTutorial component has form for submission new Tutorial.

– These Components call TutorialDataService methods which use axios to make HTTP requests and receive responses.

Technology

  • React 17/16
  • typescript 4.3.5
  • react-router-dom 5
  • axios 0.24.0
  • bootstrap 4.6.0

Project Structure

react-typescript-project-axios-crud-project-structure

I’m gonna explain it briefly.

package.json contains 5 main modules: react, typescript, react-router-dom, axios & bootstrap.
App is the container that has Router & navbar.
tutorial.type.ts exports ITutorialData interface.
– There are 3 components: TutorialsList, Tutorial, AddTutorial.
http-common.ts initializes axios with HTTP base Url and headers.
TutorialDataService has methods for sending HTTP requests to the Apis.
.env configures port for this React CRUD App.

Setup React Typescript Project

Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-axios-typescript-example --template typescript

After the process is done. We create additional folders and files like the following tree:


public

src

components

add-tutorial.component.tsx

tutorial.component.tsx

tutorials-list.component.tsx

services

tutorial.service.ts

types

tutorial.type.ts

App.css

App.tsx

index.tsx

package.json


Import Bootstrap to React Typescript Project

Run command: npm install [email protected].

Open src/App.tsx and modify the code inside it as following-

import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";

class App extends Component {
  render() {
    // ...
  }
}

export default App;

Add React Router to React Typescript Project

When using Typescript with React.js, we don’t use Proptypes. Typescript is stronger than Propstypes.

npm has many dependencies with prefix @types/{name} such as @types/lodash, @types/react… which is easy to install and use. For this project, we use @types/react-router-dom.

– Run the command: npm install @types/react-router-dom.
– Open src/index.tsx and wrap App component by BrowserRouter object.

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from "react-router-dom";

import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

reportWebVitals();

Add Navbar to React Typescript Project

Open src/App.tsx, this App component is the root container for our application, it will contain a navbar, and also, a Switch object with several Route. Each Route points to a React Component.

import React, { Component } from "react";
import { Switch, Route, Link } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";

import AddTutorial from "./components/add-tutorial.component";
import Tutorial from "./components/tutorial.component";
import TutorialsList from "./components/tutorials-list.component";

class App extends Component {
  render() {
    return (
      <div>
        <nav className="navbar navbar-expand navbar-dark bg-dark">
          <Link to={"/tutorials"} className="navbar-brand">
            bezKoder
          </Link>
          <div className="navbar-nav mr-auto">
            <li className="nav-item">
              <Link to={"/tutorials"} className="nav-link">
                Tutorials
              </Link>
            </li>
            <li className="nav-item">
              <Link to={"/add"} className="nav-link">
                Add
              </Link>
            </li>
          </div>
        </nav>

        <div className="container mt-3">
          <Switch>
            <Route exact path={["/", "/tutorials"]} component={TutorialsList} />
            <Route exact path="/add" component={AddTutorial} />
            <Route path="/tutorials/:id" component={Tutorial} />
          </Switch>
        </div>
      </div>
    );
  }
}

export default App;

Define Data Type

Now we need to define the data type for Tutorial. Create and export ITutorialData interface in types/tutorial.type.ts.

export default interface ITutorialData {
  id?: any | null,
  title: string,
  description: string,
  published?: boolean,
}

Initialize Axios for React Typescript Project

Let’s install axios with command: npm install axios.
Under src folder, we create http-common.ts file with following code:

import axios from "axios";

export default axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-type": "application/json"
  }
});

You can change the baseURL that depends on REST APIs url that your Server configures.

For more details about ways to use Axios, please visit:
Axios request: Get/Post/Put/Delete example

Create Data Service

In this step, we’re gonna create a service that uses axios object above to send HTTP requests.

services/tutorial.service.ts

import http from "../http-common";
import ITutorialData from "../types/tutorial.type"

class TutorialDataService {
  getAll() {
    return http.get<Array<ITutorialData>>("/tutorials");
  }

  get(id: string) {
    return http.get<ITutorialData>(`/tutorials/${id}`);
  }

  create(data: ITutorialData) {
    return http.post<ITutorialData>("/tutorials", data);
  }

  update(data: ITutorialData, id: any) {
    return http.put<any>(`/tutorials/${id}`, data);
  }

  delete(id: any) {
    return http.delete<any>(`/tutorials/${id}`);
  }

  deleteAll() {
    return http.delete<any>(`/tutorials`);
  }

  findByTitle(title: string) {
    return http.get<Array<ITutorialData>>(`/tutorials?title=${title}`);
  }
}

export default new TutorialDataService();

We call axios get, post, put, delete method corresponding to HTTP Requests: GET, POST, PUT, DELETE to make CRUD Operations.

You can simplify import statement with:
Absolute Import in React

Create React Typescript Components

Now we’re gonna build 3 components corresponding to 3 Routes defined before.

Add item Component

This component has a Form to submit new Tutorial with 2 fields: title & description.

react-typescript-project-axios-crud-create-tutorial

components/add-tutorial.component.tsx

import { Component, ChangeEvent } from "react";
import TutorialDataService from "../services/tutorial.service";
import ITutorialData from '../types/tutorial.type';

type Props = {};

type State = ITutorialData & {
  submitted: boolean
};

export default class AddTutorial extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.onChangeTitle = this.onChangeTitle.bind(this);
    this.onChangeDescription = this.onChangeDescription.bind(this);
    this.saveTutorial = this.saveTutorial.bind(this);
    this.newTutorial = this.newTutorial.bind(this);

    this.state = {
      id: null,
      title: "",
      description: "",
      published: false,
      submitted: false
    };
  }

  onChangeTitle(e: ChangeEvent<HTMLInputElement>) {
    this.setState({
      title: e.target.value
    });
  }

  onChangeDescription(e: ChangeEvent<HTMLInputElement>) {
    this.setState({
      description: e.target.value
    });
  }

  saveTutorial() {
    const data: ITutorialData = {
      title: this.state.title,
      description: this.state.description
    };

    TutorialDataService.create(data)
      .then((response: any) => {
        this.setState({
          id: response.data.id,
          title: response.data.title,
          description: response.data.description,
          published: response.data.published,
          submitted: true
        });
        console.log(response.data);
      })
      .catch((e: Error) => {
        console.log(e);
      });
  }

  newTutorial() {
    this.setState({
      id: null,
      title: "",
      description: "",
      published: false,
      submitted: false
    });
  }

  render() {
    // ...
  }
}

First, we define the constructor and set initial state, bind this to the different events.

Because there are 2 fields, so we create 2 functions to track the values of the input and set that state for changes. We also have a function to get value of the form (state) and send the POST request to the Web API. It calls TutorialDataService.create() method.

For render() method, we check the submitted state, if it is true, we show Add button for creating new Tutorial again. Otherwise, a Form will display.

...
export default class AddTutorial extends Component<Props, State> {
  // ...

  render() {
    const { submitted, title, description } = this.state;

    return (
      <div className="submit-form">
        {submitted ? (
          <div>
            <h4>You submitted successfully!</h4>
            <button className="btn btn-success" onClick={this.newTutorial}>
              Add
            </button>
          </div>
        ) : (
          <div>
            <div className="form-group">
              <label htmlFor="title">Title</label>
              <input
                type="text"
                className="form-control"
                id="title"
                required
                value={title}
                onChange={this.onChangeTitle}
                name="title"
              />
            </div>

            <div className="form-group">
              <label htmlFor="description">Description</label>
              <input
                type="text"
                className="form-control"
                id="description"
                required
                value={description}
                onChange={this.onChangeDescription}
                name="description"
              />
            </div>

            <button onClick={this.saveTutorial} className="btn btn-success">
              Submit
            </button>
          </div>
        )}
      </div>
    );
  }
}

List of items Component

This component has:

  • a search bar for finding Tutorials by title.
  • a tutorials array displayed as a list on the left.
  • a selected Tutorial which is shown on the right.

react-typescript-project-axios-crud-retrieve-tutorial

So we will have following state:

  • searchTitle
  • tutorials
  • currentTutorial and currentIndex

We also need to use 3 TutorialDataService methods:

  • getAll()
  • deleteAll()
  • findByTitle()

components/tutorials-list.component.tsx

import { Component, ChangeEvent } from "react";
import TutorialDataService from "../services/tutorial.service";
import { Link } from "react-router-dom";
import ITutorialData from '../types/tutorial.type';

type Props = {};

type State = {
  tutorials: Array<ITutorialData>,
  currentTutorial: ITutorialData | null,
  currentIndex: number,
  searchTitle: string
};

export default class TutorialsList extends Component<Props, State>{
  constructor(props: Props) {
    super(props);
    this.onChangeSearchTitle = this.onChangeSearchTitle.bind(this);
    this.retrieveTutorials = this.retrieveTutorials.bind(this);
    this.refreshList = this.refreshList.bind(this);
    this.setActiveTutorial = this.setActiveTutorial.bind(this);
    this.removeAllTutorials = this.removeAllTutorials.bind(this);
    this.searchTitle = this.searchTitle.bind(this);

    this.state = {
      tutorials: [],
      currentTutorial: null,
      currentIndex: -1,
      searchTitle: ""
    };
  }

  componentDidMount() {
    this.retrieveTutorials();
  }

  onChangeSearchTitle(e: ChangeEvent<HTMLInputElement>) {
    const searchTitle = e.target.value;

    this.setState({
      searchTitle: searchTitle
    });
  }

  retrieveTutorials() {
    TutorialDataService.getAll()
      .then((response: any) => {
        this.setState({
          tutorials: response.data
        });
        console.log(response.data);
      })
      .catch((e: Error) => {
        console.log(e);
      });
  }

  refreshList() {
    this.retrieveTutorials();
    this.setState({
      currentTutorial: null,
      currentIndex: -1
    });
  }

  setActiveTutorial(tutorial: ITutorialData, index: number) {
    this.setState({
      currentTutorial: tutorial,
      currentIndex: index
    });
  }

  removeAllTutorials() {
    TutorialDataService.deleteAll()
      .then((response: any) => {
        console.log(response.data);
        this.refreshList();
      })
      .catch((e: Error) => {
        console.log(e);
      });
  }

  searchTitle() {
    this.setState({
      currentTutorial: null,
      currentIndex: -1
    });

    TutorialDataService.findByTitle(this.state.searchTitle)
      .then((response: any) => {
        this.setState({
          tutorials: response.data
        });
        console.log(response.data);
      })
      .catch((e: Error) => {
        console.log(e);
      });
  }

  render() {
    // ...
  }
}

Let’s continue to implement render() method:

// ...
import { Link } from "react-router-dom";

export default class TutorialsList extends Component<Props, State>{
  // ...

  render() {
    const { searchTitle, tutorials, currentTutorial, currentIndex } = this.state;

    return (
      <div className="list row">
        <div className="col-md-8">
          <div className="input-group mb-3">
            <input
              type="text"
              className="form-control"
              placeholder="Search by title"
              value={searchTitle}
              onChange={this.onChangeSearchTitle}
            />
            <div className="input-group-append">
              <button
                className="btn btn-outline-secondary"
                type="button"
                onClick={this.searchTitle}
              >
                Search
              </button>
            </div>
          </div>
        </div>
        <div className="col-md-6">
          <h4>Tutorials List</h4>

          <ul className="list-group">
            {tutorials &&
              tutorials.map((tutorial: ITutorialData, index: number) => (
                <li
                  className={
                    "list-group-item " +
                    (index === currentIndex ? "active" : "")
                  }
                  onClick={() => this.setActiveTutorial(tutorial, index)}
                  key={index}
                >
                  {tutorial.title}
                </li>
              ))}
          </ul>

          <button
            className="m-3 btn btn-sm btn-danger"
            onClick={this.removeAllTutorials}
          >
            Remove All
          </button>
        </div>
        <div className="col-md-6">
          {currentTutorial ? (
            <div>
              <h4>Tutorial</h4>
              <div>
                <label>
                  <strong>Title:</strong>
                </label>{" "}
                {currentTutorial.title}
              </div>
              <div>
                <label>
                  <strong>Description:</strong>
                </label>{" "}
                {currentTutorial.description}
              </div>
              <div>
                <label>
                  <strong>Status:</strong>
                </label>{" "}
                {currentTutorial.published ? "Published" : "Pending"}
              </div>

              <Link
                to={"/tutorials/" + currentTutorial.id}
                className="badge badge-warning"
              >
                Edit
              </Link>
            </div>
          ) : (
            <div>
              <br />
              <p>Please click on a Tutorial...</p>
            </div>
          )}
        </div>
      </div>
    );
  }
}

If you click on Edit button of any Tutorial, the app will direct you to Tutorial page.
We use React Router Link for accessing that page with url: /tutorials/:id.

You can add Pagination to this Component, just follow instruction in the post:
React Pagination with API using Material-UI

Item details Component

We’re gonna use the component lifecycle method: componentDidMount() to fetch the data from the Web API.

react-typescript-project-axios-crud-retrieve-one-tutorial

For getting data & update, delete the Tutorial, this component will use 3 TutorialDataService methods:

  • get()
  • update()
  • delete()

components/tutorial.component.tsx

import { Component, ChangeEvent } from "react";
import { RouteComponentProps } from 'react-router-dom';

import TutorialDataService from "../services/tutorial.service";
import ITutorialData from "../types/tutorial.type";

interface RouterProps { // type for `match.params`
  id: string; // must be type `string` since value comes from the URL
}

type Props = RouteComponentProps<RouterProps>;

type State = {
  currentTutorial: ITutorialData;
  message: string;
}

export default class Tutorial extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.onChangeTitle = this.onChangeTitle.bind(this);
    this.onChangeDescription = this.onChangeDescription.bind(this);
    this.getTutorial = this.getTutorial.bind(this);
    this.updatePublished = this.updatePublished.bind(this);
    this.updateTutorial = this.updateTutorial.bind(this);
    this.deleteTutorial = this.deleteTutorial.bind(this);

    this.state = {
      currentTutorial: {
        id: null,
        title: "",
        description: "",
        published: false,
      },
      message: "",
    };
  }

  componentDidMount() {
    this.getTutorial(this.props.match.params.id);
  }

  onChangeTitle(e: ChangeEvent<HTMLInputElement>) {
    const title = e.target.value;

    this.setState(function (prevState) {
      return {
        currentTutorial: {
          ...prevState.currentTutorial,
          title: title,
        },
      };
    });
  }

  onChangeDescription(e: ChangeEvent<HTMLInputElement>) {
    const description = e.target.value;

    this.setState((prevState) => ({
      currentTutorial: {
        ...prevState.currentTutorial,
        description: description,
      },
    }));
  }

  getTutorial(id: string) {
    TutorialDataService.get(id)
      .then((response: any) => {
        this.setState({
          currentTutorial: response.data,
        });
        console.log(response.data);
      })
      .catch((e: Error) => {
        console.log(e);
      });
  }

  updatePublished(status: boolean) {
    const data: ITutorialData = {
      id: this.state.currentTutorial.id,
      title: this.state.currentTutorial.title,
      description: this.state.currentTutorial.description,
      published: status,
    };

    TutorialDataService.update(data, this.state.currentTutorial.id)
      .then((response: any) => {
        this.setState((prevState) => ({
          currentTutorial: {
            ...prevState.currentTutorial,
            published: status,
          },
          message: "The status was updated successfully!"
        }));
        console.log(response.data);
      })
      .catch((e: Error) => {
        console.log(e);
      });
  }

  updateTutorial() {
    TutorialDataService.update(
      this.state.currentTutorial,
      this.state.currentTutorial.id
    )
      .then((response: any) => {
        console.log(response.data);
        this.setState({
          message: "The tutorial was updated successfully!",
        });
      })
      .catch((e: Error) => {
        console.log(e);
      });
  }

  deleteTutorial() {
    TutorialDataService.delete(this.state.currentTutorial.id)
      .then((response: any) => {
        console.log(response.data);
        this.props.history.push("/tutorials");
      })
      .catch((e: Error) => {
        console.log(e);
      });
  }

  render() {
    // ...
  }
}

And this is the code for render() method:

...
export default class Tutorial extends Component<Props, State> {
  // ...

  render() {
    const { currentTutorial } = this.state;

    return (
      <div>
        {currentTutorial ? (
          <div className="edit-form">
            <h4>Tutorial</h4>
            <form>
              <div className="form-group">
                <label htmlFor="title">Title</label>
                <input
                  type="text"
                  className="form-control"
                  id="title"
                  value={currentTutorial.title}
                  onChange={this.onChangeTitle}
                />
              </div>
              <div className="form-group">
                <label htmlFor="description">Description</label>
                <input
                  type="text"
                  className="form-control"
                  id="description"
                  value={currentTutorial.description}
                  onChange={this.onChangeDescription}
                />
              </div>

              <div className="form-group">
                <label>
                  <strong>Status:</strong>
                </label>
                {currentTutorial.published ? "Published" : "Pending"}
              </div>
            </form>

            {currentTutorial.published ? (
              <button
                className="badge badge-primary mr-2"
                onClick={() => this.updatePublished(false)}
              >
                UnPublish
              </button>
            ) : (
              <button
                className="badge badge-primary mr-2"
                onClick={() => this.updatePublished(true)}
              >
                Publish
              </button>
            )}

            <button
              className="badge badge-danger mr-2"
              onClick={this.deleteTutorial}
            >
              Delete
            </button>

            <button
              type="submit"
              className="badge badge-success"
              onClick={this.updateTutorial}
            >
              Update
            </button>
            <p>{this.state.message}</p>
          </div>
        ) : (
          <div>
            <br />
            <p>Please click on a Tutorial...</p>
          </div>
        )}
      </div>
    );
  }
}

Add CSS style for React Typescript Components

Open src/App.css and write some CSS code as following:

.list {
  text-align: left;
  max-width: 750px;
  margin: auto;
}

.submit-form {
  max-width: 300px;
  margin: auto;
}

.edit-form {
  max-width: 300px;
  margin: auto;
}

Configure Port for React Typescript API call

Because most of HTTP Server use CORS configuration that accepts resource sharing retrictted to some sites or ports, so we also need to configure port for our App.

In project folder, create .env file with following content:

PORT=8081

Now we’ve set our app running at port 8081.

Run React Typescript example Project

You can run our App with command: npm start.
If the process is successful, open Browser with Url: http://localhost:8081/ and check it.

This React Client will work well with following back-end Rest APIs:
Express, Sequelize & MySQL
Express, Sequelize & PostgreSQL
Express, Sequelize & SQL Server
Express & MongoDb
Spring Boot & MySQL
Spring Boot & PostgreSQL
Spring Boot & MongoDB
Spring Boot & SQL Server
Spring Boot & H2
Spring Boot & Cassandra
Spring Boot & Oracle
Python/Django & MySQL
Python/Django & PostgreSQL
Python/Django & MongoDB

Conclusion

Today we’ve built a React CRUD Application successfully with React Router & Axios. Now we can consume REST APIs, display, search and modify data in a clean way. I hope you can make API call (GET/POST/PUT/DELETE) in your project at ease.

If you want to make the example use React Hooks, you can find it here:
React Hooks CRUD example with Axios and Web API

Implement Security:
React JWT Authentication (without Redux) example
React Redux: JWT Authentication example

Or you can add Pagination Component:
React Pagination with API using Material-UI

react-pagination-with-api-material-ui-change-page

Happy learning, see you again!

Further Reading

For more details about ways to use Axios, please visit:
Axios request: Get/Post/Put/Delete example

Serverless with Firebase:
React Typescript Firebase CRUD with Realtime Database
React Typescript Firestore CRUD example with Cloud Firestore

Using Material UI instead of Bootstrap:
React Material UI examples with a CRUD Application

Upload:
React Typescript File Upload example

Fullstack:
React Typescript + Spring Boot + H2: CRUD example
React + Spring Boot + MySQL: CRUD example
React + Spring Boot + PostgreSQL: CRUD example
React + Spring Boot + MongoDB: CRUD example
React + Node + Express + MySQL: CRUD example
React + Node + Express + PostgreSQL example
React + Node + Express + MongoDB example
React + Django + Rest Framework example

Integration:
Integrate React with Spring Boot
Integrate React with Node.js Express

Source Code

You can find the complete source code for this tutorial on Github.

Hooks version: React Hooks Typescript example Project

4 thoughts to “React Typescript example Project with Axios and Web API”

Comments are closed to reduce spam. If you have any question, please send me an email.