Redux-Toolkit example with CRUD Application

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

Related Posts:
React File Upload with Axios and Progress Bar to Rest API
React Redux: JWT Authentication example (without Redux-Toolkit)
React JWT Authentication (without Redux) example

Serverless:
React Firebase CRUD with Realtime Database
React Firestore CRUD App example | Firebase Cloud Firestore

React Hooks version:
Redux-Toolkit CRUD example with React Hooks

Overview of Redux-Toolkit example with Rest API

We will build a React Redux Tutorial Application with 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:

redux-toolkit-example-crud-app-create-tutorial

– Retrieve all Tutorials:

redux-toolkit-example-crud-app-retrieve-tutorial

– Click on Edit button to update an item:

redux-toolkit-example-crud-app-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

redux-toolkit-example-crud-app-update-tutorial

– Search Tutorials by title:

redux-toolkit-example-crud-app-search-tutorial

– Redux Store:

redux-toolkit-example-crud-app-redux-store

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, 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

Redux-Toolkit example Component Diagram with Router & Axios

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

redux-toolkit-example-crud-app-components

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

– Three components that call async Thunks (that will take care of dispatching the right actions) which uses TutorialDataService to call Rest API.

  • 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.

TutorialDataService uses axios to make HTTP requests and receive responses.

Redux-Toolkit with API example

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

redux-toolkit-example-crud-app-redux-store-architecture

We’re gonna create Redux store for storing tutorials data. Other React Components will work with the Store via calling async Thunks.

The reducer will take the action and return new state. The reducer for a specific section of the Redux app state is called a “slice reducer”.

Technology

  • React 17/16
  • react-redux 7.2.5
  • redux-toolkit 1.6.1
  • react-router-dom 5.3.0
  • axios 0.21.4
  • bootstrap 4

Project Structure

redux-toolkit-example-crud-app-project-structure

I’m gonna explain it briefly.

package.json contains main modules: react, react-router-dom, react-redux, redux-toolkit, axios & bootstrap.
App is the container that has Router & navbar.
– There are 3 components: TutorialsList, Tutorial, AddTutorial.
http-common.js 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.

About Redux elements that we’re gonna use:
store.js is where we create the Redux store instance with tutorials reducer. Each reducer updates a different part of the application state corresponding to dispatched action.
Reducer and actions for a single feature are defined together in each file of slices folder.

Setup React Redux-Toolkit Project

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

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


public

src

components

add-tutorial.component.js

tutorial.component.js

tutorials-list.component.js

services

tutorial.service.js

slices

tutorials.js

App.css

App.js

index.js

store.js

package.json


Import Bootstrap to React Redux-Toolkit App

Run command: yarn add [email protected].
Or npm install [email protected].

Open src/App.js 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 Redux-Toolkit example

– Run the command: yarn add react-router-dom.
Or 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";

class App extends Component {
  render() {
    return (
      <Router>
        ...
      </Router>
    );
  }
}

export default App;

Add Navbar to Redux-Toolkit example

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, { Component } 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/add-tutorial.component";
import Tutorial from "./components/tutorial.component";
import TutorialsList from "./components/tutorials-list.component";

class App extends Component {
  render() {
    return (
      <Router>
        <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>
      </Router>
    );
  }
}

export default App;

Initialize Axios for Redux-Toolkit example with API calls

Let’s install axios with command: yarn add axios or 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 or make API calls.

services/tutorial.service.js

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

class TutorialDataService {
  getAll() {
    return http.get("/tutorials");
  }

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

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

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

  delete(id) {
    return http.delete(`/tutorials/${id}`);
  }

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

  findByTitle(title) {
    return http.get(`/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.

Install react-redux and redux-toolkit

Install Redux-toolkit with the following command:
yarn add react-redux @reduxjs/toolkit
Or npm install react-redux @reduxjs/toolkit

With redux-toolkit, we don’t need to install redux-devtools-extension.

Create Slice Reducer and Actions

Instead of creating many folders and files for Redux (actions, reducers, types,…), with redux-toolkit we just need all-in-one: slice.

A slice is a collection of Redux reducer logic and actions for a single feature.

For creating a slice, we need:

  • name to identify the slice
  • initial state value
  • one or more reducer functions to define how the state can be updated

Once a slice is created, we can export the generated Redux action creators and the reducer function for the whole slice.

Redux Toolkit provides createSlice() function that will auto-generate the action types and action creators for you, based on the names of the reducer functions you provide.

import { createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    // add your non-async reducers here
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
  extraReducers: {
    // add your async reducers here
  }
})

// Action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;

In the code above, you can see that we write “mutating” logic in reducers. It doesn’t actually mutate the state because it uses the Immer library behind the scenes, which detects changes to a “draft state” and produces a brand new immutable state based off those changes.

We’re gonna create a Slice for tutorials state. In src/slices folder, create file named tutorials.js.


slices

tutorials.js


We’re gonna import TutorialDataService to make asynchronous HTTP requests with trigger one or more dispatch in the result.

We also need to use Redux Toolkit createAsyncThunk which provides a thunk that will take care of the action types and dispatching the right actions based on the returned promise. There are 6 async Thunks to be exported:

  • createTutorial
  • retrieveTutorials
  • updateTutorial
  • deleteTutorial
  • deleteAllTutorials
  • findTutorialsByTitle

slices/tutorials.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import TutorialDataService from "../services/tutorial.service";

const initialState = [];

export const createTutorial = createAsyncThunk(
  "tutorials/create",
  async ({ title, description }) => {
    const res = await TutorialDataService.create({ title, description });
    return res.data;
  }
);

export const retrieveTutorials = createAsyncThunk(
  "tutorials/retrieve",
  async () => {
    const res = await TutorialDataService.getAll();
    return res.data;
  }
);

export const updateTutorial = createAsyncThunk(
  "tutorials/update",
  async ({ id, data }) => {
    const res = await TutorialDataService.update(id, data);
    return res.data;
  }
);

export const deleteTutorial = createAsyncThunk(
  "tutorials/delete",
  async ({ id }) => {
    await TutorialDataService.delete(id);
    return { id };
  }
);

export const deleteAllTutorials = createAsyncThunk(
  "tutorials/deleteAll",
  async () => {
    const res = await TutorialDataService.deleteAll();
    return res.data;
  }
);

export const findTutorialsByTitle = createAsyncThunk(
  "tutorials/findByTitle",
  async ({ title }) => {
    const res = await TutorialDataService.findByTitle(title);
    return res.data;
  }
);

const tutorialSlice = createSlice({
  name: "tutorial",
  initialState,
  extraReducers: {
    [createTutorial.fulfilled]: (state, action) => {
      state.push(action.payload);
    },
    [retrieveTutorials.fulfilled]: (state, action) => {
      return [...action.payload];
    },
    [updateTutorial.fulfilled]: (state, action) => {
      const index = state.findIndex(tutorial => tutorial.id === action.payload.id);
      state[index] = {
        ...state[index],
        ...action.payload,
      };
    },
    [deleteTutorial.fulfilled]: (state, action) => {
      let index = state.findIndex(({ id }) => id === action.payload.id);
      state.splice(index, 1);
    },
    [deleteAllTutorials.fulfilled]: (state, action) => {
      return [];
    },
    [findTutorialsByTitle.fulfilled]: (state, action) => {
      return [...action.payload];
    },
  },
});

const { reducer } = tutorialSlice;
export default reducer;

Create Redux Store

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

The Redux Toolkit configureStore() function automatically:

  • enable the Redux DevTools Extension.
  • sets up the thunk middleware by default, so you can immediately write thunks without more configuration.

In the previous part, we exported tutorials reducer from tutorialSlice. Let’s import it, and pass it to configureStore():

store.js

import { configureStore } from '@reduxjs/toolkit'
import tutorialReducer from './slices/tutorials';

const reducer = {
  tutorials: tutorialReducer
}

const store = configureStore({
  reducer: reducer,
  devTools: true,
})

export default store;

If you have Auth Reducer that manages authentication logic, you can add it to reducer object like following code:

import { configureStore } from '@reduxjs/toolkit'
import tutorialReducer from './slices/tutorials';
import authReducer from "./slices/auth";

const reducer = {
  tutorials: tutorialReducer,
  auth: authReducer
}

const store = configureStore({
  reducer: reducer,
  devTools: true,
})

export default store;

You can simplify import statement with:
Absolute Import in React

Provide State to React Components

We will use mapStateToProps and mapDispatchToProps to connect Redux state to React Components’ props later using connect() function:

export default connect(mapStateToProps, mapDispatchToProps)(ReactComponent);

So we need to make the Redux store available to the connect() call in the Components. We will wrap a parent or ancestor Component in Provider.

Provider is an high order component that wraps up React application and makes it aware of the entire Redux store. That is, it provides the store to its child components.

Now we want our entire React App to access the store, just put the App Component within Provider.

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 item Component

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

redux-toolkit-example-crud-app-create-tutorial

components/add-tutorial.component.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { createTutorial } from "../slices/tutorials";

class AddTutorial extends Component {
  constructor(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) {
    this.setState({
      title: e.target.value,
    });
  }

  onChangeDescription(e) {
    this.setState({
      description: e.target.value,
    });
  }

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

    this.props
      .createTutorial({ title, description })
      .unwrap()
      .then((data) => {
        this.setState({
          id: data.id,
          title: data.title,
          description: data.description,
          published: data.published,
          submitted: true,
        });
        console.log(data);
      })
      .catch((e) => {
        console.log(e);
      });
  }

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

  render() {
    return (
      ...
    );
  }
}

export default connect(null, { createTutorial })(AddTutorial);

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 createTutorial async Thunk.

How about handling the Thunk result?


createTutorial(...).then(() => {
    // do something
  })

Thunks generated by createAsyncThunk will always return a resolved promise with either the fulfilled action object or rejected action object inside.

The promise has an unwrap property which can be called to extract the payload of a fulfilled action or to throw the error.


createTutorial(...)
  .unwrap()
  .then((res) => {
    // do something
  })
  .catch((err) => {
    // handle error
  })

To connect the Redux store with local Component state and props, we use connect() with mapStateToProps and mapDispatchToProps ({ createTutorial }). Then we can use this.props.createTutorial().

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.

class AddTutorial extends Component {
  ...
  render() {
    return (
      <div className="submit-form">
        {this.state.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={this.state.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={this.state.description}
                onChange={this.onChangeDescription}
                name="description"
              />
            </div>

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

export default connect(null, { createTutorial })(AddTutorial);

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.

redux-toolkit-example-crud-app-retrieve-tutorial

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

  • searchTitle
  • currentTutorial and currentIndex

We also need to use 3 async Thunks:

  • retrieveTutorials
  • findTutorialsByTitle
  • deleteAllTutorials

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

const mapStateToProps = (state) => {
  return {
      tutorials: state.tutorials
  };
}

export default connect(mapStateToProps, {
  retrieveTutorials,
  findTutorialsByTitle,
  deleteAllTutorials
})(TutorialsList);

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

// get tutorials state
this.props.tutorials

// dispatch actions
this.props.retrieveTutorials()
this.props.findTutorialsByTitle(...)
this.props.deleteAllTutorials(...)

components/tutorials-list.component.js

import React, { Component } from "react";
import { connect } from "react-redux";
import {
  retrieveTutorials,
  findTutorialsByTitle,
  deleteAllTutorials,
} from "../slices/tutorials";
import { Link } from "react-router-dom";

class TutorialsList extends Component {
  constructor(props) {
    super(props);
    this.onChangeSearchTitle = this.onChangeSearchTitle.bind(this);
    this.refreshData = this.refreshData.bind(this);
    this.setActiveTutorial = this.setActiveTutorial.bind(this);
    this.findByTitle = this.findByTitle.bind(this);
    this.removeAllTutorials = this.removeAllTutorials.bind(this);

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

  componentDidMount() {
    this.props.retrieveTutorials();
  }

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

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

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

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

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

  findByTitle() {
    this.refreshData();

    this.props.findTutorialsByTitle({ title: this.state.searchTitle });
  }

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

    return (
      ...
    );
  }
}

const mapStateToProps = (state) => {
  return {
    tutorials: state.tutorials,
  };
};

export default connect(mapStateToProps, { retrieveTutorials, findTutorialsByTitle, deleteAllTutorials })(TutorialsList);

Let’s continue to implement render() method:

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

class TutorialsList extends Component {
  ..

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

    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.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={() => 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>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    tutorials: state.tutorials,
  };
};

export default connect(mapStateToProps, { retrieveTutorials, findTutorialsByTitle, deleteAllTutorials })(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.

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.

redux-toolkit-example-crud-app-retrieve-one-tutorial

For getting tutorial details, this component will use TutorialDataService.get() method.
For update, delete the Tutorial, we work with following async Thunks:

  • updateTutorial
  • deleteTutorial

components/tutorial.component.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { updateTutorial, deleteTutorial } from "../slices/tutorials";
import TutorialDataService from "../services/tutorial.service";

class Tutorial extends Component {
  constructor(props) {
    super(props);
    this.onChangeTitle = this.onChangeTitle.bind(this);
    this.onChangeDescription = this.onChangeDescription.bind(this);
    this.getTutorial = this.getTutorial.bind(this);
    this.updateStatus = this.updateStatus.bind(this);
    this.updateContent = this.updateContent.bind(this);
    this.removeTutorial = this.removeTutorial.bind(this);

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

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

  onChangeTitle(e) {
    const title = e.target.value;

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

  onChangeDescription(e) {
    const description = e.target.value;

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

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

  updateStatus(status) {
    var data = {
      id: this.state.currentTutorial.id,
      title: this.state.currentTutorial.title,
      description: this.state.currentTutorial.description,
      published: status,
    };

    this.props
      .updateTutorial({ id: this.state.currentTutorial.id, data })
      .unwrap()
      .then((reponse) => {
        console.log(reponse);

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

        this.setState({ message: "The status was updated successfully!" });
      })
      .catch((e) => {
        console.log(e);
      });
  }

  updateContent() {
    this.props
      .updateTutorial({ id: this.state.currentTutorial.id, data: this.state.currentTutorial })
      .unwrap()
      .then((reponse) => {
        console.log(reponse);
        
        this.setState({ message: "The tutorial was updated successfully!" });
      })
      .catch((e) => {
        console.log(e);
      });
  }

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

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

    return (
      ...
    );
  }
}

export default connect(null, { updateTutorial, deleteTutorial })(Tutorial);

And this is the code for render() method:

...

class Tutorial extends Component {
  ...

  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.updateStatus(false)}
              >
                UnPublish
              </button>
            ) : (
              <button
                className="badge badge-primary mr-2"
                onClick={() => this.updateStatus(true)}
              >
                Publish
              </button>
            )}

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

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

export default connect(null, { updateTutorial, deleteTutorial })(Tutorial);

Add CSS style for React 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 Redux-Toolkit example

Because most of HTTP Server use CORS configuration that accepts resource sharing restricted 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 Redux-Toolkit example

You can run our App with command: yarn start or 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 Redux-Toolkit example with a CRUD Application 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.

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

Fullstack:
React Redux + Spring Boot example: CRUD example
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: CRUD example

Source Code

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

React Hooks version:
Redux-Toolkit CRUD example with React Hooks

Leave a Reply

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