React Hooks + Redux: CRUD example with Axios and Rest API

In this tutorial, I will show you how to build a React Hooks + Redux CRUD Application example to consume Rest API, display and modify data with Router, Axios & Bootstrap.

Security:
React Hooks: JWT Authentication (without Redux) example
React Hooks + Redux: JWT Authentication example

Related Posts:
React CRUD example with Axios and Web API (using React Components)
React Hooks (without Redux) CRUD example with Axios and Web API
React Hooks File Upload example with Axios & Progress Bar
React Table example: CRUD App | react-table 7
React Form Validation with Hooks example

Serverless with Firebase:
React Hooks + Firebase Realtime Database: CRUD App
React Hooks + Firestore example: CRUD app

Overview of React Hooks Redux CRUD example

We will build a React Redux Tutorial Application with Rest API calls 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 Redux CRUD Application.

– Create a Tutorial:

react-hooks-redux-crud-example-create-tutorial

– Retrieve all Tutorials:

react-hooks-redux-crud-example-retrieve-tutorial

– Click on Edit button to update a Tutorial:

react-hooks-redux-crud-example-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-hooks-redux-crud-example-update-tutorial

If you need Form Validation with React Hook Form 7, please visit:
React Form Validation with Hooks example

– Search Tutorials by title:

react-hooks-redux-crud-example-search-tutorial

– Check Redux State with Dev-tool:

react-hooks-redux-crud-example-check-redux-state

This React Client consumes the following Web API:

MethodsUrlsActions
POST/api/tutorialscreate new Tutorial
GET/api/tutorialsretrieve all Tutorials
GET/api/tutorials/:idretrieve a Tutorial by :id
PUT/api/tutorials/:idupdate a Tutorial by :id
DELETE/api/tutorials/:iddelete a Tutorial by :id
DELETE/api/tutorialsdelete 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 & MongoDb
Spring Boot & MySQL
Spring Boot & PostgreSQL
Spring Boot & MongoDB
Spring Boot & H2
Spring Boot & Cassandra
Django & MySQL
Django & PostgreSQL
Django & MongoDB

React Hooks Redux CRUD Component Diagram with Router & Axios

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

react-hooks-redux-crud-example-components

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

– Three pages that dispatch actions to Redux Thunk Middleware which uses TutorialDataService to call Rest API:

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

TutorialDataService uses axios to make HTTP requests and receive responses.

React Hooks + Redux with API example

This diagram shows how Redux elements work in our React Hooks Application:

react-hooks-redux-crud-example-redux-store-architecture

We’re gonna create Redux store for storing tutorials data. Other React Components will work with the Store via dispatching an action or getting value using React-Redux Hooks API.

The reducer will take the action and return new state.

Technology

  • React 17/16
  • react-redux 7.2.3
  • redux 4.0.5
  • redux-thunk 2.3.0
  • react-router-dom 5.2.0
  • axios 0.21.1
  • bootstrap 4

Project Structure

react-hooks-redux-crud-example-project-structure

I’m gonna explain it briefly.

package.json contains main modules: react, react-router-dom, react-redux, redux, redux-thunk, axios & bootstrap.
App is the container that has Router & navbar.
– There are 3 pages: TutorialsList, Tutorial, AddTutorial.
http-common.js initializes axios with HTTP base Url and headers.
TutorialService has methods for sending HTTP requests to the Apis.
.env configures port for this React CRUD App.

About Redux elements that we’re gonna use:
actions folder contains the action creator (tutorials.js for CRUD operations and searching).
reducers folder contains the reducer (tutorials.js) which updates the application state corresponding to dispatched action.

Setup React.js Project

Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-hooks-redux-crud

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


public

src

actions

types.js

tutorials.js (create/retrieve/update/delete actions)

reducers

index.js

tutorials.js

components

AddTutorial.js

Tutorial.js

TutorialsList.js

services

TutorialService.js

App.css

App.js

index.js

store.js

package.json


Install Bootstrap to React Hooks Redux CRUD App

Run command: npm install bootstrap.

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

import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";

function App() {
  return (
    ...
  );
}

export default App;

Add React Router to React Hooks Redux CRUD App

– Run the command: npm install react-router-dom.
– Open src/App.js and wrap all UI elements by BrowserRouter object.

...
import { BrowserRouter as Router } from "react-router-dom";

function App() {
  return (
    <Router>
      ...
    </Router>
  );
}

export default App;

Add Navbar to React Hooks Redux CRUD App

The App component is the root container for our application, it will contain a navbar inside <Router> above, and also, a Switch object with several Route. Each Route points to a React Component.

Now App.js looks like:

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

import AddTutorial from "./components/AddTutorial";
import Tutorial from "./components/Tutorial";
import TutorialsList from "./components/TutorialsList";

function App() {
  return (
    <Router>
      <nav className="navbar navbar-expand navbar-dark bg-dark">
        <a href="/tutorials" className="navbar-brand">
          bezKoder
        </a>
        <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>
    </Router>
  );
}

export default App;

Initialize Axios for React Hooks Redux CRUD HTTP Client

Let’s install axios with command: npm install axios.
Under src folder, we create http-common.js 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.
The service exports CRUD functions and finder method:

  • CREATE: create
  • RETRIEVE: getAll, get
  • UPDATE: update
  • DELETE: remove, removeAll
  • FINDER: findByTitle

services/TutorialService.js

import http from "../http-common";

const getAll = () => {
  return http.get("/tutorials");
};

const get = id => {
  return http.get(`/tutorials/${id}`);
};

const create = data => {
  return http.post("/tutorials", data);
};

const update = (id, data) => {
  return http.put(`/tutorials/${id}`, data);
};

const remove = id => {
  return http.delete(`/tutorials/${id}`);
};

const removeAll = () => {
  return http.delete(`/tutorials`);
};

const findByTitle = title => {
  return http.get(`/tutorials?title=${title}`);
};

const TutorialService = {
  getAll,
  get,
  create,
  update,
  remove,
  removeAll,
  findByTitle
};

export default TutorialService;

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

Create Redux Actions

We’re gonna create actions in src/actions folder:


actions

types.js

tutorials.js (create/retrieve/update/delete actions)


Action Types

First we defined some string constant that indicates the type of action being performed.

actions/type.js

export const CREATE_TUTORIAL = "CREATE_TUTORIAL";
export const RETRIEVE_TUTORIALS = "RETRIEVE_TUTORIALS";
export const UPDATE_TUTORIAL = "UPDATE_TUTORIAL";
export const DELETE_TUTORIAL = "DELETE_TUTORIAL";
export const DELETE_ALL_TUTORIALS = "DELETE_ALL_TUTORIALS";

Actions Creator

This is creator for actions related to tutorials. We’re gonna import TutorialDataService to make asynchronous HTTP requests with trigger dispatch on the result.

createTutorial()

  • calls the TutorialDataService.create()
  • dispatch CREATE_TUTORIAL

retrieveTutorials()

  • calls the TutorialDataService.getAll()
  • dispatch RETRIEVE_TUTORIALS

updateTutorial()

  • calls the TutorialDataService.update()
  • dispatch UPDATE_TUTORIAL

deleteTutorial()

  • calls the TutorialDataService.remove()
  • dispatch DELETE_TUTORIAL

deleteAllTutorials()

  • calls the TutorialDataService.removeAll()
  • dispatch DELETE_ALL_TUTORIALS

findTutorialsByTitle()

  • calls the TutorialDataService.findByTitle()
  • dispatch RETRIEVE_TUTORIALS

Some action creators return a Promise for Components using them.

actions/tutorials.js

import {
  CREATE_TUTORIAL,
  RETRIEVE_TUTORIALS,
  UPDATE_TUTORIAL,
  DELETE_TUTORIAL,
  DELETE_ALL_TUTORIALS,
} from "./types";

import TutorialDataService from "../services/TutorialService";

export const createTutorial = (title, description) => async (dispatch) => {
  try {
    const res = await TutorialDataService.create({ title, description });

    dispatch({
      type: CREATE_TUTORIAL,
      payload: res.data,
    });

    return Promise.resolve(res.data);
  } catch (err) {
    return Promise.reject(err);
  }
};

export const retrieveTutorials = () => async (dispatch) => {
  try {
    const res = await TutorialDataService.getAll();

    dispatch({
      type: RETRIEVE_TUTORIALS,
      payload: res.data,
    });
  } catch (err) {
    console.log(err);
  }
};

export const updateTutorial = (id, data) => async (dispatch) => {
  try {
    const res = await TutorialDataService.update(id, data);

    dispatch({
      type: UPDATE_TUTORIAL,
      payload: data,
    });

    return Promise.resolve(res.data);
  } catch (err) {
    return Promise.reject(err);
  }
};

export const deleteTutorial = (id) => async (dispatch) => {
  try {
    await TutorialDataService.remove(id);

    dispatch({
      type: DELETE_TUTORIAL,
      payload: { id },
    });
  } catch (err) {
    console.log(err);
  }
};

export const deleteAllTutorials = () => async (dispatch) => {
  try {
    const res = await TutorialDataService.removeAll();

    dispatch({
      type: DELETE_ALL_TUTORIALS,
      payload: res.data,
    });

    return Promise.resolve(res.data);
  } catch (err) {
    return Promise.reject(err);
  }
};

export const findTutorialsByTitle = (title) => async (dispatch) => {
  try {
    const res = await TutorialDataService.findByTitle(title);

    dispatch({
      type: RETRIEVE_TUTORIALS,
      payload: res.data,
    });
  } catch (err) {
    console.log(err);
  }
};

Create Redux Reducer

There will be a reducer in src/reducers folder, the reducer updates the state corresponding to dispatched Redux actions.


reducers

index.js

tutorials.js


Tutorials Reducer

The tutorials reducer will update tutorials state of the Redux store:
reducers/tutorials.js

import {
  CREATE_TUTORIAL,
  RETRIEVE_TUTORIALS,
  UPDATE_TUTORIAL,
  DELETE_TUTORIAL,
  DELETE_ALL_TUTORIALS,
} from "../actions/types";

const initialState = [];

function tutorialReducer(tutorials = initialState, action) {
  const { type, payload } = action;

  switch (type) {
    case CREATE_TUTORIAL:
      return [...tutorials, payload];

    case RETRIEVE_TUTORIALS:
      return payload;

    case UPDATE_TUTORIAL:
      return tutorials.map((tutorial) => {
        if (tutorial.id === payload.id) {
          return {
            ...tutorial,
            ...payload,
          };
        } else {
          return tutorial;
        }
      });

    case DELETE_TUTORIAL:
      return tutorials.filter(({ id }) => id !== payload.id);

    case DELETE_ALL_TUTORIALS:
      return [];

    default:
      return tutorials;
  }
};

export default tutorialReducer;

Combine Reducers

reducers/index.js

import { combineReducers } from "redux";
import tutorials from "./tutorials";

export default combineReducers({
  tutorials,
});

Because we only have a single store in a Redux application. We use reducer composition instead of many stores to split data handling logic.

For example, if you have Auth Reducer that manages authentication logic, you can use combineReducers() like following code:

import { combineReducers } from "redux";
import tutorials from "./tutorials";
import auth from "./auth";

export default combineReducers({
  tutorials,
  auth
});

Create Redux Store

This Store will bring Actions and Reducers together and hold the Application state.

Now we need to install Redux, Thunk Middleware and Redux Devtool Extension.
Run the command:

npm install redux react-redux redux-thunk
npm install --save-dev redux-devtools-extension

In the previous section, we used combineReducers() to combine 2 reducers into one. Let’s import it, and pass it to createStore():

store.js

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const initialState = {};

const middleware = [thunk];

const store = createStore(
  rootReducer,
  initialState,
  composeWithDevTools(applyMiddleware(...middleware))
);

export default store;

Provide State to React Components

We start by wrapping our entire application in a <Provider> component to make the store available to its child components.

Open src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
...
import { Provider } from 'react-redux';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Create React Components

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

Add Object

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

react-hooks-redux-crud-example-create-tutorial

components/AddTutorial.js

import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { createTutorial } from "../actions/tutorials";

const AddTutorial = () => {
  const initialTutorialState = {
    id: null,
    title: "",
    description: "",
    published: false
  };
  const [tutorial, setTutorial] = useState(initialTutorialState);
  const [submitted, setSubmitted] = useState(false);

  const dispatch = useDispatch();

  const handleInputChange = event => {
    const { name, value } = event.target;
    setTutorial({ ...tutorial, [name]: value });
  };

  const saveTutorial = () => {
    const { title, description } = tutorial;

    dispatch(createTutorial(title, description))
      .then(data => {
        setTutorial({
          id: data.id,
          title: data.title,
          description: data.description,
          published: data.published
        });
        setSubmitted(true);

        console.log(data);
      })
      .catch(e => {
        console.log(e);
      });
  };

  const newTutorial = () => {
    setTutorial(initialTutorialState);
    setSubmitted(false);
  };

  return (
    ...
  );
};

export default AddTutorial;

First, we define and set initial state: tutorial & submitted.

Next, we create handleInputChange() function to track the values of the input and set that state for changes.

We also have a function to get tutorial local state and send the POST request to the Web API. It dispatchs action with createTutorial() action creator with useDispatch(). This hook returns a reference to the dispatch function from the Redux store.


const dispatch = useDispatch();

dispatch(createTutorial(...));

For return, we check the submitted state, if it is true, we show Add button for creating new Tutorial again. Otherwise, a Form with Submit button will display.

...
const AddTutorial = () => {
  ...

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

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

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

export default AddTutorial;

List of Objects

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-hooks-redux-crud-example-retrieve-tutorial

Beside global state tutorials, we also have following local state:

  • searchTitle
  • currentTutorial and currentIndex

We also need to use 3 action creators:

  • retrieveTutorials
  • findTutorialsByTitle
  • deleteAllTutorials

To connect the Redux store with local Component state and props, we use useSelector() and useDispatch().


const tutorials = useSelector(state => state.tutorials);
const dispatch = useDispatch();

When using useSelector with an inline selector above, a new instance of the selector is created whenever the component is rendered.

Now we can work with tutorials state and dispatch actions like this:

// use state
tutorials.map(...);

// dispatch actions
dispatch(retrieveTutorials());
dispatch(findTutorialsByTitle(...));
dispatch(deleteAllTutorials(...));

components/TutorialsList.js

import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
  retrieveTutorials,
  findTutorialsByTitle,
  deleteAllTutorials,
} from "../actions/tutorials";

const TutorialsList = () => {
  const [currentTutorial, setCurrentTutorial] = useState(null);
  const [currentIndex, setCurrentIndex] = useState(-1);
  const [searchTitle, setSearchTitle] = useState("");

  const tutorials = useSelector(state => state.tutorials);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(retrieveTutorials());
  }, []);

  const onChangeSearchTitle = e => {
    const searchTitle = e.target.value;
    setSearchTitle(searchTitle);
  };

  const refreshData = () => {
    setCurrentTutorial(null);
    setCurrentIndex(-1);
  };

  const setActiveTutorial = (tutorial, index) => {
    setCurrentTutorial(tutorial);
    setCurrentIndex(index);
  };

  const removeAllTutorials = () => {
    dispatch(deleteAllTutorials())
      .then(response => {
        console.log(response);
        refreshData();
      })
      .catch(e => {
        console.log(e);
      });
  };

  const findByTitle = () => {
    refreshData();
    dispatch(findTutorialsByTitle(searchTitle));
  };

  return (
    ...
  );
};

export default TutorialsList;

Let’s continue to implement UI elements:

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

const TutorialsList = () => {
  ...

  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={onChangeSearchTitle}
          />
          <div className="input-group-append">
            <button
              className="btn btn-outline-secondary"
              type="button"
              onClick={findByTitle}
            >
              Search
            </button>
          </div>
        </div>
      </div>
      <div className="col-md-6">
        <h4>Tutorials List</h4>

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

        <button
          className="m-3 btn btn-sm btn-danger"
          onClick={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>
  );
};

export default TutorialsList;

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.

Object details

react-hooks-redux-crud-example-retrieve-one-tutorial

This component will use TutorialDataService.get() method in the Effect Hook useEffect() to get Tutorial by id in the URL.

For update, delete the Tutorial, we work with following action creators:

  • updateTutorial
  • deleteTutorial

components/Tutorial.js

import React, { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { updateTutorial, deleteTutorial } from "../actions/tutorials";
import TutorialDataService from "../services/TutorialService";

const Tutorial = (props) => {
  const initialTutorialState = {
    id: null,
    title: "",
    description: "",
    published: false
  };
  const [currentTutorial, setCurrentTutorial] = useState(initialTutorialState);
  const [message, setMessage] = useState("");

  const dispatch = useDispatch();

  const getTutorial = id => {
    TutorialDataService.get(id)
      .then(response => {
        setCurrentTutorial(response.data);
        console.log(response.data);
      })
      .catch(e => {
        console.log(e);
      });
  };

  useEffect(() => {
    getTutorial(props.match.params.id);
  }, [props.match.params.id]);

  const handleInputChange = event => {
    const { name, value } = event.target;
    setCurrentTutorial({ ...currentTutorial, [name]: value });
  };

  const updateStatus = status => {
    const data = {
      id: currentTutorial.id,
      title: currentTutorial.title,
      description: currentTutorial.description,
      published: status
    };

    dispatch(updateTutorial(currentTutorial.id, data))
      .then(response => {
        console.log(response);

        setCurrentTutorial({ ...currentTutorial, published: status });
        setMessage("The status was updated successfully!");
      })
      .catch(e => {
        console.log(e);
      });
  };

  const updateContent = () => {
    dispatch(updateTutorial(currentTutorial.id, currentTutorial))
      .then(response => {
        console.log(response);

        setMessage("The tutorial was updated successfully!");
      })
      .catch(e => {
        console.log(e);
      });
  };

  const removeTutorial = () => {
    dispatch(deleteTutorial(currentTutorial.id))
      .then(() => {
        props.history.push("/tutorials");
      })
      .catch(e => {
        console.log(e);
      });
  };

  return (
    ...
  );
};

export default Tutorial;

And this is the code inside return:

...

const Tutorial = (props) => {
  ...

  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"
                name="title"
                value={currentTutorial.title}
                onChange={handleInputChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="description">Description</label>
              <input
                type="text"
                className="form-control"
                id="description"
                name="description"
                value={currentTutorial.description}
                onChange={handleInputChange}
              />
            </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={() => updateStatus(false)}
            >
              UnPublish
            </button>
          ) : (
            <button
              className="badge badge-primary mr-2"
              onClick={() => updateStatus(true)}
            >
              Publish
            </button>
          )}

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

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

export default Tutorial;

Add CSS style for React Components

Open src/App.css and write 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 Redux CRUD with Web API

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 Hooks Redux CRUD App

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 & MongoDb
Spring Boot & MySQL
Spring Boot & PostgreSQL
Spring Boot & MongoDB
Spring Boot & H2
Spring Boot & Cassandra
Python/Django & MySQL
Python/Django & PostgreSQL
Python/Django & MongoDB

Conclusion

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

If you don’t want to use Redux:
React Hooks (without Redux) CRUD example with Axios and Web API

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

Uou can add Pagination Component:
React Pagination using Hooks example

react-pagination-hooks-material-ui-change-page

Or Form Validation:
React Form Validation with Hooks example

Happy learning, see you again!

Further Reading

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

Fullstack:
Spring Boot + React Redux example: Build a CRUD App
React + Spring Boot + MySQL: CRUD example
React + Spring Boot + PostgreSQL: CRUD example
React + Spring Boot + MongoDB: CRUD example
React + Node.js + Express + MySQL: CRUD example
React Redux + Node.js + Express + MySQL: CRUD example
React + Node.js + Express + PostgreSQL example
React + Node.js + Express + MongoDB example
React + Django + Rest Framework example

Source Code

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

3 thoughts to “React Hooks + Redux: CRUD example with Axios and Rest API”

  1. There is warnings on src\components\TutorialsList.js
    warning message: Line 20:6: React Hook useEffect has a missing dependency: ‘dispatch’. Either include it or remove the dependency array react-hooks/exhaustive-deps

    how to fix it?

    1. solution

        const initFetch = useCallback(() => {
          dispatch(retrieveTutorials());
        }, [dispatch])
        useEffect(() => {
          initFetch()
        }, [initFetch])
      

Leave a Reply

Your email address will not be published. Required fields are marked *