Building a CRUD App in React with Hooks and Axios

As a web developer, one of the most fundamental patterns you‘ll encounter is CRUD – Create, Read, Update, and Delete. Nearly every application needs to create and store some kind of data, display it to users, allow for updating it, and support removing it. While the underlying implementation may vary, the core operations remain the same.

In this guide, we‘ll walk through building a simple but fully-functional CRUD application using the popular React library along with hooks for state management and Axios for making API calls to a backend server. By the end, you‘ll have a solid understanding of how the pieces fit together and be able to apply these concepts in your own projects.

What is CRUD?

CRUD stands for the four basic types of functionality for persistent storage, especially databases:

  • Create – add new entries
  • Read – fetch and display existing entries
  • Update – modify existing entries
  • Delete – remove entries

For example, in a blog application, you might have the ability to create new posts, display a list of existing posts, edit the title or content of a post, and delete old posts you no longer want. While there are many other operations an app might support, CRUD tends to make up the backbone.

Why Use React, Hooks, and Axios?

React has become one of the most popular JavaScript libraries for building interactive UIs. Its component-based architecture and declarative approach help break complex interfaces down into reusable pieces. Hooks, added in React 16.8, allow for cleaner code and more easily reusable stateful logic compared to class components.

Axios is a lightweight HTTP client that works in the browser and Node.js. It provides an easy-to-use API and has some nice features like interceptors, request/response transformation, and error handling.

By leveraging these technologies together, we can create a robust and maintainable CRUD application with a clean separation of concerns between the frontend and backend. React will handle displaying and updating the UI, hooks will manage the internal component state, and Axios will take care of sending requests to a RESTful API.

Setting Up the Project

Let‘s get started by scaffolding a new React project. The quickest way is with Create React App:

npx create-react-app react-crud-tutorial
cd react-crud-tutorial

Next, we‘ll install the dependencies we need:

npm install axios react-router-dom

Axios will handle our API requests and react-router-dom will allow us to set up routing between different pages in our app.

We‘ll also be using React Bootstrap for some pre-built UI components to save time styling. Install it with:

npm install react-bootstrap bootstrap

Make sure to import the CSS file in your src/index.js:

import ‘bootstrap/dist/css/bootstrap.min.css‘;

Creating the Components

Inside the src directory, create a new folder called components. Here we‘ll put the individual pieces that make up our CRUD interface. Let‘s start with a basic component for displaying a list of items which we‘ll call ItemList.

// src/components/ItemList.js
import React, { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;
import ListGroup from ‘react-bootstrap/ListGroup‘;

const ItemList = () => {
  const [items, setItems] = useState([])

  useEffect(() => {
    const fetchItems = async () => {
      const result = await axios(
        ‘https://jsonplaceholder.typicode.com/posts‘,
      );

      setItems(result.data);
    };

    fetchItems();
  }, []);

  return (
    <ListGroup>
      {items.map(item => (
        <ListGroup.Item key={item.id}>{item.title}</ListGroup.Item>
      ))}
    </ListGroup>
  )
}

export default ItemList

There are a few key things happening here:

  1. We import the useState and useEffect hooks from React. useState allows us to add state to functional components and useEffect lets us perform side effects like fetching data.

  2. In the component, we declare a state variable called items along with a function to update it called setItems. The initial value is an empty array.

  3. The useEffect hook is used to fetch data from an API when the component mounts. We‘ve defined an asynchronous function inside it called fetchItems that uses Axios to send a GET request to https://jsonplaceholder.typicode.com/posts. This is a fake API for testing and prototyping. The response data is then put into the component state using setItems.

  4. In the JSX being returned, we map over the items array and render a ListGroup.Item for each one displaying the post title. The key prop is important for React to efficiently update the list.

With this setup, ItemList will fetch posts from the API and display their titles in a list when mounted.

Next, let‘s create components for the other CRUD operations, starting with ItemCreate for adding new items.

// src/components/ItemCreate.js 
import React, { useState } from ‘react‘;
import axios from ‘axios‘;
import Form from ‘react-bootstrap/Form‘;
import Button from ‘react-bootstrap/Button‘;

const ItemCreate = () => {
  const [title, setTitle] = useState(‘‘);
  const [body, setBody] = useState(‘‘);

  const handleSubmit = async e => {
    e.preventDefault();

    const result = await axios.post(
      ‘https://jsonplaceholder.typicode.com/posts‘,
      {
        title,
        body,
      }
    );

    console.log(result.data);
    setTitle(‘‘);
    setBody(‘‘);
  }

  return (
    <Form onSubmit={handleSubmit}>
      <Form.Group controlId="formTitle">
        <Form.Label>Title</Form.Label>
        <Form.Control
          type="text"
          placeholder="Enter title"
          value={title}
          onChange={e => setTitle(e.target.value)}
        />
      </Form.Group>

      <Form.Group controlId="formBody">
        <Form.Label>Body</Form.Label>
        <Form.Control
          as="textarea"
          placeholder="Enter body"
          rows={3}
          value={body}
          onChange={e => setBody(e.target.value)}
        />
      </Form.Group>

      <Button variant="primary" type="submit">
        Submit
      </Button>
    </Form>
  );
}

export default ItemCreate;

The ItemCreate component renders a form with fields for entering a post title and body. When submitted, it sends a POST request to the API to create a new post with the entered data. We use the useState hook to manage the form state and the axios.post method to send the request. Upon a successful response, we log the result and clear out the form.

The process is very similar for ItemUpdate:

// src/components/ItemUpdate.js
import React, { useState, useEffect } from ‘react‘; 
import axios from ‘axios‘;
import Form from ‘react-bootstrap/Form‘;
import Button from ‘react-bootstrap/Button‘;

const ItemUpdate = ({ match }) => {
  const [title, setTitle] = useState(‘‘);
  const [body, setBody] = useState(‘‘);

  useEffect(() => {
    const fetchItem = async () => {
      const result = await axios(
        `https://jsonplaceholder.typicode.com/posts/${match.params.id}`
      );

      setTitle(result.data.title);   
      setBody(result.data.body);
    };

    fetchItem();
  }, [match]);

  const handleSubmit = async e => {
    e.preventDefault();

    const result = await axios.put(
      `https://jsonplaceholder.typicode.com/posts/${match.params.id}`,
      {
        title,
        body,
      }
    );

    console.log(result.data);
  };

  return (
    <Form onSubmit={handleSubmit}>
      <Form.Group controlId="formTitle">
        <Form.Label>Title</Form.Label>
        <Form.Control
          type="text"
          placeholder="Enter title"
          value={title}
          onChange={e => setTitle(e.target.value)}
        />
      </Form.Group>

      <Form.Group controlId="formBody">
        <Form.Label>Body</Form.Label>
        <Form.Control
          as="textarea"
          placeholder="Enter body"
          rows={3}
          value={body}
          onChange={e => setBody(e.target.value)}
        />
      </Form.Group>

      <Button variant="primary" type="submit">
        Update  
      </Button>
    </Form>
  );
};

export default ItemUpdate;

The main difference is that ItemUpdate needs to fetch the existing post data and populate the form fields with it. We do this in a useEffect hook that runs when the component mounts and whenever the post ID changes (e.g. if the user navigated directly to the update URL).

We get the ID from the URL parameter which is passed in via the match prop by React Router. Inside the useEffect, we make a GET request for the individual post data and use the response to set the title and body state values.

The form submission handler sends a PUT request to update the post with the new field values. Again, we‘re just logging the response here, but in a real app you would likely show some kind of success message and navigate the user back to the post list.

Finally, for deleting we don‘t need a separate component, we can add a handler to ItemList:

// src/components/ItemList.js

// ...

const handleDelete = async id => {
  await axios.delete(`https://jsonplaceholder.typicode.com/posts/${id}`);
  const updatedItems = items.filter(item => item.id !== id);
  setItems(updatedItems);
}

return (
  <ListGroup>
    {items.map(item => (
      <ListGroup.Item key={item.id}>
        {item.title}
        <Button 
          variant="danger"
          size="sm"
          onClick={() => handleDelete(item.id)}
        >
          Delete
        </Button>
      </ListGroup.Item>
    ))}
  </ListGroup>
)

// ...

Each list item now has a button that will delete that post when clicked. The handleDelete function sends a DELETE request to the API and then removes the deleted item from the items state array.

Adding Routing

We‘ve got components for each CRUD operation, but we need a way to navigate between them. React Router makes this easy. First, we‘ll set up the routes in App.js:

// src/App.js
import React from ‘react‘;
import { BrowserRouter as Router, Route } from ‘react-router-dom‘;
import Container from ‘react-bootstrap/Container‘;
import ItemList from ‘./components/ItemList‘;
import ItemCreate from ‘./components/ItemCreate‘;
import ItemUpdate from ‘./components/ItemUpdate‘;

const App = () => {
  return (
    <Router>
      <Container>
        <Route path="/" exact component={ItemList} />
        <Route path="/create" component={ItemCreate} />
        <Route path="/update/:id" component={ItemUpdate} />
      </Container>
    </Router>
  );
};

export default App;

Each Route maps a URL path to a component. The exact prop on the root path is necessary to ensure it doesn‘t match all paths.

To actually navigate between routes, we can use the Link component from React Router:

// src/components/ItemList.js
import { Link } from ‘react-router-dom‘; 

// ...

return (
  <>
    <Link to="/create">
      <Button variant="primary">Create</Button>
    </Link>

    <ListGroup>
      {items.map(item => (
        <ListGroup.Item key={item.id}>
          {item.title}

          <Link to={`/update/${item.id}`}>
            <Button variant="secondary" size="sm">
              Update
            </Button>
          </Link>

          <Button 
            variant="danger"
            size="sm" 
            onClick={() => handleDelete(item.id)}
          >
            Delete  
          </Button>
        </ListGroup.Item>
      ))}
    </ListGroup>
  </>  
)

// ...

Here we‘ve added a "Create" button at the top of the list that navigates to the /create route. Each list item also has an "Update" button that navigates to /update/:id with the post ID.

With that, we have a fully functional CRUD application in React! You can run it with npm start and test it out. Of course, since we‘re using a fake API, any changes won‘t persist if you refresh the page, but you should see the correct behavior and responses logged in the console.

Conclusion and Next Steps

We‘ve covered the basics of implementing CRUD functionality in a React application using hooks for state management and Axios for making API requests. You can use this as a starting point for your own projects and extend it with additional features like form validation, error handling, search/filtering, authentication and more.

Some other things to consider:

  • Extracting the Axios logic into a separate service module for better organization and reusability
  • Using a real backend API with a database to persist the data
  • Adding loading states and spinners for better UX
  • Implementing client-side routing with React Router
  • Writing tests for your components

I hope this guide has been helpful in understanding how to put together a CRUD app in React. The full code is available on GitHub. Feel free to use it as a reference and reach out with any questions!

Happy coding!

Similar Posts