├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
└── index.html
└── src
├── Animation.css
├── App.js
├── components
├── Comment
│ ├── Comment.css
│ └── Comment.js
├── CommentList
│ ├── CommentList.css
│ └── CommentList.js
├── Header
│ ├── Header.css
│ └── Header.js
├── Navigate
│ ├── Navigate.css
│ └── Navigate.js
├── Post
│ ├── Post.css
│ └── Post.js
├── PostWrapper
│ ├── PostWrapper.css
│ └── PostWrapper.js
├── Warning
│ ├── Warning.css
│ └── Warning.js
└── index.js
├── containers
├── PostContainer
│ └── PostContainer.js
└── index.js
├── index.css
├── index.js
├── logo.svg
└── services
└── post.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # testing
7 | coverage
8 |
9 | # production
10 | build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-ajax-tutorial
2 |
3 | # Description
4 | This is a sample project that retrieves data from [JSONPlaceHolder](https://jsonplaceholder.typicode.com/) using axios.
5 |
6 | ## Preview
7 | https://react-ajax-tutorial.surge.sh/
8 |
9 | ## Tutorial
10 | Tutorial on this project is available from [Velopert.log](https://velopert.com/2597) (KOREAN)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-ajax-tutorial",
3 | "version": "0.1.0",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.8.4"
7 | },
8 | "dependencies": {
9 | "axios": "^0.15.3",
10 | "promise-polyfill": "^6.0.2",
11 | "react": "^15.4.1",
12 | "react-dom": "^15.4.1",
13 | "semantic-ui-css": "^2.2.4",
14 | "semantic-ui-react": "^0.63.1"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test --env=jsdom",
20 | "eject": "react-scripts eject"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/velopert/react-ajax-tutorial/98445f1cbf9a43aa051d60618ffa3277516674c2/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 | React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Animation.css:
--------------------------------------------------------------------------------
1 | /*base code*/
2 |
3 | .animated {
4 | -webkit-animation-duration: 1s;
5 | animation-duration: 1s;
6 | -webkit-animation-fill-mode: both;
7 | animation-fill-mode: both;
8 | }
9 |
10 | .animated.infinite {
11 | -webkit-animation-iteration-count: infinite;
12 | animation-iteration-count: infinite;
13 | }
14 |
15 | .animated.hinge {
16 | -webkit-animation-duration: 2s;
17 | animation-duration: 2s;
18 | }
19 |
20 | /*the animation definition*/
21 |
22 | @-webkit-keyframes bounceIn {
23 | 0%, 100%, 20%, 40%, 60%, 80% {
24 | -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
25 | transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
26 | }
27 | 0% {
28 | opacity: 0;
29 | -webkit-transform: scale3d(.3, .3, .3);
30 | transform: scale3d(.3, .3, .3)
31 | }
32 | 20% {
33 | -webkit-transform: scale3d(1.1, 1.1, 1.1);
34 | transform: scale3d(1.1, 1.1, 1.1)
35 | }
36 | 40% {
37 | -webkit-transform: scale3d(.9, .9, .9);
38 | transform: scale3d(.9, .9, .9)
39 | }
40 | 60% {
41 | opacity: 1;
42 | -webkit-transform: scale3d(1.03, 1.03, 1.03);
43 | transform: scale3d(1.03, 1.03, 1.03)
44 | }
45 | 80% {
46 | -webkit-transform: scale3d(.97, .97, .97);
47 | transform: scale3d(.97, .97, .97)
48 | }
49 | 100% {
50 | opacity: 1;
51 | -webkit-transform: scale3d(1, 1, 1);
52 | transform: scale3d(1, 1, 1)
53 | }
54 | }
55 |
56 | @keyframes bounceIn {
57 | 0%, 100%, 20%, 40%, 60%, 80% {
58 | -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
59 | transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
60 | }
61 | 0% {
62 | opacity: 0;
63 | -webkit-transform: scale3d(.3, .3, .3);
64 | -ms-transform: scale3d(.3, .3, .3);
65 | transform: scale3d(.3, .3, .3)
66 | }
67 | 20% {
68 | -webkit-transform: scale3d(1.1, 1.1, 1.1);
69 | -ms-transform: scale3d(1.1, 1.1, 1.1);
70 | transform: scale3d(1.1, 1.1, 1.1)
71 | }
72 | 40% {
73 | -webkit-transform: scale3d(.9, .9, .9);
74 | -ms-transform: scale3d(.9, .9, .9);
75 | transform: scale3d(.9, .9, .9)
76 | }
77 | 60% {
78 | opacity: 1;
79 | -webkit-transform: scale3d(1.03, 1.03, 1.03);
80 | -ms-transform: scale3d(1.03, 1.03, 1.03);
81 | transform: scale3d(1.03, 1.03, 1.03)
82 | }
83 | 80% {
84 | -webkit-transform: scale3d(.97, .97, .97);
85 | -ms-transform: scale3d(.97, .97, .97);
86 | transform: scale3d(.97, .97, .97)
87 | }
88 | 100% {
89 | opacity: 1;
90 | -webkit-transform: scale3d(1, 1, 1);
91 | -ms-transform: scale3d(1, 1, 1);
92 | transform: scale3d(1, 1, 1)
93 | }
94 | }
95 |
96 | .bounceIn {
97 | -webkit-animation-name: bounceIn;
98 | animation-name: bounceIn
99 | }
100 |
101 | @-webkit-keyframes bounceOut {
102 | 20% {
103 | -webkit-transform: scale3d(.9, .9, .9);
104 | transform: scale3d(.9, .9, .9)
105 | }
106 | 50%,
107 | 55% {
108 | opacity: 1;
109 | -webkit-transform: scale3d(1.1, 1.1, 1.1);
110 | transform: scale3d(1.1, 1.1, 1.1)
111 | }
112 | 100% {
113 | opacity: 0;
114 | -webkit-transform: scale3d(.3, .3, .3);
115 | transform: scale3d(.3, .3, .3)
116 | }
117 | }
118 |
119 | @keyframes bounceOut {
120 | 20% {
121 | -webkit-transform: scale3d(.9, .9, .9);
122 | -ms-transform: scale3d(.9, .9, .9);
123 | transform: scale3d(.9, .9, .9)
124 | }
125 | 50%,
126 | 55% {
127 | opacity: 1;
128 | -webkit-transform: scale3d(1.1, 1.1, 1.1);
129 | -ms-transform: scale3d(1.1, 1.1, 1.1);
130 | transform: scale3d(1.1, 1.1, 1.1)
131 | }
132 | 100% {
133 | opacity: 0;
134 | -webkit-transform: scale3d(.3, .3, .3);
135 | -ms-transform: scale3d(.3, .3, .3);
136 | transform: scale3d(.3, .3, .3)
137 | }
138 | }
139 |
140 | .bounceOut {
141 | -webkit-animation-name: bounceOut;
142 | animation-name: bounceOut
143 | }
144 |
145 | /*the animation definition*/
146 |
147 | @-webkit-keyframes bounceInLeft {
148 | 0%, 100%, 60%, 75%, 90% {
149 | -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
150 | transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
151 | }
152 | 0% {
153 | opacity: 0;
154 | -webkit-transform: translate3d(-3000px, 0, 0);
155 | transform: translate3d(-3000px, 0, 0)
156 | }
157 | 60% {
158 | opacity: 1;
159 | -webkit-transform: translate3d(25px, 0, 0);
160 | transform: translate3d(25px, 0, 0)
161 | }
162 | 75% {
163 | -webkit-transform: translate3d(-10px, 0, 0);
164 | transform: translate3d(-10px, 0, 0)
165 | }
166 | 90% {
167 | -webkit-transform: translate3d(5px, 0, 0);
168 | transform: translate3d(5px, 0, 0)
169 | }
170 | 100% {
171 | -webkit-transform: none;
172 | transform: none
173 | }
174 | }
175 |
176 | @keyframes bounceInLeft {
177 | 0%, 100%, 60%, 75%, 90% {
178 | -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
179 | transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
180 | }
181 | 0% {
182 | opacity: 0;
183 | -webkit-transform: translate3d(-3000px, 0, 0);
184 | -ms-transform: translate3d(-3000px, 0, 0);
185 | transform: translate3d(-3000px, 0, 0)
186 | }
187 | 60% {
188 | opacity: 1;
189 | -webkit-transform: translate3d(25px, 0, 0);
190 | -ms-transform: translate3d(25px, 0, 0);
191 | transform: translate3d(25px, 0, 0)
192 | }
193 | 75% {
194 | -webkit-transform: translate3d(-10px, 0, 0);
195 | -ms-transform: translate3d(-10px, 0, 0);
196 | transform: translate3d(-10px, 0, 0)
197 | }
198 | 90% {
199 | -webkit-transform: translate3d(5px, 0, 0);
200 | -ms-transform: translate3d(5px, 0, 0);
201 | transform: translate3d(5px, 0, 0)
202 | }
203 | 100% {
204 | -webkit-transform: none;
205 | -ms-transform: none;
206 | transform: none
207 | }
208 | }
209 |
210 | .bounceInLeft {
211 | -webkit-animation-name: bounceInLeft;
212 | animation-name: bounceInLeft
213 | }
214 |
215 | @-webkit-keyframes bounceOutLeft {
216 | 20% {
217 | opacity: 1;
218 | -webkit-transform: translate3d(20px, 0, 0);
219 | transform: translate3d(20px, 0, 0)
220 | }
221 | 100% {
222 | opacity: 0;
223 | -webkit-transform: translate3d(-2000px, 0, 0);
224 | transform: translate3d(-2000px, 0, 0)
225 | }
226 | }
227 |
228 | @keyframes bounceOutLeft {
229 | 20% {
230 | opacity: 1;
231 | -webkit-transform: translate3d(20px, 0, 0);
232 | -ms-transform: translate3d(20px, 0, 0);
233 | transform: translate3d(20px, 0, 0)
234 | }
235 | 100% {
236 | opacity: 0;
237 | -webkit-transform: translate3d(-2000px, 0, 0);
238 | -ms-transform: translate3d(-2000px, 0, 0);
239 | transform: translate3d(-2000px, 0, 0)
240 | }
241 | }
242 |
243 | .bounceOutLeft {
244 | -webkit-animation-name: bounceOutLeft;
245 | animation-name: bounceOutLeft
246 | }
247 |
248 | @-webkit-keyframes bounceInRight {
249 | 0%, 100%, 60%, 75%, 90% {
250 | -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
251 | transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
252 | }
253 | 0% {
254 | opacity: 0;
255 | -webkit-transform: translate3d(3000px, 0, 0);
256 | transform: translate3d(3000px, 0, 0)
257 | }
258 | 60% {
259 | opacity: 1;
260 | -webkit-transform: translate3d(-25px, 0, 0);
261 | transform: translate3d(-25px, 0, 0)
262 | }
263 | 75% {
264 | -webkit-transform: translate3d(10px, 0, 0);
265 | transform: translate3d(10px, 0, 0)
266 | }
267 | 90% {
268 | -webkit-transform: translate3d(-5px, 0, 0);
269 | transform: translate3d(-5px, 0, 0)
270 | }
271 | 100% {
272 | -webkit-transform: none;
273 | transform: none
274 | }
275 | }
276 |
277 | @keyframes bounceInRight {
278 | 0%, 100%, 60%, 75%, 90% {
279 | -webkit-transition-timing-function: cubic-bezier(0.215, .61, .355, 1);
280 | transition-timing-function: cubic-bezier(0.215, .61, .355, 1)
281 | }
282 | 0% {
283 | opacity: 0;
284 | -webkit-transform: translate3d(3000px, 0, 0);
285 | -ms-transform: translate3d(3000px, 0, 0);
286 | transform: translate3d(3000px, 0, 0)
287 | }
288 | 60% {
289 | opacity: 1;
290 | -webkit-transform: translate3d(-25px, 0, 0);
291 | -ms-transform: translate3d(-25px, 0, 0);
292 | transform: translate3d(-25px, 0, 0)
293 | }
294 | 75% {
295 | -webkit-transform: translate3d(10px, 0, 0);
296 | -ms-transform: translate3d(10px, 0, 0);
297 | transform: translate3d(10px, 0, 0)
298 | }
299 | 90% {
300 | -webkit-transform: translate3d(-5px, 0, 0);
301 | -ms-transform: translate3d(-5px, 0, 0);
302 | transform: translate3d(-5px, 0, 0)
303 | }
304 | 100% {
305 | -webkit-transform: none;
306 | -ms-transform: none;
307 | transform: none
308 | }
309 | }
310 |
311 | .bounceInRight {
312 | -webkit-animation-name: bounceInRight;
313 | animation-name: bounceInRight
314 | }
315 |
316 | @-webkit-keyframes bounceOutRight {
317 | 20% {
318 | opacity: 1;
319 | -webkit-transform: translate3d(-20px, 0, 0);
320 | transform: translate3d(-20px, 0, 0)
321 | }
322 | 100% {
323 | opacity: 0;
324 | -webkit-transform: translate3d(2000px, 0, 0);
325 | transform: translate3d(2000px, 0, 0)
326 | }
327 | }
328 |
329 | @keyframes bounceOutRight {
330 | 20% {
331 | opacity: 1;
332 | -webkit-transform: translate3d(-20px, 0, 0);
333 | -ms-transform: translate3d(-20px, 0, 0);
334 | transform: translate3d(-20px, 0, 0)
335 | }
336 | 100% {
337 | opacity: 0;
338 | -webkit-transform: translate3d(2000px, 0, 0);
339 | -ms-transform: translate3d(2000px, 0, 0);
340 | transform: translate3d(2000px, 0, 0)
341 | }
342 | }
343 |
344 | .bounceOutRight {
345 | -webkit-animation-name: bounceOutRight;
346 | animation-name: bounceOutRight
347 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {PostContainer} from './containers';
3 | import { Header } from './components';
4 |
5 | class App extends Component {
6 | render() {
7 | return (
8 |
12 | );
13 | }
14 | }
15 |
16 | export default App;
--------------------------------------------------------------------------------
/src/components/Comment/Comment.css:
--------------------------------------------------------------------------------
1 | .Comment p {
2 | font-size: 1.2rem;
3 | color: #868e96;
4 | }
5 |
6 | .Comment p b {
7 | color: #212529;
8 | }
--------------------------------------------------------------------------------
/src/components/Comment/Comment.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Comment.css';
3 |
4 | const Comment = ({name, body}) => {
5 | return (
6 |
7 |
8 | {name} {body}
9 |
10 |
11 | );
12 | };
13 |
14 | export default Comment;
--------------------------------------------------------------------------------
/src/components/CommentList/CommentList.css:
--------------------------------------------------------------------------------
1 | .CommentList {
2 | list-style-type: none;
3 | background-color: #F0F0F0;
4 | padding: 1rem;
5 | border-radius: 5px;
6 | }
--------------------------------------------------------------------------------
/src/components/CommentList/CommentList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Comment} from '../';
3 |
4 | import './CommentList.css';
5 |
6 | const CommentList = ({comments}) => {
7 |
8 | // map data to components
9 | const commentList = comments.map(
10 | (comment, index)=>(
11 |
16 | )
17 | );
18 |
19 | return (
20 |
23 | );
24 | };
25 |
26 | export default CommentList;
--------------------------------------------------------------------------------
/src/components/Header/Header.css:
--------------------------------------------------------------------------------
1 | .Header {
2 | background-color: #00B5AD;
3 | color: white;
4 | font-size: 2rem;
5 | padding: 1.2rem;
6 | text-align: center;
7 | font-weight: 600;
8 | margin-bottom: 2rem;
9 | }
--------------------------------------------------------------------------------
/src/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Header.css';
3 |
4 | const Header = () => (
5 |
6 | POSTS
7 |
8 | )
9 |
10 | export default Header;
--------------------------------------------------------------------------------
/src/components/Navigate/Navigate.css:
--------------------------------------------------------------------------------
1 | .Navigate {
2 | position: relative;
3 | }
4 |
5 | .ui.button.Navigate-right-button {
6 | float: right;
7 | margin: 0px;
8 | }
9 |
10 | .Navigate-page-num {
11 | position: absolute;
12 | left: 50%;
13 | top: 50%;
14 | transform: translate(-50%, -50%);
15 | }
--------------------------------------------------------------------------------
/src/components/Navigate/Navigate.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Button} from 'semantic-ui-react';
3 | import './Navigate.css'
4 |
5 | const Navigate = ({onClick, postId, disabled}) => (
6 |
7 |
32 | );
33 |
34 | export default Navigate;
--------------------------------------------------------------------------------
/src/components/Post/Post.css:
--------------------------------------------------------------------------------
1 | .Post {
2 | padding: 1rem;
3 | margin-top: 1rem;
4 | margin-bottom: 2rem;
5 | background-color: #FAFAFA;
6 | box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
7 | }
8 |
9 | .Post h1 {
10 | font-size: 3rem;
11 | }
12 |
13 | .Post p {
14 | font-size: 2rem;
15 | }
--------------------------------------------------------------------------------
/src/components/Post/Post.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import './Post.css';
3 | import { CommentList } from '../';
4 |
5 | class Post extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | postInfo: {
10 | title: null,
11 | body: null,
12 | comments: []
13 | },
14 | animate: false,
15 | direction: 'left'
16 | }
17 | }
18 |
19 |
20 | componentWillReceiveProps (nextProps) {
21 |
22 | const { title, body, comments } = nextProps;
23 |
24 | if(this.props.postId !== nextProps.postId) {
25 | // identify the animation direction
26 | const direction = this.props.postId < nextProps.postId ? 'left' : 'right';
27 |
28 | this.setState({
29 | direction,
30 | animate: true
31 | });
32 |
33 | // sync the props to state 0.5 sec later
34 | setTimeout(
35 | () => {
36 | this.setState({
37 | postInfo: {
38 | title, body, comments
39 | },
40 | animate: false
41 | })
42 | }, 500
43 | );
44 | return;
45 | }
46 |
47 | // sync the props to state directly (this is the first post)
48 | this.setState({
49 | postInfo: {
50 | title, body, comments
51 | }
52 | })
53 | }
54 |
55 | render() {
56 | const { title, body, comments } = this.state.postInfo;
57 |
58 | const { animate, direction } = this.state;
59 |
60 | const animation = animate
61 | ? (direction==='left' ? 'bounceOutLeft' : 'bounceOutRight')
62 | : (direction==='left' ? 'bounceInRight' : 'bounceInLeft');
63 |
64 | // show nothing when data is not loaded
65 | if(title===null) return null;
66 |
67 | return (
68 |
69 |
{title}
70 |
71 | {body}
72 |
73 |
74 |
75 | );
76 | }
77 | }
78 |
79 | export default Post;
--------------------------------------------------------------------------------
/src/components/PostWrapper/PostWrapper.css:
--------------------------------------------------------------------------------
1 | .PostWrapper {
2 | width: 50rem;
3 | margin: 0 auto;
4 | }
5 |
6 | @media (max-width: 768px) {
7 | .PostWrapper {
8 | width: 90%;
9 | }
10 | }
--------------------------------------------------------------------------------
/src/components/PostWrapper/PostWrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './PostWrapper.css'
3 |
4 | const PostWrapper = ({children}) => {
5 | return (
6 |
7 | {children}
8 |
9 | );
10 | };
11 |
12 | export default PostWrapper;
--------------------------------------------------------------------------------
/src/components/Warning/Warning.css:
--------------------------------------------------------------------------------
1 | .Warning-wrapper {
2 | position: fixed;
3 | top: 50%;
4 | left: 50%;
5 | transform: translate(-50%, -50%);
6 | z-index: 10;
7 | }
8 | .Warning {
9 | white-space: nowrap;
10 | font-size: 1.5rem;
11 | padding-top: 2rem;
12 | padding-bottom: 2rem;
13 | padding-left: 2rem;
14 | padding-right: 2rem;
15 | background-color: rgba(0,0,0,0.8);
16 | border-radius: 5px;
17 | color: white;
18 | font-weight: 600;
19 | }
--------------------------------------------------------------------------------
/src/components/Warning/Warning.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import "./Warning.css";
3 |
4 | class Warning extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | closing: false
9 | };
10 | }
11 |
12 | componentWillReceiveProps (nextProps) {
13 | if(this.props.visible && !nextProps.visible) {
14 | // visible props is changing from true -> false
15 |
16 | this.setState({
17 | closing: true
18 | });
19 |
20 | // 1 sec after
21 | setTimeout(
22 | () => {
23 | this.setState({
24 | closing: false
25 | });
26 | }, 1000
27 | );
28 | }
29 | }
30 |
31 |
32 | render() {
33 | const { visible, message } = this.props;
34 | const { closing } = this.state;
35 |
36 | if(!visible && !closing) return null;
37 | return (
38 |
39 |
40 | {message}
41 |
42 |
43 | );
44 | }
45 | }
46 |
47 | export default Warning;
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import Header from './Header/Header';
2 | import PostWrapper from './PostWrapper/PostWrapper';
3 | import Navigate from './Navigate/Navigate';
4 | import Post from './Post/Post';
5 | import Comment from './Comment/Comment';
6 | import CommentList from './CommentList/CommentList';
7 | import Warning from './Warning/Warning';
8 |
9 | export {
10 | Header,
11 | PostWrapper,
12 | Navigate,
13 | Post,
14 | CommentList,
15 | Comment,
16 | Warning
17 | };
--------------------------------------------------------------------------------
/src/containers/PostContainer/PostContainer.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import { PostWrapper, Navigate, Post, Warning } from '../../components';
3 | import * as service from '../../services/post';
4 |
5 |
6 | class PostContainer extends Component {
7 |
8 | constructor(props) {
9 | super();
10 | // initializes component state
11 | this.state = {
12 | postId: 1,
13 | fetching: false, // tells whether the request is waiting for response or not
14 | post: {
15 | title: null,
16 | body: null
17 | },
18 | comments: [],
19 | warningVisibility: false
20 | };
21 | }
22 |
23 | componentDidMount() {
24 | this.fetchPostInfo(1);
25 | }
26 |
27 | showWarning = () => {
28 | this.setState({
29 | warningVisibility: true
30 | });
31 |
32 | // after 1.5 sec
33 |
34 | setTimeout(
35 | () => {
36 | this.setState({
37 | warningVisibility: false
38 | });
39 | }, 1500
40 | );
41 | }
42 |
43 |
44 | fetchPostInfo = async (postId) => {
45 | this.setState({
46 | fetching: true // requesting..
47 | });
48 |
49 | try {
50 | // wait for two promises
51 | const info = await Promise.all([
52 | service.getPost(postId),
53 | service.getComments(postId)
54 | ]);
55 |
56 | // Object destructuring Syntax,
57 | // takes out required values and create references to them
58 | const {title, body} = info[0].data;
59 |
60 | const comments = info[1].data;
61 |
62 | this.setState({
63 | postId,
64 | post: {
65 | title,
66 | body
67 | },
68 | comments,
69 | fetching: false // done!
70 | });
71 |
72 | } catch(e) {
73 | // if err, stop at this point
74 | this.setState({
75 | fetching: false
76 | });
77 | this.showWarning();
78 | }
79 | }
80 |
81 |
82 | handleNavigateClick = (type) => {
83 | const postId = this.state.postId;
84 |
85 | if(type === 'NEXT') {
86 | this.fetchPostInfo(postId+1);
87 | } else {
88 | this.fetchPostInfo(postId-1);
89 | }
90 | }
91 |
92 | render() {
93 | const {postId, fetching, post, comments, warningVisibility} = this.state;
94 |
95 | return (
96 |
97 |
102 |
108 |
109 |
110 | );
111 | }
112 | }
113 |
114 | export default PostContainer;
--------------------------------------------------------------------------------
/src/containers/index.js:
--------------------------------------------------------------------------------
1 | import PostContainer from './PostContainer/PostContainer.js';
2 |
3 | export {
4 | PostContainer
5 | };
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #E0E0E0;
3 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import 'semantic-ui-css/semantic.min.css';
5 | import './index.css';
6 | import './Animation.css';
7 | import Promise from 'promise-polyfill';
8 |
9 | // To add to window
10 | if (!window.Promise) {
11 | window.Promise = Promise;
12 | }
13 |
14 | ReactDOM.render(
15 | ,
16 | document.getElementById('root')
17 | );
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/services/post.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export function getPost(postId) {
4 | return axios.get('https://jsonplaceholder.typicode.com/posts/' + postId);
5 | }
6 |
7 | export function getComments(postId) {
8 | return axios.get(`https://jsonplaceholder.typicode.com/posts/${postId}/comments`)
9 | }
--------------------------------------------------------------------------------