├── .gitignore
├── README.md
├── data
└── db.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── About.js
├── App.js
├── EditPost.js
├── Feed.js
├── Footer.js
├── Header.js
├── Home.js
├── Missing.js
├── Nav.js
├── NewPost.js
├── Post.js
├── PostPage.js
├── api
└── posts.js
├── index.css
└── index.js
/.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 JS - Axios API Requests"
2 |
3 | ✅ [Check out my YouTube Channel with all of my tutorials](https://www.youtube.com/DaveGrayTeachesCode).
4 |
5 | **Description:**
6 |
7 | This repository shares the code applied during the Youtube tutorial. The tutorial is part of a [Learn React Playlist](https://www.youtube.com/playlist?list=PL0Zuz27SZ-6PrE9srvEn8nbhOOyxnWXfp) on my channel.
8 |
9 | [YouTube Tutorial](https://youtu.be/ZEKBDXGnD4s) for this repository.
10 |
11 | I suggest completing my [8 hour JavaScript course tutorial video](https://youtu.be/EfAl9bwzVZk) if you are new to Javascript.
12 |
13 | ### Academic Honesty
14 |
15 | **DO NOT COPY FOR AN ASSIGNMENT** - Avoid plagiargism and adhere to the spirit of this [Academic Honesty Policy](https://www.freecodecamp.org/news/academic-honesty-policy/).
16 |
--------------------------------------------------------------------------------
/data/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "posts": [
3 | {
4 | "id": 1,
5 | "title": "1st post",
6 | "datetime": "July 16, 2021 11:47:39 AM",
7 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
8 | },
9 | {
10 | "id": 2,
11 | "title": "Second post",
12 | "datetime": "July 16, 2021 11:47:48 AM",
13 | "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. two"
14 | },
15 | {
16 | "id": 3,
17 | "title": "Number Three",
18 | "datetime": "July 16, 2021 11:48:01 AM",
19 | "body": "Third post... Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
20 | },
21 | {
22 | "id": 4,
23 | "title": "Testing a 4th post",
24 | "datetime": "August 02, 2021 11:46:27 AM",
25 | "body": "Some more testing paragraphs!"
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "16tut",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.14.1",
7 | "@testing-library/react": "^11.2.7",
8 | "@testing-library/user-event": "^12.8.3",
9 | "axios": "^0.21.1",
10 | "date-fns": "^2.22.1",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "react-router-dom": "^5.2.0",
14 | "react-scripts": "4.0.3",
15 | "web-vitals": "^1.1.2"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test",
21 | "eject": "react-scripts eject"
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app",
26 | "react-app/jest"
27 | ]
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitdagray/react_axios_requests/6ddffe85d43def83251995c08fe4138418ccaf7a/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/gitdagray/react_axios_requests/6ddffe85d43def83251995c08fe4138418ccaf7a/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitdagray/react_axios_requests/6ddffe85d43def83251995c08fe4138418ccaf7a/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 |
--------------------------------------------------------------------------------
/src/About.js:
--------------------------------------------------------------------------------
1 | const About = () => {
2 | return (
3 |
4 | About
5 | This blog app is a project in the Learn React tutorial series.
6 |
7 | )
8 | }
9 |
10 | export default About
11 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import Header from './Header';
2 | import Nav from './Nav';
3 | import Footer from './Footer';
4 | import Home from './Home';
5 | import NewPost from './NewPost';
6 | import PostPage from './PostPage';
7 | import EditPost from './EditPost';
8 | import About from './About';
9 | import Missing from './Missing';
10 | import { Route, Switch, useHistory } from 'react-router-dom';
11 | import { useState, useEffect } from 'react';
12 | import { format } from 'date-fns';
13 | import api from './api/posts';
14 |
15 | function App() {
16 | const [posts, setPosts] = useState([])
17 | const [search, setSearch] = useState('');
18 | const [searchResults, setSearchResults] = useState([]);
19 | const [postTitle, setPostTitle] = useState('');
20 | const [postBody, setPostBody] = useState('');
21 | const [editTitle, setEditTitle] = useState('');
22 | const [editBody, setEditBody] = useState('');
23 | const history = useHistory();
24 |
25 | useEffect(() => {
26 | const fetchPosts = async () => {
27 | try {
28 | const response = await api.get('/posts');
29 | setPosts(response.data);
30 | } catch (err) {
31 | if (err.response) {
32 | // Not in the 200 response range
33 | console.log(err.response.data);
34 | console.log(err.response.status);
35 | console.log(err.response.headers);
36 | } else {
37 | console.log(`Error: ${err.message}`);
38 | }
39 | }
40 | }
41 |
42 | fetchPosts();
43 | }, [])
44 |
45 | useEffect(() => {
46 | const filteredResults = posts.filter((post) =>
47 | ((post.body).toLowerCase()).includes(search.toLowerCase())
48 | || ((post.title).toLowerCase()).includes(search.toLowerCase()));
49 |
50 | setSearchResults(filteredResults.reverse());
51 | }, [posts, search])
52 |
53 | const handleSubmit = async (e) => {
54 | e.preventDefault();
55 | const id = posts.length ? posts[posts.length - 1].id + 1 : 1;
56 | const datetime = format(new Date(), 'MMMM dd, yyyy pp');
57 | const newPost = { id, title: postTitle, datetime, body: postBody };
58 | try {
59 | const response = await api.post('/posts', newPost);
60 | const allPosts = [...posts, response.data];
61 | setPosts(allPosts);
62 | setPostTitle('');
63 | setPostBody('');
64 | history.push('/');
65 | } catch (err) {
66 | console.log(`Error: ${err.message}`);
67 | }
68 | }
69 |
70 | const handleEdit = async (id) => {
71 | const datetime = format(new Date(), 'MMMM dd, yyyy pp');
72 | const updatedPost = { id, title: editTitle, datetime, body: editBody };
73 | try {
74 | const response = await api.put(`/posts/${id}`, updatedPost);
75 | setPosts(posts.map(post => post.id === id ? { ...response.data } : post));
76 | setEditTitle('');
77 | setEditBody('');
78 | history.push('/');
79 | } catch (err) {
80 | console.log(`Error: ${err.message}`);
81 | }
82 | }
83 |
84 | const handleDelete = async (id) => {
85 | try {
86 | await api.delete(`/posts/${id}`);
87 | const postsList = posts.filter(post => post.id !== id);
88 | setPosts(postsList);
89 | history.push('/');
90 | } catch (err) {
91 | console.log(`Error: ${err.message}`);
92 | }
93 | }
94 |
95 | return (
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
111 |
112 |
113 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | );
131 | }
132 |
133 | export default App;
134 |
--------------------------------------------------------------------------------
/src/EditPost.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useParams, Link } from "react-router-dom";
3 |
4 | const EditPost = ({
5 | posts, handleEdit, editBody, setEditBody, editTitle, setEditTitle
6 | }) => {
7 | const { id } = useParams();
8 | const post = posts.find(post => (post.id).toString() === id);
9 |
10 | useEffect(() => {
11 | if (post) {
12 | setEditTitle(post.title);
13 | setEditBody(post.body);
14 | }
15 | }, [post, setEditTitle, setEditBody])
16 |
17 | return (
18 |
19 | {editTitle &&
20 | <>
21 | Edit Post
22 |
40 | >
41 | }
42 | {!editTitle &&
43 | <>
44 | Post Not Found
45 | Well, that's disappointing.
46 |
47 | Visit Our Homepage
48 |
49 | >
50 | }
51 |
52 | )
53 | }
54 |
55 | export default EditPost
56 |
--------------------------------------------------------------------------------
/src/Feed.js:
--------------------------------------------------------------------------------
1 | import Post from './Post';
2 |
3 | const Feed = ({ posts }) => {
4 | return (
5 | <>
6 | {posts.map(post => (
7 |
8 | ))}
9 | >
10 | )
11 | }
12 |
13 | export default Feed
14 |
--------------------------------------------------------------------------------
/src/Footer.js:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | const today = new Date();
3 | return (
4 |
7 | )
8 | }
9 |
10 | export default Footer
11 |
--------------------------------------------------------------------------------
/src/Header.js:
--------------------------------------------------------------------------------
1 | const Header = ({ title }) => {
2 | return (
3 |
6 | )
7 | }
8 |
9 | export default Header
10 |
--------------------------------------------------------------------------------
/src/Home.js:
--------------------------------------------------------------------------------
1 | import Feed from './Feed';
2 |
3 | const Home = ({ posts }) => {
4 | return (
5 |
6 | {posts.length ? (
7 |
8 | ) : (
9 |
10 | No posts to display.
11 |
12 | )}
13 |
14 | )
15 | }
16 |
17 | export default Home
18 |
--------------------------------------------------------------------------------
/src/Missing.js:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 |
3 | const Missing = () => {
4 | return (
5 |
6 | Page Not Found
7 | Well, that's disappointing.
8 |
9 | Visit Our Homepage
10 |
11 |
12 | )
13 | }
14 |
15 | export default Missing
16 |
--------------------------------------------------------------------------------
/src/Nav.js:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 |
3 | const Nav = ({ search, setSearch }) => {
4 | return (
5 |
22 | )
23 | }
24 |
25 | export default Nav
26 |
--------------------------------------------------------------------------------
/src/NewPost.js:
--------------------------------------------------------------------------------
1 | const NewPost = ({
2 | handleSubmit, postTitle, setPostTitle, postBody, setPostBody
3 | }) => {
4 | return (
5 |
6 | New Post
7 |
25 |
26 | )
27 | }
28 |
29 | export default NewPost
30 |
--------------------------------------------------------------------------------
/src/Post.js:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 |
3 | const Post = ({ post }) => {
4 | return (
5 |
6 |
7 | {post.title}
8 | {post.datetime}
9 |
10 | {
11 | (post.body).length <= 25
12 | ? post.body
13 | : `${(post.body).slice(0, 25)}...`
14 | }
15 |
16 | )
17 | }
18 |
19 | export default Post
20 |
--------------------------------------------------------------------------------
/src/PostPage.js:
--------------------------------------------------------------------------------
1 | import { useParams, Link } from "react-router-dom";
2 |
3 | const PostPage = ({ posts, handleDelete }) => {
4 | const { id } = useParams();
5 | const post = posts.find(post => (post.id).toString() === id);
6 | return (
7 |
8 |
9 | {post &&
10 | <>
11 | {post.title}
12 | {post.datetime}
13 | {post.body}
14 |
15 |
18 | >
19 | }
20 | {!post &&
21 | <>
22 | Post Not Found
23 | Well, that's disappointing.
24 |
25 | Visit Our Homepage
26 |
27 | >
28 | }
29 |
30 |
31 | )
32 | }
33 |
34 | export default PostPage
35 |
--------------------------------------------------------------------------------
/src/api/posts.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export default axios.create({
4 | baseURL: 'http://localhost:3500'
5 | });
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans&display=swap');
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | html {
10 | font-size: 16px;
11 | }
12 |
13 | body {
14 | min-height: 100vh;
15 | font-family: 'Open Sans', sans-serif;
16 | -webkit-font-smoothing: antialiased;
17 | -moz-osx-font-smoothing: grayscale;
18 | display: flex;
19 | background-color: #efefef;
20 | }
21 |
22 | #root {
23 | flex-grow: 1;
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 | }
28 |
29 | .App {
30 | width: 100%;
31 | max-width: 800px;
32 | height: 100vh;
33 | display: flex;
34 | flex-direction: column;
35 | justify-content: flex-start;
36 | align-items: center;
37 | border: 1px solid #333;
38 | box-shadow: 0px 0px 15px gray;
39 | }
40 |
41 | .Header, .Footer {
42 | width: 100%;
43 | background-color: #66d8f5;
44 | padding: 1rem;
45 | display: flex;
46 | justify-content: space-between;
47 | align-items: center;
48 | }
49 |
50 | .Header h1 {
51 | font-size: 1.5rem;
52 | }
53 |
54 | .Header svg {
55 | font-size: 2rem;
56 | }
57 |
58 | .Footer {
59 | padding: 0.75rem;
60 | display: grid;
61 | place-content: center;
62 | }
63 |
64 | .Nav {
65 | width: 100%;
66 | background-color: #333;
67 | display: flex;
68 | flex-direction: column;
69 | justify-content: flex-start;
70 | align-items: flex-start;
71 | }
72 |
73 | .searchForm {
74 | width: 80%;
75 | padding: 1rem 0 0 0.75rem;
76 | }
77 |
78 | .searchForm input[type="text"] {
79 | font-family: 'Open Sans', sans-serif;
80 | width: 100%;
81 | min-height: 48px;
82 | font-size: 1rem;
83 | padding: 0.25rem;
84 | border-radius: 0.25rem;
85 | outline: none;
86 | }
87 |
88 | .searchForm label {
89 | position: absolute;
90 | left: -99999px;
91 | }
92 |
93 | .Nav ul {
94 | color: #fff;
95 | list-style-type: none;
96 | display: flex;
97 | flex-wrap: nowrap;
98 | align-items: center;
99 | }
100 |
101 | .Nav li {
102 | padding: 1rem;
103 | }
104 |
105 | .Nav li:hover,
106 | .Nav li:focus {
107 | padding: 1rem;
108 | }
109 |
110 | .Nav li a {
111 | color: #fff;
112 | text-decoration: none;
113 | }
114 |
115 | .Nav li:hover,
116 | .Nav li:focus,
117 | .Nav li:hover a,
118 | .Nav li:focus a {
119 | background-color: #eee;
120 | color: #333;
121 | }
122 |
123 | .Nav li:hover a,
124 | .Nav li:focus a {
125 | cursor: pointer;
126 | }
127 |
128 | .Home, .NewPost, .PostPage, .About, .Missing {
129 | width: 100%;
130 | flex-grow: 1;
131 | padding: 1rem;
132 | overflow-y: auto;
133 | background-color: #fff;
134 | }
135 |
136 | .post {
137 | margin-top: 1rem;
138 | padding-bottom: 1rem;
139 | border-bottom: 1px solid lightgray;
140 | }
141 |
142 | .Home .post a {
143 | text-decoration: none;
144 | }
145 |
146 | .Home .post a,
147 | .Home .post a:visited {
148 | color: #000;
149 | }
150 |
151 | .post:first-child {
152 | margin-top: 0;
153 | }
154 |
155 | .post:last-child {
156 | border-bottom: none;
157 | }
158 |
159 | .postDate {
160 | font-size: 0.75rem;
161 | margin-top: 0.25rem;
162 | }
163 |
164 | .postBody {
165 | margin: 1rem 0;
166 | }
167 |
168 | .newPostForm {
169 | display: flex;
170 | flex-direction: column;
171 | }
172 |
173 | .newPostForm label {
174 | margin-top: 1rem;
175 | }
176 |
177 | .newPostForm input[type='text'],
178 | .newPostForm textarea {
179 | font-family: 'Open Sans', sans-serif;
180 | width: 100%;
181 | min-height: 48px;
182 | font-size: 1rem;
183 | padding: 0.25rem;
184 | border-radius: 0.25rem;
185 | margin-right: 0.25rem;
186 | outline: none;
187 | }
188 |
189 | .newPostForm textarea {
190 | height: 100px;
191 | }
192 |
193 | .newPostForm button {
194 | margin-top: 1rem;
195 | height: 48px;
196 | min-width: 48px;
197 | border-radius: 10px;
198 | padding: 0.5rem;
199 | font-size: 1rem;
200 | cursor: pointer;
201 | }
202 |
203 | .Missing p, .PostPage p, .NewPost p {
204 | margin-top: 1rem;
205 | }
206 |
207 | .PostPage button {
208 | height: 48px;
209 | min-width: 48px;
210 | border-radius: 0.25rem;
211 | padding: 0.5rem;
212 | margin-right: 0.5rem;
213 | font-size: 1rem;
214 | color: #fff;
215 | cursor: pointer;
216 | }
217 |
218 | .deleteButton {
219 | background-color: red;
220 | }
221 |
222 | .editButton {
223 | background-color: #333;
224 | }
225 |
226 | .statusMsg {
227 | margin-top: 2rem;
228 | }
229 |
230 | @media only screen and (min-width: 610px) {
231 | html {
232 | font-size: 22px;
233 | }
234 |
235 | .Header h1 {
236 | font-size: 2rem;
237 | }
238 |
239 | .Nav {
240 | flex-direction: row;
241 | flex-wrap: nowrap;
242 | justify-content: space-between;
243 | align-items: center;
244 | }
245 |
246 | .Nav ul {
247 | text-align: right;
248 | }
249 |
250 | .Nav li:hover,
251 | .Nav li:focus,
252 | .Nav li:hover a,
253 | .Nav li:focus a {
254 | background-color: #eee;
255 | color: #333;
256 | }
257 |
258 | .searchForm {
259 | width: 50%;
260 | padding: .5rem 0;
261 | }
262 |
263 | .searchForm input[type="text"] {
264 | margin-left: 0.5rem;
265 | }
266 |
267 | .newPostForm textarea {
268 | height: 300px;
269 | }
270 | }
271 |
272 | @media only screen and (min-width: 992px) {
273 |
274 | .Header svg {
275 | font-size: 3rem;
276 | }
277 |
278 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import { BrowserRouter as Router, Route } from 'react-router-dom';
6 |
7 | ReactDOM.render(
8 |
9 |
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
15 |
16 |
--------------------------------------------------------------------------------