├── .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 |
9 |
10 | 11 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | } --------------------------------------------------------------------------------