├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── client │ ├── components │ │ ├── App.js │ │ ├── blog │ │ │ ├── Blog.js │ │ │ ├── BlogAdd.js │ │ │ ├── BlogContainer.js │ │ │ ├── Blogs.js │ │ │ ├── BlogsContainer.js │ │ │ └── api │ │ │ │ ├── actions.js │ │ │ │ └── state.js │ │ ├── common │ │ │ ├── Layout.js │ │ │ ├── Loading.js │ │ │ └── NotFound.js │ │ └── home │ │ │ ├── About.js │ │ │ └── Home.js │ ├── index.js │ └── setup │ │ ├── routes.js │ │ └── store.js ├── server │ ├── index.js │ └── views │ │ └── index.js └── static │ ├── favicon.ico │ └── universal-react.png └── webpack.config.babel.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["@babel/plugin-transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | src/static/js/bundle.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Atul Yadav 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🌐 Universal React 2 | Simple universal React application with server side rendering. 3 | 4 | Built using latest version of React (v16), React Router (v5+), Redux (v7+), Express (v5+), Webpack (v4+), Babel Preset ES6 5 | 6 | ## 📝 Features 7 | - [x] List blogs (async API call using `axios`) 8 | - [x] View single blog 9 | - [x] Add blog 10 | - [x] Container Components ([read here](https://medium.com/@learnreact/container-components-c0e67432e005)) 11 | - [x] Server Side Rendering 12 | - [x] Cache data in client `state` to prevent re-fetch 13 | 14 | ## ▶️ Running 15 | - Clone repo `git clone git@github.com:atulmy/universal-react.git universal-react` 16 | - Install NPM modules `cd universal-react` and `npm install` 17 | - Run `npm run start` 18 | 19 | ## 📦 Packages Used 20 | 21 | ### dependencies 22 | - **react** (Library for building user interfaces) 23 | - **react-dom** (React package for working with the DOM) 24 | - **react-router-dom** (A complete routing library for React) 25 | - **redux** (Predictable state container for JavaScript apps) 26 | - **redux-thunk** (Thunk middleware for Redux) 27 | - **react-redux** (Official React bindings for Redux) 28 | - **react-helmet** (Manage all of your changes to the document head) 29 | - **express** (Fast, unopinionated, minimalist web framework) 30 | - **axios** (Promise based HTTP client for the browser and node.js) 31 | 32 | ## 📚 Resources 33 | - Universal JavaScript Web Applications with React - Luciano Mammino ([YouTube](https://t.co/HVXd0HMOlC)) 34 | - Container Components - ([Medium Post](https://medium.com/@learnreact/container-components-c0e67432e005)) 35 | - React Router 4 SSR example - Ryan Florence ([Gist](https://gist.github.com/ryanflorence/efbe562332d4f1cc9331202669763741/)) 36 | - Start learning by looking at sample codes: [#LearnByExamples](https://github.com/topics/learn-by-examples) 37 | 38 | ## ⭐ Showcase 39 | Following projects have been built with or inspired from [universal-react](https://github.com/atulmy/universal-react/) 40 | - Crate - Get monthly subscription of trendy clothes and accessories - [GitHub](https://github.com/atulmy/crate) 41 | - HIRESMART - Application to streamline hiring process - [GitHub](https://github.com/atulmy/hire-smart) 42 | - Would really appreciate if you add your project to this list by sending a PR 43 | 44 | ## 🎩 Author 45 | Atul Yadav - [GitHub](https://github.com/atulmy) • [Twitter](https://twitter.com/atulmy) 46 | 47 | ## 📜 License 48 | Copyright (c) 2017 Atul Yadav http://github.com/atulmy 49 | 50 | The MIT License (http://www.opensource.org/licenses/mit-license.php) 51 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": { 3 | "restart": "npm run build" 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "universal", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "export NODE_ENV=development && webpack -d", 8 | "start": "export NODE_ENV=development && npm run build && nodemon --ignore src/static/ --exec babel-node -- src/server/index.js", 9 | "build:prod": "export NODE_ENV=production && webpack -p", 10 | "start:prod": "export NODE_ENV=production && npm run build:prod && nodemon --ignore src/static/ --exec babel-node -- src/server/index.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/core": "7.13.1", 17 | "@babel/node": "7.13.0", 18 | "@babel/plugin-transform-runtime": "7.13.7", 19 | "@babel/preset-env": "7.13.5", 20 | "@babel/preset-react": "7.12.13", 21 | "babel-loader": "8.2.2", 22 | "nodemon": "2.0.7", 23 | "webpack": "4.44.2", 24 | "webpack-cli": "3.3.12" 25 | }, 26 | "dependencies": { 27 | "@babel/runtime": "7.13.7", 28 | "axios": "0.21.1", 29 | "express": "5.0.0-alpha.7", 30 | "react": "17.0.1", 31 | "react-dom": "17.0.1", 32 | "react-helmet": "^6.1.0", 33 | "react-redux": "^7.2.2", 34 | "react-router-dom": "^5.2.0", 35 | "redux": "^4.0.5", 36 | "redux-thunk": "^2.3.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/client/components/App.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | import React from 'react' 3 | import { Route, Switch } from 'react-router-dom' 4 | 5 | // App Imports 6 | import routes from '../setup/routes' 7 | import Layout from './common/Layout' 8 | import NotFound from './common/NotFound' 9 | 10 | const App = () => ( 11 | 12 | 13 | {routes.map((route, index) => ( 14 | // pass in the initialData from the server for this specific route 15 | 16 | ))} 17 | 18 | 19 | 20 | 21 | ) 22 | 23 | export default App 24 | -------------------------------------------------------------------------------- /src/client/components/blog/Blog.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | import React from 'react' 3 | import { Helmet } from 'react-helmet' 4 | 5 | const Blog = ({ blog: { title, body } }) => { 6 | return ( 7 |
8 | 9 | Blog { title } 10 | 11 | 12 |

{ title }

13 | 14 |

{ body }

15 |
16 | ) 17 | } 18 | 19 | export default Blog 20 | -------------------------------------------------------------------------------- /src/client/components/blog/BlogAdd.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | import React, { useState } from 'react' 3 | import { Helmet } from "react-helmet" 4 | 5 | // App Imports 6 | import { actionBlogAdd } from './api/actions' 7 | 8 | // Component 9 | const BlogAdd = () => { 10 | // state 11 | const [isSubmitting, isSubmittingToggle] = useState(false) 12 | const [message, setMessage] = useState(false) 13 | const [blog, setBlog] = useState({ 14 | userId: 1, // Example 15 | title: '', 16 | body: '' 17 | }) 18 | 19 | // on submit 20 | const onSubmit = async event => { 21 | event.preventDefault() 22 | 23 | isSubmittingToggle(true) 24 | 25 | try { 26 | const { data } = await actionBlogAdd(blog) 27 | 28 | if(data) { 29 | setMessage(`Blog added successfully with id ${ data.id }`) 30 | } 31 | } catch (error) { 32 | setMessage(`Error ${ error.message }. Please try again.`) 33 | } finally { 34 | isSubmittingToggle(false) 35 | } 36 | } 37 | 38 | // on change 39 | const onChange = event => { 40 | setBlog({ ...blog, [event.target.name]: event.target.value}) 41 | } 42 | 43 | // render 44 | return ( 45 |
46 | 47 | Blog Add 48 | 49 | 50 |

Blog Add

51 | 52 |
53 |

54 | 63 |

64 | 65 |

66 |