├── .env
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── react-redux-crud-example-web-api-demo.png
├── src
├── App.css
├── App.js
├── actions
│ ├── tutorials.js
│ └── types.js
├── components
│ ├── add-tutorial.component.js
│ ├── tutorial.component.js
│ └── tutorials-list.component.js
├── http-common.js
├── index.css
├── index.js
├── logo.svg
├── reducers
│ ├── index.js
│ └── tutorials.js
├── reportWebVitals.js
├── services
│ └── tutorial.service.js
├── setupTests.js
└── store.js
└── yarn.lock
/.env:
--------------------------------------------------------------------------------
1 | PORT=8081
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Redux CRUD App example with Axios and Rest API
2 |
3 | Build a React Redux CRUD Application to consume Web API using Axios, display and modify data with Router & Bootstrap.
4 |
5 | React Redux Tutorial Application in that:
6 | - Each Tutorial has id, title, description, published status.
7 | - We can create, retrieve, update, delete Tutorials.
8 | - There is a Search bar for finding Tutorials by title.
9 |
10 | 
11 |
12 | For instruction, please visit:
13 | > [React Redux CRUD App example with Rest API](https://bezkoder.com/react-redux-crud-example/)
14 |
15 | Related Posts:
16 | > [React (without Redux) CRUD example to consume Web API](https://bezkoder.com/react-crud-web-api/)
17 |
18 | > [React (Hooks) CRUD example to consume Web API](https://bezkoder.com/react-hooks-crud-axios-api/)
19 |
20 | > [React Table example: CRUD App with react-table v7](https://bezkoder.com/react-table-example-hooks-crud/)
21 |
22 | Using Material UI instead of Bootstrap:
23 | > [React Material UI examples with a CRUD Application](https://bezkoder.com/react-material-ui-examples-crud/)
24 |
25 | More Practice:
26 | > [React Pagination example](https://bezkoder.com/react-pagination-material-ui/)
27 |
28 | > [React File Upload example](https://bezkoder.com/react-file-upload-axios/)
29 |
30 | > [React JWT Authentication & Authorization example](https://bezkoder.com/react-jwt-auth/)
31 |
32 | > [React + Redux: JWT Authentication & Authorization example](https://bezkoder.com/react-redux-jwt-auth/)
33 |
34 | Fullstack with Node.js Express:
35 | > [React.js + Node.js Express + MySQL](https://bezkoder.com/react-node-express-mysql/)
36 |
37 | > [React.js + Node.js Express + PostgreSQL](https://bezkoder.com/react-node-express-postgresql/)
38 |
39 | > [React.js + Node.js Express + MongoDB](https://bezkoder.com/react-node-express-mongodb-mern-stack/)
40 |
41 | Fullstack with Spring Boot:
42 | > [React.js + Spring Boot + MySQL](https://bezkoder.com/react-spring-boot-crud/)
43 |
44 | > [React.js + Spring Boot + PostgreSQL](https://bezkoder.com/spring-boot-react-postgresql/)
45 |
46 | > [React.js + Spring Boot + MongoDB](https://bezkoder.com/react-spring-boot-mongodb/)
47 |
48 | Fullstack with Django:
49 |
50 | > [React.js + Django Rest Framework](https://bezkoder.com/django-react-axios-rest-framework/)
51 |
52 | Serverless:
53 | > [React Firebase CRUD App with Realtime Database](https://bezkoder.com/react-firebase-crud/)
54 |
55 | > [React Firestore CRUD App example | Firebase Cloud Firestore](https://bezkoder.com/react-firestore-crud/)
56 |
57 | Integration (run back-end & front-end on same server/port)
58 | > [How to integrate React.js with Spring Boot](https://bezkoder.com/integrate-reactjs-spring-boot/)
59 |
60 | > [Integrate React with Node.js Express on same Server/Port](https://bezkoder.com/integrate-react-express-same-server-port/)
61 |
62 |
63 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
64 |
65 | ### Set port
66 | .env
67 | ```
68 | PORT=8081
69 | ```
70 |
71 | ## Project setup
72 |
73 | In the project directory, you can run:
74 |
75 | ```
76 | npm install
77 | # or
78 | yarn install
79 | ```
80 |
81 | or
82 |
83 | ### Compiles and hot-reloads for development
84 |
85 | ```
86 | npm start
87 | # or
88 | yarn start
89 | ```
90 |
91 | Open [http://localhost:8081](http://localhost:8081) to view it in the browser.
92 |
93 | The page will reload if you make edits.
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-crud-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "axios": "^0.21.1",
10 | "bootstrap": "^4.6.0",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "react-redux": "^7.2.3",
14 | "react-router-dom": "^5.2.0",
15 | "react-scripts": "4.0.3",
16 | "redux": "^4.0.5",
17 | "redux-thunk": "^2.3.0",
18 | "web-vitals": "^1.0.1"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | },
44 | "devDependencies": {
45 | "redux-devtools-extension": "^2.13.9"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/react-redux-axios-crud/ab341a75b5ebac3ce55f85c3e38d605cacbfc652/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/react-redux-axios-crud/ab341a75b5ebac3ce55f85c3e38d605cacbfc652/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/react-redux-axios-crud/ab341a75b5ebac3ce55f85c3e38d605cacbfc652/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/react-redux-crud-example-web-api-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/react-redux-axios-crud/ab341a75b5ebac3ce55f85c3e38d605cacbfc652/react-redux-crud-example-web-api-demo.png
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .list {
2 | text-align: left;
3 | max-width: 750px;
4 | margin: auto;
5 | }
6 |
7 | .submit-form {
8 | max-width: 300px;
9 | margin: auto;
10 | }
11 |
12 | .edit-form {
13 | max-width: 300px;
14 | margin: auto;
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
3 | import "bootstrap/dist/css/bootstrap.min.css";
4 | import "./App.css";
5 |
6 | import AddTutorial from "./components/add-tutorial.component";
7 | import Tutorial from "./components/tutorial.component";
8 | import TutorialsList from "./components/tutorials-list.component";
9 |
10 | class App extends Component {
11 | render() {
12 | return (
13 |
14 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | export default App;
45 |
--------------------------------------------------------------------------------
/src/actions/tutorials.js:
--------------------------------------------------------------------------------
1 | import {
2 | CREATE_TUTORIAL,
3 | RETRIEVE_TUTORIALS,
4 | UPDATE_TUTORIAL,
5 | DELETE_TUTORIAL,
6 | DELETE_ALL_TUTORIALS
7 | } from "./types";
8 |
9 | import TutorialDataService from "../services/tutorial.service";
10 |
11 | export const createTutorial = (title, description) => async (dispatch) => {
12 | try {
13 | const res = await TutorialDataService.create({ title, description });
14 |
15 | dispatch({
16 | type: CREATE_TUTORIAL,
17 | payload: res.data,
18 | });
19 |
20 | return Promise.resolve(res.data);
21 | } catch (err) {
22 | return Promise.reject(err);
23 | }
24 | };
25 |
26 | export const retrieveTutorials = () => async (dispatch) => {
27 | try {
28 | const res = await TutorialDataService.getAll();
29 |
30 | dispatch({
31 | type: RETRIEVE_TUTORIALS,
32 | payload: res.data,
33 | });
34 | } catch (err) {
35 | console.log(err);
36 | }
37 | };
38 |
39 | export const updateTutorial = (id, data) => async (dispatch) => {
40 | try {
41 | const res = await TutorialDataService.update(id, data);
42 |
43 | dispatch({
44 | type: UPDATE_TUTORIAL,
45 | payload: data,
46 | });
47 |
48 | return Promise.resolve(res.data);
49 | } catch (err) {
50 | return Promise.reject(err);
51 | }
52 | };
53 |
54 | export const deleteTutorial = (id) => async (dispatch) => {
55 | try {
56 | await TutorialDataService.delete(id);
57 |
58 | dispatch({
59 | type: DELETE_TUTORIAL,
60 | payload: { id },
61 | });
62 | } catch (err) {
63 | console.log(err);
64 | }
65 | };
66 |
67 | export const deleteAllTutorials = () => async (dispatch) => {
68 | try {
69 | const res = await TutorialDataService.deleteAll();
70 |
71 | dispatch({
72 | type: DELETE_ALL_TUTORIALS,
73 | payload: res.data,
74 | });
75 |
76 | return Promise.resolve(res.data);
77 | } catch (err) {
78 | return Promise.reject(err);
79 | }
80 | };
81 |
82 | export const findTutorialsByTitle = (title) => async (dispatch) => {
83 | try {
84 | const res = await TutorialDataService.findByTitle(title);
85 |
86 | dispatch({
87 | type: RETRIEVE_TUTORIALS,
88 | payload: res.data,
89 | });
90 | } catch (err) {
91 | console.log(err);
92 | }
93 | };
--------------------------------------------------------------------------------
/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const CREATE_TUTORIAL = "CREATE_TUTORIAL";
2 | export const RETRIEVE_TUTORIALS = "RETRIEVE_TUTORIALS";
3 | export const UPDATE_TUTORIAL = "UPDATE_TUTORIAL";
4 | export const DELETE_TUTORIAL = "DELETE_TUTORIAL";
5 | export const DELETE_ALL_TUTORIALS = "DELETE_ALL_TUTORIALS";
--------------------------------------------------------------------------------
/src/components/add-tutorial.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { createTutorial } from "../actions/tutorials";
4 |
5 | class AddTutorial extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.onChangeTitle = this.onChangeTitle.bind(this);
9 | this.onChangeDescription = this.onChangeDescription.bind(this);
10 | this.saveTutorial = this.saveTutorial.bind(this);
11 | this.newTutorial = this.newTutorial.bind(this);
12 |
13 | this.state = {
14 | id: null,
15 | title: "",
16 | description: "",
17 | published: false,
18 |
19 | submitted: false,
20 | };
21 | }
22 |
23 | onChangeTitle(e) {
24 | this.setState({
25 | title: e.target.value,
26 | });
27 | }
28 |
29 | onChangeDescription(e) {
30 | this.setState({
31 | description: e.target.value,
32 | });
33 | }
34 |
35 | saveTutorial() {
36 | const { title, description } = this.state;
37 |
38 | this.props
39 | .createTutorial(title, description)
40 | .then((data) => {
41 | this.setState({
42 | id: data.id,
43 | title: data.title,
44 | description: data.description,
45 | published: data.published,
46 |
47 | submitted: true,
48 | });
49 | console.log(data);
50 | })
51 | .catch((e) => {
52 | console.log(e);
53 | });
54 | }
55 |
56 | newTutorial() {
57 | this.setState({
58 | id: null,
59 | title: "",
60 | description: "",
61 | published: false,
62 |
63 | submitted: false,
64 | });
65 | }
66 |
67 | render() {
68 | return (
69 |
70 | {this.state.submitted ? (
71 |
72 |
You submitted successfully!
73 |
76 |
77 | ) : (
78 |
79 |
80 |
81 |
90 |
91 |
92 |
93 |
94 |
103 |
104 |
105 |
108 |
109 | )}
110 |
111 | );
112 | }
113 | }
114 |
115 | export default connect(null, { createTutorial })(AddTutorial);
116 |
--------------------------------------------------------------------------------
/src/components/tutorial.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { updateTutorial, deleteTutorial } from "../actions/tutorials";
4 | import TutorialDataService from "../services/tutorial.service";
5 |
6 | class Tutorial extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.onChangeTitle = this.onChangeTitle.bind(this);
10 | this.onChangeDescription = this.onChangeDescription.bind(this);
11 | this.getTutorial = this.getTutorial.bind(this);
12 | this.updateStatus = this.updateStatus.bind(this);
13 | this.updateContent = this.updateContent.bind(this);
14 | this.removeTutorial = this.removeTutorial.bind(this);
15 |
16 | this.state = {
17 | currentTutorial: {
18 | id: null,
19 | title: "",
20 | description: "",
21 | published: false,
22 | },
23 | message: "",
24 | };
25 | }
26 |
27 | componentDidMount() {
28 | this.getTutorial(this.props.match.params.id);
29 | }
30 |
31 | onChangeTitle(e) {
32 | const title = e.target.value;
33 |
34 | this.setState(function (prevState) {
35 | return {
36 | currentTutorial: {
37 | ...prevState.currentTutorial,
38 | title: title,
39 | },
40 | };
41 | });
42 | }
43 |
44 | onChangeDescription(e) {
45 | const description = e.target.value;
46 |
47 | this.setState((prevState) => ({
48 | currentTutorial: {
49 | ...prevState.currentTutorial,
50 | description: description,
51 | },
52 | }));
53 | }
54 |
55 | getTutorial(id) {
56 | TutorialDataService.get(id)
57 | .then((response) => {
58 | this.setState({
59 | currentTutorial: response.data,
60 | });
61 | console.log(response.data);
62 | })
63 | .catch((e) => {
64 | console.log(e);
65 | });
66 | }
67 |
68 | updateStatus(status) {
69 | var data = {
70 | id: this.state.currentTutorial.id,
71 | title: this.state.currentTutorial.title,
72 | description: this.state.currentTutorial.description,
73 | published: status,
74 | };
75 |
76 | this.props
77 | .updateTutorial(this.state.currentTutorial.id, data)
78 | .then((reponse) => {
79 | console.log(reponse);
80 |
81 | this.setState((prevState) => ({
82 | currentTutorial: {
83 | ...prevState.currentTutorial,
84 | published: status,
85 | },
86 | }));
87 |
88 | this.setState({ message: "The status was updated successfully!" });
89 | })
90 | .catch((e) => {
91 | console.log(e);
92 | });
93 | }
94 |
95 | updateContent() {
96 | this.props
97 | .updateTutorial(this.state.currentTutorial.id, this.state.currentTutorial)
98 | .then((reponse) => {
99 | console.log(reponse);
100 |
101 | this.setState({ message: "The tutorial was updated successfully!" });
102 | })
103 | .catch((e) => {
104 | console.log(e);
105 | });
106 | }
107 |
108 | removeTutorial() {
109 | this.props
110 | .deleteTutorial(this.state.currentTutorial.id)
111 | .then(() => {
112 | this.props.history.push("/tutorials");
113 | })
114 | .catch((e) => {
115 | console.log(e);
116 | });
117 | }
118 |
119 | render() {
120 | const { currentTutorial } = this.state;
121 |
122 | return (
123 |
124 | {currentTutorial ? (
125 |
126 |
Tutorial
127 |
156 |
157 | {currentTutorial.published ? (
158 |
164 | ) : (
165 |
171 | )}
172 |
173 |
179 |
180 |
187 |
{this.state.message}
188 |
189 | ) : (
190 |
191 |
192 |
Please click on a Tutorial...
193 |
194 | )}
195 |
196 | );
197 | }
198 | }
199 |
200 | export default connect(null, { updateTutorial, deleteTutorial })(Tutorial);
201 |
--------------------------------------------------------------------------------
/src/components/tutorials-list.component.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import {
4 | retrieveTutorials,
5 | findTutorialsByTitle,
6 | deleteAllTutorials,
7 | } from "../actions/tutorials";
8 | import { Link } from "react-router-dom";
9 |
10 | class TutorialsList extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.onChangeSearchTitle = this.onChangeSearchTitle.bind(this);
14 | this.refreshData = this.refreshData.bind(this);
15 | this.setActiveTutorial = this.setActiveTutorial.bind(this);
16 | this.findByTitle = this.findByTitle.bind(this);
17 | this.removeAllTutorials = this.removeAllTutorials.bind(this);
18 |
19 | this.state = {
20 | currentTutorial: null,
21 | currentIndex: -1,
22 | searchTitle: "",
23 | };
24 | }
25 |
26 | componentDidMount() {
27 | this.props.retrieveTutorials();
28 | }
29 |
30 | onChangeSearchTitle(e) {
31 | const searchTitle = e.target.value;
32 |
33 | this.setState({
34 | searchTitle: searchTitle,
35 | });
36 | }
37 |
38 | refreshData() {
39 | this.setState({
40 | currentTutorial: null,
41 | currentIndex: -1,
42 | });
43 | }
44 |
45 | setActiveTutorial(tutorial, index) {
46 | this.setState({
47 | currentTutorial: tutorial,
48 | currentIndex: index,
49 | });
50 | }
51 |
52 | removeAllTutorials() {
53 | this.props
54 | .deleteAllTutorials()
55 | .then((response) => {
56 | console.log(response);
57 | this.refreshData();
58 | })
59 | .catch((e) => {
60 | console.log(e);
61 | });
62 | }
63 |
64 | findByTitle() {
65 | this.refreshData();
66 |
67 | this.props.findTutorialsByTitle(this.state.searchTitle);
68 | }
69 |
70 | render() {
71 | const { searchTitle, currentTutorial, currentIndex } = this.state;
72 | const { tutorials } = this.props;
73 |
74 | return (
75 |
76 |
77 |
78 |
85 |
86 |
93 |
94 |
95 |
96 |
97 |
Tutorials List
98 |
99 |
100 | {tutorials &&
101 | tutorials.map((tutorial, index) => (
102 | - this.setActiveTutorial(tutorial, index)}
108 | key={index}
109 | >
110 | {tutorial.title}
111 |
112 | ))}
113 |
114 |
115 |
121 |
122 |
123 | {currentTutorial ? (
124 |
125 |
Tutorial
126 |
127 | {" "}
130 | {currentTutorial.title}
131 |
132 |
133 | {" "}
136 | {currentTutorial.description}
137 |
138 |
139 | {" "}
142 | {currentTutorial.published ? "Published" : "Pending"}
143 |
144 |
145 |
149 | Edit
150 |
151 |
152 | ) : (
153 |
154 |
155 |
Please click on a Tutorial...
156 |
157 | )}
158 |
159 |
160 | );
161 | }
162 | }
163 |
164 | const mapStateToProps = (state) => {
165 | return {
166 | tutorials: state.tutorials,
167 | };
168 | };
169 |
170 | export default connect(mapStateToProps, {
171 | retrieveTutorials,
172 | findTutorialsByTitle,
173 | deleteAllTutorials,
174 | })(TutorialsList);
175 |
--------------------------------------------------------------------------------
/src/http-common.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export default axios.create({
4 | baseURL: "http://localhost:8080/api",
5 | headers: {
6 | "Content-type": "application/json"
7 | }
8 | });
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import reportWebVitals from './reportWebVitals';
5 | import { Provider } from 'react-redux';
6 | import store from './store';
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById('root')
13 | );
14 |
15 | // If you want to start measuring performance in your app, pass a function
16 | // to log results (for example: reportWebVitals(console.log))
17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
18 | reportWebVitals();
19 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import tutorials from "./tutorials";
3 |
4 | export default combineReducers({
5 | tutorials,
6 | });
7 |
--------------------------------------------------------------------------------
/src/reducers/tutorials.js:
--------------------------------------------------------------------------------
1 | import {
2 | CREATE_TUTORIAL,
3 | RETRIEVE_TUTORIALS,
4 | UPDATE_TUTORIAL,
5 | DELETE_TUTORIAL,
6 | DELETE_ALL_TUTORIALS,
7 | } from "../actions/types";
8 |
9 | const initialState = [];
10 |
11 | function tutorialReducer(tutorials = initialState, action) {
12 | const { type, payload } = action;
13 |
14 | switch (type) {
15 | case CREATE_TUTORIAL:
16 | return [...tutorials, payload];
17 |
18 | case RETRIEVE_TUTORIALS:
19 | return payload;
20 |
21 | case UPDATE_TUTORIAL:
22 | return tutorials.map((tutorial) => {
23 | if (tutorial.id === payload.id) {
24 | return {
25 | ...tutorial,
26 | ...payload,
27 | };
28 | } else {
29 | return tutorial;
30 | }
31 | });
32 |
33 | case DELETE_TUTORIAL:
34 | return tutorials.filter(({ id }) => id !== payload.id);
35 |
36 | case DELETE_ALL_TUTORIALS:
37 | return [];
38 |
39 | default:
40 | return tutorials;
41 | }
42 | };
43 |
44 | export default tutorialReducer;
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/services/tutorial.service.js:
--------------------------------------------------------------------------------
1 | import http from "../http-common";
2 |
3 | class TutorialDataService {
4 | getAll() {
5 | return http.get("/tutorials");
6 | }
7 |
8 | get(id) {
9 | return http.get(`/tutorials/${id}`);
10 | }
11 |
12 | create(data) {
13 | return http.post("/tutorials", data);
14 | }
15 |
16 | update(id, data) {
17 | return http.put(`/tutorials/${id}`, data);
18 | }
19 |
20 | delete(id) {
21 | return http.delete(`/tutorials/${id}`);
22 | }
23 |
24 | deleteAll() {
25 | return http.delete(`/tutorials`);
26 | }
27 |
28 | findByTitle(title) {
29 | return http.get(`/tutorials?title=${title}`);
30 | }
31 | }
32 |
33 | export default new TutorialDataService();
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import { composeWithDevTools } from "redux-devtools-extension";
3 | import thunk from 'redux-thunk';
4 | import rootReducer from './reducers';
5 |
6 | const initialState = {};
7 |
8 | const middleware = [thunk];
9 |
10 | const store = createStore(
11 | rootReducer,
12 | initialState,
13 | composeWithDevTools(applyMiddleware(...middleware))
14 | );
15 |
16 | export default store;
--------------------------------------------------------------------------------