Repository: ReactTraining/react-router-5-course
Branch: master
Commit: c27c2e37826f
Files: 64
Total size: 48.6 KB
Directory structure:
gitextract_xcgoxx4g/
├── .babelrc
├── .eslintrc
├── .gitignore
├── .prettierrc
├── README.md
├── config/
│ ├── env.js
│ ├── setup.js
│ ├── webpack.config.dev.js
│ ├── webpack.config.production.js
│ └── webpack.devserver.config.js
├── getbranches.sh
├── package.json
├── public/
│ ├── index.html
│ └── static/
│ └── css/
│ └── main.css
└── src/
├── auth/
│ └── Login.js
├── database.json
├── index.js
├── layouts/
│ ├── AccountSubLayout.js
│ ├── AuthorizedLayout.js
│ ├── ProjectSubLayout.js
│ └── UnauthorizedLayout.js
├── projects/
│ ├── AddProject.js
│ ├── Overview.js
│ ├── authentication/
│ │ ├── AuthenticationLayout.js
│ │ ├── SignInMethods.js
│ │ ├── Templates.js
│ │ └── Users.js
│ └── database/
│ ├── Data.js
│ ├── DataIndexes.js
│ ├── DatabaseHome.js
│ ├── DatabaseLayout.js
│ └── Rules.js
├── styles/
│ ├── layouts/
│ │ ├── account-sub-layout.scss
│ │ ├── app.scss
│ │ └── project-sub-layout.scss
│ ├── main.scss
│ ├── mixins/
│ │ └── layout.scss
│ ├── reset.scss
│ ├── ui/
│ │ ├── authorized-primary-header.scss
│ │ ├── button.scss
│ │ ├── card.scss
│ │ ├── input-fields.scss
│ │ ├── page-header.scss
│ │ ├── panels/
│ │ │ ├── panel.scss
│ │ │ ├── recent-projects.scss
│ │ │ └── welcome-to-firebase.scss
│ │ ├── project-sidebar.scss
│ │ └── tiles.scss
│ ├── utilities.scss
│ └── vars.scss
├── ui/
│ ├── AuthorizedPrimaryHeader.js
│ ├── Card.js
│ ├── PageHeader.js
│ ├── PageHeaderTabs.js
│ ├── Panel.js
│ ├── ProjectSidebar.js
│ └── Tiles.js
├── utils/
│ ├── AuthUser.js
│ ├── AuthorizedRoute.js
│ ├── ProjectContext.js
│ ├── Router.js
│ └── api.js
└── wireframe/
├── App.js
└── wireframes.scss
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
================================================
FILE: .eslintrc
================================================
{
"parser": "babel-eslint",
"extends": ["eslint:recommended", "plugin:react/recommended"],
"env": {
"browser": true,
"node": true,
"es6": true
},
"rules": {
"react/prop-types": "off",
"no-console": "off",
"react/no-children-prop": "off",
"jsx-quotes": ["error", "prefer-double"],
"react/no-unescaped-entities": "off",
"no-unused-vars": "off"
}
}
================================================
FILE: .gitignore
================================================
*.log
.env
.env.production
node_modules
build
npm-debug.log
================================================
FILE: .prettierrc
================================================
tabWidth: 2
semi: false
singleQuote: true
printWidth: 120
trailingComma: true
jsxBracketSameLine: true
================================================
FILE: README.md
================================================
# React Router 5 Course Material
React Router 5 is out and we have created this course so you can learn all about it. The course covers basic and advanced topics. We'll be building a Firebase-looking app that has complex nested layouts and interesting problems to solve.

## What's New?
If you're wondering what's new in React Router 5? Not a lot, except internal optimizations and fixes for React 16.x. So you could also think of this as a React Router 4 course since the API is the same. [See more info on the React Router 5 release](https://reacttraining.com/blog/react-router-v5/).
## Download and Install
After downloading the repository from Github, enter the following commands into your command line from the project folder:
```bash
npm install
npm start
```
Then go to [localhost:8000](http://localhost:8000)
> Be sure to see notes on Lesson Branches below...
## Each Lesson is a Git Branch
To view the code for a given lesson, _checkout_ the appropriate branch name. The branch will have the finished code from that lesson.
### Installing lesson branches
All the branches are checked out to your local machine automatically when you do `npm install`. Just do a `git branch` to verify and see all branches after. If they didn't appear, try running `npm run branches` to download all the branches.
To view a branch: `git checkout [branch-name]`
Branch names are minimal for easy typing:
- **01-basics** - JSX Routing with `BrowserRouter` and `Route`
- **02-basics** - Route Matching - Inclusive vs Exclusive (exact) and Switch
- **03-basics** - `Link` (anchors)
- **04-basics** - `BrowserRouter` vs `HashRouter`
- **05-basics** - Dynamic (Parameter) Matching
- **06-basics** - Nested Layout Strategy
- **07-basics** - `match.url`
- **08-basics** - `match.path`
- **09-basics** - `NavLink`
- **10-basics** - `withRouter` HoC
- **11-basics** - Programmatic Navigation (History Object)
- **12-basics** - URL Query Strings
- **13-advanced** - Route Render Methods
- **14-advanced** - React Router - Just Components ™
- **15-advanced** - Authentication Strategy with Context
- **16-advanced** - Authenticated Routes (Dynamic Routes)
- **17-advanced** - Navigation With State
- **18-advanced** - Prompt Before Route Changes
- **19-advanced** - Animating Route Changes (Part One)
- **20-advanced** - Animating Route Changes (Part Two)
## Fake Database
Just so we can mimic some data and pretend it's asynchronous, there's a `src/database.json`. Feel free to add more "Firebase Projects" if you want.
## The CSS
I used `className`. Who cares, it keeps the styling clutter out of the JS files since this is teaching material for routing.
## Code Organization
In `/src` you'll see:
- `layouts` for highly re-usable app-wide layouts
- `styles` for Sass modules
- `ui` is where I like to put re-usable "leaf-types" of components
- `utils` is a catch all for React components that are more utilitarian in nature (and less UI in nature) and other general utils.
Any other folders in `/src` is a section of the site, like `/auth` and `/projects` which correspond to `localhost:8000/auth` etc. I guess I could organize those into a `/pages` folder, but who likes deep nesting anyways?
================================================
FILE: config/env.js
================================================
const fs = require('fs')
const path = require('path')
const React = require('react')
const packageJSON = require('../package.json')
const NODE_ENV = process.env.NODE_ENV
if (!NODE_ENV) {
throw new Error(
'The NODE_ENV environment variable is required but was not specified.'
)
}
// Create list of possible env files
const envPath = path.resolve(process.cwd(), '.env')
const dotenvFiles = [
`${envPath}.${NODE_ENV}`,
envPath,
]
// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.
// https://github.com/motdotla/dotenv
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv').config({
path: dotenvFile,
})
}
})
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
// injected into the application via DefinePlugin in Webpack configuration.
const REACT_APP = /^REACT_APP_/i
function getClientEnvironment() {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key]
return env
},
{
// Useful for determining whether we’re running in production mode.
// Most importantly, it switches React into the correct mode.
NODE_ENV: process.env.NODE_ENV || 'development',
// Determines where static assets are located. In development, Webpack
// DevServer puts them in a virtual folder called `/static`. But we can
// customise that with .env for production in situations where our assets
// might be on a CDN
STATIC_ASSET_URL: process.env.STATIC_ASSET_URL || '/static',
REACT_VERSION: React.version,
APP_VERSION: packageJSON.version
}
)
// Stringify all values so we can feed into Webpack DefinePlugin
const stringified = {
'process.env': Object.keys(raw).reduce(
(env, key) => {
env[key] = JSON.stringify(raw[key])
return env
},
{}
),
}
return { raw, stringified }
}
module.exports = getClientEnvironment
================================================
FILE: config/setup.js
================================================
/* global process */
if (typeof Promise === 'undefined') {
// Rejection tracking prevents a common issue where React gets into an
// inconsistent state due to an error, but it gets swallowed by a Promise,
// and the user has no idea what causes React's erratic future behavior.
// Only do this in dev for performance reasons
if (process.env.NODE_ENV) {
require('promise/lib/rejection-tracking').enable()
}
window.Promise = require('promise/lib/es6-extensions.js')
}
================================================
FILE: config/webpack.config.dev.js
================================================
// Do this as the first thing so that any code reading it knows the right env.
process.env.NODE_ENV = 'development'
const getClientEnvironment = require('./env')
const env = getClientEnvironment()
const webpack = require('webpack')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpackDevServerConfig = require('./webpack.devserver.config.js')
module.exports = {
devServer: webpackDevServerConfig,
// Enhanced dev support (like correct line numbers on errors)
devtool: 'source-map',
entry: [path.resolve(process.cwd(), 'config/setup.js'), path.resolve(process.cwd(), 'src/index.js')],
output: {
// This does not produce a real file. It's just the virtual path that is
// served by WebpackDevServer in development. This is the JS bundle
// containing code from all our entry points, and the Webpack runtime.
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
// Where to create the build
path: path.resolve(process.cwd(), 'build'),
publicPath: '/',
},
module: {
rules: [
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.js$/,
enforce: 'pre',
exclude: /node_modules/,
loader: 'eslint-loader',
},
// Process JS with Babel
{
test: /\.js$/,
exclude: /node_modules/,
use: {
options: {
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
},
loader: 'babel-loader',
},
},
{
test: /\.scss$/,
use: [
'style-loader', // creates style nodes from JS strings
'css-loader', // translates CSS into CommonJS
'sass-loader', // compiles Sass to CSS, using Node Sass by default
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
// Path to HTML file
template: './public/index.html',
// Variables listed here in the configurations for HtmlWebpackPlugin become ejs
// variables for interpolation in the HTML file, accessible with
// <%- htmlWebpackPlugin.options.[varName] %>. We put our env variables in for HTML access
env: env.raw,
}),
// Make global variables available to the application. We use this to
// set process.env vars in the front-end
new webpack.DefinePlugin(env.stringified),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
}
================================================
FILE: config/webpack.config.production.js
================================================
// Do this as the first thing so that any code reading it knows the right env.
process.env.NODE_ENV = 'production'
const getClientEnvironment = require('./env')
const env = getClientEnvironment()
const webpack = require('webpack')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
// Don't attempt to continue if there are any errors.
bail: true,
entry: [path.resolve(process.cwd(), 'config/setup.js'), path.resolve(process.cwd(), 'src/index.js')],
output: {
filename: 'static/js/bundle.js',
// Where to create the build
path: path.resolve(process.cwd(), 'build'),
publicPath: '/',
},
module: {
rules: [
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.js$/,
enforce: 'pre',
exclude: /node_modules/,
loader: 'eslint-loader',
},
// Process JS with Babel
{
test: /\.js$/,
exclude: /node_modules/,
use: {
options: { compact: true },
loader: 'babel-loader',
},
},
{
test: /\.scss$/,
use: [
'style-loader', // creates style nodes from JS strings
'css-loader', // translates CSS into CommonJS
'sass-loader', // compiles Sass to CSS, using Node Sass by default
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
// Path to HTML file
template: './public/index.html',
// Minification options
minify: {
removeComments: false,
collapseWhitespace: false,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
hash: true,
// Variables listed here in the configurations for HtmlWebpackPlugin become ejs
// variables for interpolation in the HTML file, accessible with
// <%- htmlWebpackPlugin.options.[varName] %>. We put our env variables in for HTML access
env: env.raw,
}),
// Copy static assets from public/static to build/static
new CopyWebpackPlugin([
{
from: path.resolve(process.cwd(), 'public/static'),
to: 'static',
},
]),
// Make global variables available to the application. We use this to
// set process.env vars in the front-end
new webpack.DefinePlugin(env.stringified),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
// Don't show performance hints at build time. They just tell us to use code splitting
performance: {
hints: false,
},
}
================================================
FILE: config/webpack.devserver.config.js
================================================
const path = require('path')
module.exports = {
// Tell the server where to serve static content from.
contentBase: path.join(process.cwd(), 'public'),
// By default files from `contentBase` will not trigger a page reload.
watchContentBase: true,
// Open a web browser.
open: true,
// Overlay errors in the browser
overlay: true,
// Enable gzip compression of generated files.
compress: true,
// Port to view dev env (localhost:3010)
port: 8000,
// Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting.
clientLogLevel: 'none',
// Show less info in the terminal (errors only)
stats: "errors-only",
// Reportedly, this avoids CPU overload on some systems.
// https://github.com/facebookincubator/create-react-app/issues/293
watchOptions: {
ignored: /node_modules/,
},
// Since development mode thinks of Webpack DevServer as the place to get index.html
// instead of a file system or a Node backend, we need to tell DevServer to always
// serve index.html for refreshes since we have an SPA
historyApiFallback: true
}
================================================
FILE: getbranches.sh
================================================
for branch in `git branch -a | grep remotes | grep -v HEAD | grep -v master `; do
git branch --track ${branch#remotes/origin/} $branch
done
================================================
FILE: package.json
================================================
{
"name": "react-router-course",
"version": "0.1.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --mode development --config config/webpack.config.dev.js",
"build": "webpack --mode production --config config/webpack.config.production.js",
"branches": "sh getbranches.sh",
"postinstall": "npm run branches"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^8.2.3",
"babel-loader": "^8.0.4",
"copy-webpack-plugin": "^4.5.1",
"css-loader": "^2.1.1",
"dotenv": "^5.0.1",
"eslint": "^4.19.1",
"eslint-loader": "^2.0.0",
"eslint-plugin-react": "^7.7.0",
"fs-extra": "^5.0.0",
"html-webpack-plugin": "^3.2.0",
"sass": "^1.34.1",
"sass-loader": "^7.3.1",
"style-loader": "^0.23.1",
"webpack": "^4.28.0",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.2.1"
},
"dependencies": {
"@babel/runtime": "^7.2.0",
"classnames": "^2.2.6",
"js-cookie": "^2.2.0",
"promise": "^8.0.3",
"prop-types": "^15.6.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-flex-columns": "^0.4.2",
"react-router-dom": "^5.1.2",
"react-transition-group": "^4.1.1"
}
}
================================================
FILE: public/index.html
================================================
React Router 5 Course
================================================
FILE: public/static/css/main.css
================================================
/* Empty */
================================================
FILE: src/auth/Login.js
================================================
import React, { useState } from 'react'
import Panel from '../ui/Panel'
import Card from '../ui/Card'
import { useAuthUser } from '../utils/AuthUser'
// Fake API Network Call
const apiLogin = (username, password) => {
return new Promise((resolve, reject) => {
if (username === 'react' && password === 'react') {
resolve()
} else {
reject()
}
})
}
const Login = ({ history }) => {
const { setLogged } = useAuthUser()
const [errorMessage, setErrorMessage] = useState()
function handleSubmit(e) {
e.preventDefault()
const [usernameNode, passwordNode] = e.target.elements
apiLogin(usernameNode.value, passwordNode.value)
.then(() => {
setLogged(true)
history.push('/projects')
})
.catch(() => {
setLogged(false)
setErrorMessage('Invalid')
})
}
return (
Welcome to Firebase, almost...
This mock application will demonstrate React Router with nested layouts and a strategy for authenticated
(protected) routes.
The username is react and the password is react
{errorMessage &&
{errorMessage}
}
)
}
export default Login
================================================
FILE: src/database.json
================================================
{
"projects": [
{
"id": "react-training",
"name": "ReactTraining.com"
},
{
"id": "secret-project",
"name": "Secret Project"
}
]
}
================================================
FILE: src/index.js
================================================
import React from 'react'
import ReactDOM from 'react-dom'
import { Switch, Route, Redirect } from 'react-router-dom'
import Router from './utils/Router'
import { AuthUserProvider } from './utils/AuthUser'
import AuthorizedRoute from './utils/AuthorizedRoute'
import UnauthorizedLayout from './layouts/UnauthorizedLayout'
import AuthorizedLayout from './layouts/AuthorizedLayout'
import './styles/main.scss'
const App = () => (
)
ReactDOM.render(, document.getElementById('root'))
================================================
FILE: src/layouts/AccountSubLayout.js
================================================
import React, { useEffect, useState, Fragment } from 'react'
import { Link, Route } from 'react-router-dom'
import Panel from '../ui/Panel'
import Tiles from '../ui/Tiles'
import Card from '../ui/Card'
import AddProject from '../projects/AddProject'
import { getProjects } from '../utils/api'
const AccountSubLayout = ({ match, history }) => {
const [projects, setProjects] = useState(false)
useEffect(() => {
let isCurrent = true
getProjects().then(projects => {
if (isCurrent) {
setProjects(projects)
}
})
return () => (isCurrent = false)
}, [])
return (
Almost Firebase!
Tools from Google for developing great apps, engaging with
your users, and earning more through mobile ads.
// Just a wrapper around NavLink
export const Tab = ({ ...rest }) =>
================================================
FILE: src/ui/Panel.js
================================================
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const Panel = ({ children, useMaxWidth, className }) => (
{children}
)
Panel.defaultProps = {
useMaxWidth: true,
}
Panel.propTypes = {
useMaxWidth: PropTypes.bool,
}
export default Panel
================================================
FILE: src/ui/ProjectSidebar.js
================================================
import React from 'react'
import { NavLink, withRouter } from 'react-router-dom'
const ProjectSidebar = ({ match }) => (
)
export default withRouter(ProjectSidebar)
================================================
FILE: src/ui/Tiles.js
================================================
import React from 'react'
const Tiles = ({ children, ...rest }) => {
// The reason for wrapping in an arbitrary div is for one, we need to ensure
// the first child of grid is under our control without affecting the children
// passed in, but also grid items will grow vertically to the height of their
// row siblings, we might not want that for the children passed in, but we can
// do that to our arbitrary div
return (