├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── config ├── webpack.base.config.js └── webpack.prod.config.js ├── enzyme.config.js ├── jest.config.js ├── package.json ├── prettier.config.js ├── server ├── index.js └── routes │ └── index.js └── src ├── animations ├── Fade.js └── Progress.js ├── context └── index.js ├── helpers └── Section.js ├── hooks ├── UseContextExample.js ├── UseCustomHooks.js ├── UseEffectExample.js ├── UseReducerExample.js └── UseStateExample.js ├── index.html ├── index.js ├── static └── public │ └── style.css └── styles.scss /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-syntax-dynamic-import", 8 | "@babel/plugin-proposal-class-properties", 9 | "@babel/plugin-proposal-export-namespace-from", 10 | "@babel/plugin-proposal-throw-expressions" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*.md] 7 | trim_trailing_whitespace = false 8 | 9 | [*.js] 10 | trim_trailing_whitespace = true 11 | 12 | # Unix-style newlines with a newline ending every file 13 | [*] 14 | indent_style = space 15 | indent_size = 2 16 | end_of_line = lf 17 | charset = utf-8 18 | insert_final_newline = true 19 | max_line_length = 80 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /.git 2 | /.vscode 3 | /dist 4 | node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | env: { 5 | es6: true, 6 | browser: true, 7 | node: true, 8 | }, 9 | extends: ['airbnb', 'plugin:jest/recommended', 'jest-enzyme'], 10 | plugins: [ 11 | 'babel', 12 | 'import', 13 | 'jsx-a11y', 14 | 'react', 15 | 'prettier', 16 | ], 17 | parser: 'babel-eslint', 18 | parserOptions: { 19 | ecmaVersion: 6, 20 | sourceType: 'module', 21 | ecmaFeatures: { 22 | jsx: true 23 | } 24 | }, 25 | // settings: { 26 | // 'import/resolver': { 27 | // webpack: { 28 | // config: path.join(__dirname, 'config', 'webpack.base.config.js'), 29 | // }, 30 | // }, 31 | // }, 32 | rules: { 33 | 'linebreak-style': 'off', // Don't play nicely with Windows. 34 | 35 | 'arrow-parens': 'off', // Incompatible with prettier 36 | 'object-curly-newline': 'off', // Incompatible with prettier 37 | 'no-mixed-operators': 'off', // Incompatible with prettier 38 | 'arrow-body-style': 'off', // Not our taste? 39 | 'function-paren-newline': 'off', // Incompatible with prettier 40 | 'no-plusplus': 'off', 41 | 'space-before-function-paren': 0, // Incompatible with prettier 42 | 43 | 'max-len': ['error', 80, 2, { ignoreUrls: true, }], // airbnb is allowing some edge cases 44 | 'no-console': 'error', // airbnb is using warn 45 | 'no-alert': 'error', // airbnb is using warn 46 | 47 | 'no-param-reassign': 'off', // Not our taste? 48 | "radix": "off", // parseInt, parseFloat radix turned off. Not my taste. 49 | 50 | 'react/require-default-props': 'off', // airbnb use error 51 | 'react/forbid-prop-types': 'off', // airbnb use error 52 | 'react/jsx-filename-extension': ['error', { extensions: ['.js'] }], // airbnb is using .jsx 53 | 54 | 'prefer-destructuring': 'off', 55 | 56 | 'react/no-find-dom-node': 'off', // I don't know 57 | 'react/no-did-mount-set-state': 'off', 58 | 'react/no-unused-prop-types': 'off', // Is still buggy 59 | 'react/jsx-one-expression-per-line': 'off', 60 | 61 | "jsx-a11y/anchor-is-valid": ["error", { "components": ["Link"], "specialLink": ["to"] }], 62 | "jsx-a11y/label-has-for": [2, { 63 | "required": { 64 | "every": ["id"] 65 | } 66 | }], // for nested label htmlFor error 67 | 68 | 'prettier/prettier': ['error'], 69 | }, 70 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NPM packages folder. 2 | node_modules 3 | 4 | # Build files 5 | dist/ 6 | 7 | # lock files 8 | yarn.lock 9 | 10 | # Logs 11 | logs 12 | *.log 13 | npm-debug.log* 14 | 15 | # node-waf configuration 16 | .lock-wscript 17 | 18 | # Optional npm cache directory 19 | .npm 20 | 21 | # Optional REPL history 22 | .node_repl_history 23 | 24 | # Jest Coverage 25 | coverage -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.rulers": [80], 4 | "editor.formatOnSave": false, 5 | "eslint.autoFixOnSave": true, 6 | "editor.showFoldingControls": "always", 7 | "editor.folding": true, 8 | "editor.foldingStrategy": "indentation", 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ### Starter Kit 4 | The demo uses starter kit https://github.com/adeelibr/react-starter-kit 5 | 6 | ### Demo Includes 7 | * Example of useState 8 | * Example of useEffect 9 | * Example of useContext 10 | * Example of custom hooks 11 | * custom hook useWindowWidth 12 | * custom hook useDocumentTitle 13 | * custom hook useFormInput 14 | * Example of useReducer 15 | * Examples of useSpring (react-spring) hooks 16 | * Progress bar example 17 | * Fade example 18 | 19 | ### How to run 20 | 21 | ``` 22 | $ yarn 23 | $ yarn start // development 24 | $ yarn build // production build 25 | ``` 26 | 27 | ### How to use 28 | 29 | In `src/index.js` 30 | 31 | There is an object called `const VISIBLE = {};` with a list of all examples, all you have to do is, to see a particular example. Let's say you want to see `react-spring` examples of hooks, do the following in your `App` state 32 | ``` 33 | state = { 34 | isVisible: [ 35 | VISIBLE.IS_REACT_SPRING_1_VISIBLE, 36 | VISIBLE.IS_REACT_SPRING_2_VISIBLE, 37 | ], 38 | }; 39 | ``` 40 | And that's pretty much it, just add a `VISIBLE.SOME_EXMAPLE` to your `isVisible` state array, your app will hot reload with the new UI information. -------------------------------------------------------------------------------- /config/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const webpack = require('webpack'); 4 | const merge = require("webpack-merge"); 5 | 6 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 7 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 8 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 9 | 10 | const APP_DIR = path.resolve(__dirname, '../src'); 11 | 12 | module.exports = env => { 13 | const { PLATFORM, VERSION } = env; 14 | return merge([ 15 | { 16 | entry: ['@babel/polyfill', APP_DIR], 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.js$/, 21 | exclude: /node_modules/, 22 | use: { 23 | loader: 'babel-loader' 24 | } 25 | }, 26 | { 27 | test: /\.scss$/, 28 | use: [ 29 | PLATFORM === 'production' ? MiniCssExtractPlugin.loader : 'style-loader', 30 | 'css-loader', 31 | 'sass-loader' 32 | ] 33 | } 34 | ] 35 | }, 36 | plugins: [ 37 | new HtmlWebpackPlugin({ 38 | template: './src/index.html', 39 | filename: './index.html' 40 | }), 41 | new webpack.DefinePlugin({ 42 | 'process.env.VERSION': JSON.stringify(env.VERSION), 43 | 'process.env.PLATFORM': JSON.stringify(env.PLATFORM) 44 | }), 45 | new CopyWebpackPlugin([ { from: 'src/static' } ]), 46 | ], 47 | // output: { 48 | // filename: '[name].bundle.js', 49 | // chunkFilename: '[name].chunk.bundle.js', 50 | // path: path.resolve(__dirname, '..', 'dist'), 51 | // publicPath: '/', 52 | // }, 53 | } 54 | ]) 55 | }; 56 | -------------------------------------------------------------------------------- /config/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const merge = require('webpack-merge'); 3 | // Plugins 4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 5 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 7 | const Visualizer = require('webpack-visualizer-plugin'); 8 | // Configs 9 | const baseConfig = require('./webpack.base.config'); 10 | 11 | const prodConfiguration = env => { 12 | return merge([ 13 | { 14 | optimization: { 15 | // runtimeChunk: 'single', 16 | // splitChunks: { 17 | // cacheGroups: { 18 | // vendor: { 19 | // test: /[\\/]node_modules[\\/]/, 20 | // name: 'vendors', 21 | // chunks: 'all' 22 | // } 23 | // } 24 | // }, 25 | minimizer: [new UglifyJsPlugin()], 26 | }, 27 | plugins: [ 28 | new MiniCssExtractPlugin(), 29 | new OptimizeCssAssetsPlugin(), 30 | new Visualizer({ filename: './statistics.html' }) 31 | ], 32 | }, 33 | ]); 34 | } 35 | 36 | module.exports = env => { 37 | return merge(baseConfig(env), prodConfiguration(env)); 38 | } -------------------------------------------------------------------------------- /enzyme.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | /** Used in jest.config.js */ 4 | 5 | import { configure } from 'enzyme'; 6 | import Adapter from 'enzyme-adapter-react-16'; 7 | 8 | configure({ adapter: new Adapter() }); 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | // For a detailed explanation regarding each configuration property, visit: 3 | // https://jestjs.io/docs/en/configuration.html 4 | 5 | module.exports = { 6 | // All imported modules in your tests should be mocked automatically 7 | // automock: false, 8 | 9 | // Stop running tests after the first failure 10 | // bail: false, 11 | 12 | // Respect "browser" field in package.json when resolving modules 13 | // browser: false, 14 | 15 | // The directory where Jest should store its cached dependency information 16 | // cacheDirectory: "C:\\Users\\VenD\\AppData\\Local\\Temp\\jest", 17 | 18 | // Automatically clear mock calls and instances between every test 19 | clearMocks: true, 20 | 21 | // Indicates whether the coverage information should be collected while executing the test 22 | // collectCoverage: false, 23 | 24 | // An array of glob patterns indicating a set of files for which coverage information should be collected 25 | collectCoverageFrom: ['src/modules/**/*.{js,jsx,mjs}'], 26 | 27 | // The directory where Jest should output its coverage files 28 | coverageDirectory: 'coverage', 29 | 30 | // An array of regexp pattern strings used to skip coverage collection 31 | // coveragePathIgnorePatterns: [ 32 | // "\\\\node_modules\\\\" 33 | // ], 34 | 35 | // A list of reporter names that Jest uses when writing coverage reports 36 | // coverageReporters: [ 37 | // "json", 38 | // "text", 39 | // "lcov", 40 | // "clover" 41 | // ], 42 | 43 | // An object that configures minimum threshold enforcement for coverage results 44 | // coverageThreshold: null, 45 | 46 | // Make calling deprecated APIs throw helpful error messages 47 | // errorOnDeprecated: false, 48 | 49 | // Force coverage collection from ignored files usin a array of glob patterns 50 | // forceCoverageMatch: [], 51 | 52 | // A path to a module which exports an async function that is triggered once before all test suites 53 | // globalSetup: null, 54 | 55 | // A path to a module which exports an async function that is triggered once after all test suites 56 | // globalTeardown: null, 57 | 58 | // A set of global variables that need to be available in all test environments 59 | // globals: {}, 60 | 61 | // An array of directory names to be searched recursively up from the requiring module's location 62 | // moduleDirectories: [ 63 | // "node_modules" 64 | // ], 65 | 66 | // An array of file extensions your modules use 67 | moduleFileExtensions: ['js', 'json', 'jsx'], 68 | 69 | // A map from regular expressions to module names that allow to stub out resources with a single module 70 | // moduleNameMapper: {}, 71 | 72 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 73 | // modulePathIgnorePatterns: [], 74 | 75 | // Activates notifications for test results 76 | // notify: false, 77 | 78 | // An enum that specifies notification mode. Requires { notify: true } 79 | // notifyMode: "always", 80 | 81 | // A preset that is used as a base for Jest's configuration 82 | // preset: null, 83 | 84 | // Run tests from one or more projects 85 | // projects: null, 86 | 87 | // Use this configuration option to add custom reporters to Jest 88 | // reporters: undefined, 89 | 90 | // Automatically reset mock state between every test 91 | // resetMocks: false, 92 | 93 | // Reset the module registry before running each individual test 94 | // resetModules: false, 95 | 96 | // A path to a custom resolver 97 | // resolver: null, 98 | 99 | // Automatically restore mock state between every test 100 | // restoreMocks: false, 101 | 102 | // The root directory that Jest should scan for tests and modules within 103 | // rootDir: null, 104 | 105 | // A list of paths to directories that Jest should use to search for files in 106 | // roots: [ 107 | // "" 108 | // ], 109 | 110 | // Allows you to use a custom runner instead of Jest's default test runner 111 | // runner: "jest-runner", 112 | 113 | // The paths to modules that run some code to configure or set up the testing environment before each test 114 | setupFiles: ['/enzyme.config.js'], 115 | 116 | // The path to a module that runs some code to configure or set up the testing framework before each test 117 | // setupTestFrameworkScriptFile: '', 118 | 119 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 120 | // snapshotSerializers: [], 121 | 122 | // The test environment that will be used for testing 123 | testEnvironment: 'jsdom', 124 | 125 | // Options that will be passed to the testEnvironment 126 | // testEnvironmentOptions: {}, 127 | 128 | // Adds a location field to test results 129 | // testLocationInResults: false, 130 | 131 | // The glob patterns Jest uses to detect test files 132 | testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'], 133 | 134 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 135 | testPathIgnorePatterns: ['\\\\node_modules\\\\'], 136 | 137 | // The regexp pattern Jest uses to detect test files 138 | // testRegex: "", 139 | 140 | // This option allows the use of a custom results processor 141 | // testResultsProcessor: null, 142 | 143 | // This option allows use of a custom test runner 144 | // testRunner: "jasmine2", 145 | 146 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 147 | testURL: 'http://localhost', 148 | 149 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 150 | // timers: "real", 151 | 152 | // A map from regular expressions to paths to transformers 153 | // transform: {}, 154 | 155 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 156 | transformIgnorePatterns: ['/node_modules/'], 157 | 158 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 159 | // unmockedModulePathPatterns: undefined, 160 | 161 | // Indicates whether each individual test should be reported during the run 162 | verbose: false, 163 | 164 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 165 | // watchPathIgnorePatterns: [], 166 | 167 | // Whether to use watchman for file crawling 168 | // watchman: true, 169 | }; 170 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-boiler-plate", 3 | "version": "1.0.0", 4 | "description": "A react boiler plate", 5 | "main": "src/index.js", 6 | "author": "Adeel Imran", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "webpack-dev-server --mode development --config config/webpack.base.config.js --open --hot --history-api-fallback --env.PLATFORM=local --env.VERSION=stag", 10 | "prebuild": "webpack --mode production --config config/webpack.prod.config.js --env.PLATFORM=production --env.VERSION=stag --progress", 11 | "build": "node server", 12 | "lint": "eslint --debug src/", 13 | "lint:write": "eslint --debug src/ --fix", 14 | "test": "jest", 15 | "test:watch": "jest --watch", 16 | "test:coverage": "jest --coverage --colors", 17 | "prettier": "prettier --write src/**/*.js" 18 | }, 19 | "husky": { 20 | "hooks": { 21 | "pre-commit": "lint-staged" 22 | } 23 | }, 24 | "lint-staged": { 25 | "*.(js|jsx)": [ 26 | "npm run lint:write", 27 | "git add" 28 | ] 29 | }, 30 | "dependencies": { 31 | "@material-ui/core": "^3.0.0", 32 | "@material-ui/icons": "^3.0.0", 33 | "axios": "^0.18.0", 34 | "bootstrap": "^4.1.1", 35 | "express": "^4.16.3", 36 | "prop-types": "^15.6.2", 37 | "react": "16.7.0-alpha ", 38 | "react-dom": "16.7.0-alpha", 39 | "react-redux": "^5.0.7", 40 | "react-router-dom": "^4.3.1", 41 | "react-spring": "^6.1.6", 42 | "react-transition-group": "^2.4.0", 43 | "redux": "^4.0.0", 44 | "redux-thunk": "^2.3.0", 45 | "styled-components": "^3.3.3" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "^7.0.0", 49 | "@babel/plugin-proposal-class-properties": "^7.0.0", 50 | "@babel/plugin-proposal-export-namespace-from": "^7.0.0", 51 | "@babel/plugin-proposal-throw-expressions": "^7.0.0", 52 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 53 | "@babel/polyfill": "^7.0.0-beta.51", 54 | "@babel/preset-env": "^7.0.0-beta.51", 55 | "@babel/preset-react": "^7.0.0-beta.51", 56 | "babel-core": "^7.0.0-bridge.0", 57 | "babel-eslint": "^8.2.3", 58 | "babel-jest": "^23.4.2", 59 | "babel-loader": "^8.0.0-beta.0", 60 | "copy-webpack-plugin": "^4.5.1", 61 | "css-loader": "^0.28.11", 62 | "enzyme": "^3.3.0", 63 | "enzyme-adapter-react-16": "^1.1.1", 64 | "eslint": "^4.19.1", 65 | "eslint-config-airbnb": "^17.0.0", 66 | "eslint-config-jest-enzyme": "^6.0.2", 67 | "eslint-plugin-babel": "^5.1.0", 68 | "eslint-plugin-import": "^2.12.0", 69 | "eslint-plugin-jest": "^21.18.0", 70 | "eslint-plugin-jsx-a11y": "^6.0.3", 71 | "eslint-plugin-prettier": "^2.6.0", 72 | "eslint-plugin-react": "^7.9.1", 73 | "html-webpack-plugin": "^3.2.0", 74 | "husky": "^1.1.2", 75 | "jest": "^23.4.2", 76 | "lint-staged": "^7.3.0", 77 | "mini-css-extract-plugin": "^0.4.3", 78 | "node-sass": "^4.8.3", 79 | "optimize-css-assets-webpack-plugin": "^4.0.0", 80 | "prettier": "^1.14.3", 81 | "react-test-renderer": "^16.4.1", 82 | "sass-loader": "^7.0.3", 83 | "style-loader": "^0.21.0", 84 | "uglifyjs-webpack-plugin": "^1.2.5", 85 | "webpack": "^4.12.0", 86 | "webpack-cli": "^3.0.8", 87 | "webpack-dev-server": "^3.1.4", 88 | "webpack-merge": "^4.1.3", 89 | "webpack-visualizer-plugin": "^0.1.11" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | bracketSpacing: true, 6 | jsxBracketSameLine: false, 7 | tabWidth: 2, 8 | semi: true, 9 | }; 10 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const express = require('express'); 3 | const path = require('path'); 4 | const http = require('http'); 5 | 6 | const app = express(); 7 | 8 | // Point static path to dist 9 | app.use('/', express.static(path.join(__dirname, '..', 'dist'))); 10 | app.use('/dist', express.static(path.join(__dirname, '..', 'dist'))); 11 | 12 | const routes = require('./routes'); 13 | 14 | app.use('/', routes); 15 | 16 | /** Get port from environment and store in Express. */ 17 | const port = process.env.PORT || '3000'; 18 | app.set('port', port); 19 | 20 | /** Create HTTP server. */ 21 | const server = http.createServer(app); 22 | /** Listen on provided port, on all network interfaces. */ 23 | server.listen(port, () => console.log(`Server Running on port ${port}`)); 24 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const router = require('express').Router(); 3 | 4 | router.get('*', (req, res) => { 5 | const route = path.join(__dirname, '..', '..', 'dist', 'index.html'); 6 | res.sendFile(route); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /src/animations/Fade.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useSpring, animated } from 'react-spring'; 3 | import styled from 'styled-components'; 4 | import Button from '@material-ui/core/Button'; 5 | 6 | const Wrapper = styled.div` 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | justify-content: center; 11 | margin-top: 30px; 12 | `; 13 | 14 | const Text = styled.p` 15 | text-align: center; 16 | `; 17 | 18 | function Fade() { 19 | const [fade, setFade] = useState(false); 20 | const [fadeStyle] = useSpring({ 21 | opacity: fade ? 0 : 1, 22 | color: 'black', 23 | from: { 24 | opacity: 0, 25 | color: 'transparent', 26 | }, 27 | }); 28 | function onHandleSetFade() { 29 | setFade(!fade); 30 | } 31 | return ( 32 | 33 | 36 | 37 | Life is a mystery 38 | 39 | 40 | ); 41 | } 42 | 43 | export default Fade; 44 | -------------------------------------------------------------------------------- /src/animations/Progress.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import styled from 'styled-components'; 3 | import { useSpring, animated } from 'react-spring'; 4 | 5 | // Material UI 6 | import Button from '@material-ui/core/Button'; 7 | 8 | const Wrapper = styled.div` 9 | display: flex; 10 | flex-direction: row; 11 | align-items: center; 12 | margin-top: 60px; 13 | `; 14 | 15 | const Bar = styled.div` 16 | flex: 1; 17 | background: #f5f5f5; 18 | margin: 10px 6px 10px; 19 | border-radius: 4px; 20 | overflow: hidden; 21 | 22 | & div { 23 | height: 25px; 24 | background: #ff4d4f; 25 | } 26 | `; 27 | 28 | function Progress() { 29 | const [value, setValue] = useState(0); 30 | const [animatedBarStyle] = useSpring({ 31 | width: `${value}%`, 32 | from: { 33 | width: `${0}%`, 34 | }, 35 | }); 36 | return ( 37 | 38 | 41 | 42 | 43 | 44 | 47 | 48 | ); 49 | } 50 | 51 | export default Progress; 52 | -------------------------------------------------------------------------------- /src/context/index.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export const ThemeContext = createContext({ 4 | theme: { 5 | dark: 'purple-bg', 6 | light: 'gainsboro-bg', 7 | }, 8 | }); 9 | 10 | export const LocaleContext = createContext({ 11 | locale: { 12 | local: '🚩', 13 | foreign: '🎌', 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/helpers/Section.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Section = ({ isVisible, heading, sectionBg, children }) => { 5 | if (!isVisible) { 6 | return null; 7 | } 8 | return ( 9 |
10 |

{heading}

11 |
{children}
12 |
13 | ); 14 | }; 15 | 16 | Section.propTypes = { 17 | isVisible: PropTypes.bool, 18 | heading: PropTypes.string, 19 | sectionBg: PropTypes.string, 20 | children: PropTypes.node, 21 | }; 22 | 23 | export default Section; 24 | -------------------------------------------------------------------------------- /src/hooks/UseContextExample.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState, useContext } from 'react'; 2 | // Context 3 | import { ThemeContext, LocaleContext } from '../context'; 4 | 5 | export class ContextClass extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | name: 'Bilbo Baggins', 10 | age: 25, 11 | }; 12 | this.onHandleNameChange = this.onHandleNameChange.bind(this); 13 | this.onHandleAgeChange = this.onHandleAgeChange.bind(this); 14 | } 15 | 16 | onHandleNameChange(e) { 17 | this.setState({ 18 | name: e.target.value, 19 | }); 20 | } 21 | 22 | onHandleAgeChange(e) { 23 | this.setState({ 24 | age: e.target.value, 25 | }); 26 | } 27 | 28 | render() { 29 | const { name, age } = this.state; 30 | return ( 31 | 32 | {({ theme }) => ( 33 | 34 |
35 | Name 36 | 41 |
42 |
43 | Age 44 | 49 |
50 | 51 | {({ locale }) => ( 52 |
53 | Locale 54 | 55 |
56 | )} 57 |
58 |
59 | )} 60 |
61 | ); 62 | } 63 | } 64 | 65 | export function Context() { 66 | const [name, setName] = useState('Albus Dumbledore'); 67 | const [age, setAge] = useState(175); 68 | const { theme } = useContext(ThemeContext); 69 | const { locale } = useContext(LocaleContext); 70 | function handleNameChange(e) { 71 | setName(e.target.value); 72 | } 73 | function handleAgeChange(e) { 74 | setAge(e.target.value); 75 | } 76 | return ( 77 | 78 |
79 | Name 80 | 81 |
82 |
83 | Age 84 | 85 |
86 |
87 | Age 88 | 89 |
90 |
91 | ); 92 | } 93 | -------------------------------------------------------------------------------- /src/hooks/UseCustomHooks.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState, useEffect } from 'react'; 2 | 3 | export function useWindowWidth() { 4 | const [width, setWidth] = useState(window.innerWidth); 5 | useEffect(() => { 6 | const handleResize = () => setWidth(window.innerWidth); 7 | window.addEventListener('resize', handleResize); 8 | return () => { 9 | window.removeEventListener('resize', handleResize); 10 | }; 11 | }); 12 | return width; 13 | } 14 | 15 | export function useDocumentTitle(title) { 16 | useEffect(() => { 17 | document.title = title; 18 | }); 19 | } 20 | 21 | export function useFormInput(initialValue) { 22 | const [value, setValue] = useState(initialValue); 23 | function onHandleChange(e) { 24 | setValue(e.target.value); 25 | } 26 | return { value, onChange: onHandleChange }; 27 | } 28 | 29 | export function CustomHook() { 30 | const title = useFormInput('Heading'); 31 | const designation = useFormInput('Software Engineer'); 32 | const width = useWindowWidth(); 33 | useDocumentTitle(title.value); 34 | return ( 35 | 36 |
37 | Title 38 | 39 |
40 |
41 | Designation 42 | 43 |
44 |
45 | Width 46 | 47 |
48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/hooks/UseEffectExample.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState, useEffect } from 'react'; 2 | 3 | export class TitleClass extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | name: 'Bilbo Baggins', 8 | age: 25, 9 | width: window.innerWidth, 10 | }; 11 | this.onHandleNameChange = this.onHandleNameChange.bind(this); 12 | this.onHandleAgeChange = this.onHandleAgeChange.bind(this); 13 | this.onHandleResize = this.onHandleResize.bind(this); 14 | } 15 | 16 | componentDidMount() { 17 | const { name, age } = this.state; 18 | document.title = `${name} | ${age}`; 19 | window.addEventListener('resize', this.onHandleResize); 20 | } 21 | 22 | componentDidUpdate() { 23 | const { name, age } = this.state; 24 | document.title = `${name} | ${age}`; 25 | } 26 | 27 | componentWillUnmount() { 28 | window.removeEventListener('resize', this.onHandleResize); 29 | } 30 | 31 | onHandleNameChange(e) { 32 | this.setState({ 33 | name: e.target.value, 34 | }); 35 | } 36 | 37 | onHandleAgeChange(e) { 38 | this.setState({ 39 | age: e.target.value, 40 | }); 41 | } 42 | 43 | onHandleResize() { 44 | this.setState({ width: window.innerWidth }); 45 | } 46 | 47 | render() { 48 | const { name, age, width } = this.state; 49 | return ( 50 | 51 |
52 | Name 53 | 54 |
55 |
56 | Age 57 | 58 |
59 |
60 | Width 61 | 62 |
63 |
64 | ); 65 | } 66 | } 67 | 68 | export function Title() { 69 | const [name, setName] = useState('Albus Dumbledore'); 70 | const [age, setAge] = useState(175); 71 | useEffect(() => { 72 | document.title = `${name} | ${age}`; 73 | }); 74 | const [height, setHeight] = useState(window.innerHeight); 75 | useEffect(() => { 76 | const handleResize = () => setHeight(window.innerHeight); 77 | window.addEventListener('resize', handleResize); 78 | return () => { 79 | window.removeEventListener('resize', handleResize); 80 | }; 81 | }); 82 | function handleNameChange(e) { 83 | setName(e.target.value); 84 | } 85 | function handleAgeChange(e) { 86 | setAge(e.target.value); 87 | } 88 | return ( 89 | 90 |
91 | Name 92 | 93 |
94 |
95 | Age 96 | 97 |
98 |
99 | Height 100 | 101 |
102 |
103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /src/hooks/UseReducerExample.js: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from 'react'; 2 | 3 | const initialState = { 4 | loading: false, 5 | count: 0, 6 | }; 7 | 8 | const reducer = (state, action) => { 9 | switch (action.type) { 10 | case 'increment': { 11 | return { ...state, count: state.count + 1, loading: false }; 12 | } 13 | case 'decrement': { 14 | return { ...state, count: state.count - 1, loading: false }; 15 | } 16 | case 'loading': { 17 | return { ...state, loading: true }; 18 | } 19 | default: { 20 | return state; 21 | } 22 | } 23 | }; 24 | 25 | const delay = (time = 1500) => { 26 | return new Promise(resolve => { 27 | setTimeout(() => { 28 | resolve(true); 29 | }, time); 30 | }); 31 | }; 32 | 33 | function PokemonInfo() { 34 | const [{ count, loading }, dispatch] = useReducer(reducer, initialState); 35 | const onHandleIncrement = async () => { 36 | dispatch({ type: 'loading' }); 37 | await delay(500); 38 | dispatch({ type: 'increment' }); 39 | }; 40 | const onHandleDecrement = async () => { 41 | dispatch({ type: 'loading' }); 42 | await delay(500); 43 | dispatch({ type: 'decrement' }); 44 | }; 45 | return ( 46 |
47 |

Count {loading ? 'loading..' : count}

48 | 51 | 54 |
55 | ); 56 | } 57 | 58 | export default PokemonInfo; 59 | -------------------------------------------------------------------------------- /src/hooks/UseStateExample.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState } from 'react'; 2 | 3 | export class GreetingClass extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | name: 'Bilbo Baggins', 8 | age: 25, 9 | }; 10 | this.onHandleNameChange = this.onHandleNameChange.bind(this); 11 | this.onHandleAgeChange = this.onHandleAgeChange.bind(this); 12 | } 13 | 14 | onHandleNameChange(e) { 15 | this.setState({ 16 | name: e.target.value, 17 | }); 18 | } 19 | 20 | onHandleAgeChange(e) { 21 | this.setState({ 22 | age: e.target.value, 23 | }); 24 | } 25 | 26 | render() { 27 | const { name, age } = this.state; 28 | return ( 29 | 30 |
31 | Name 32 | 33 |
34 |
35 | Age 36 | 37 |
38 |
39 | ); 40 | } 41 | } 42 | 43 | export function Greeting() { 44 | const [name, setName] = useState('Albus Dumbledore'); 45 | const [age, setAge] = useState(175); 46 | function handleNameChange(e) { 47 | setName(e.target.value); 48 | } 49 | function handleAgeChange(e) { 50 | setAge(e.target.value); 51 | } 52 | return ( 53 | 54 |
55 | Name 56 | 57 |
58 |
59 | Age 60 | 61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Sample App 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | // Styles 5 | import './styles.scss'; 6 | // Helpers 7 | import Section from './helpers/Section'; 8 | // Examples 9 | import { Greeting, GreetingClass } from './hooks/UseStateExample'; 10 | import { Context, ContextClass } from './hooks/UseContextExample'; 11 | import { Title, TitleClass } from './hooks/UseEffectExample'; 12 | import { CustomHook } from './hooks/UseCustomHooks'; 13 | import PokemonInfo from './hooks/UseReducerExample'; 14 | import Progress from './animations/Progress'; 15 | import Fade from './animations/Fade'; 16 | 17 | const VISIBLE = { 18 | IS_USE_STATE_VISIBLE: 'UseStateExample', 19 | IS_USE_CONTEXT_VISIBLE: 'UseContextExample', 20 | IS_USE_EFFECT_VISIBLE: 'UseEffectExample', 21 | IS_USE_CUSTOM_HOOKS_VISIBLE: 'UseCustomHooks', 22 | IS_USE_REDUCER_VISIBLE: 'UseReducerExample', 23 | IS_REACT_SPRING_1_VISIBLE: 'Progress', 24 | IS_REACT_SPRING_2_VISIBLE: 'Fade', 25 | }; 26 | 27 | class App extends Component { 28 | state = { 29 | isVisible: [ 30 | VISIBLE.IS_REACT_SPRING_1_VISIBLE, 31 | VISIBLE.IS_REACT_SPRING_2_VISIBLE, 32 | ], 33 | }; 34 | 35 | render() { 36 | const { isVisible } = this.state; 37 | return ( 38 |
39 |
44 |
45 |

new way

46 | 47 |
48 |
49 |

old way

50 | 51 |
52 |
53 |
58 |
59 |

new way

60 | 61 |
62 |
63 |

old way

64 | 65 |
66 |
67 |
72 |
73 |

new way

74 | 75 | </div> 76 | <div> 77 | <h1 className="heading">old way</h1> 78 | <TitleClass /> 79 | </div> 80 | </Section> 81 | <Section 82 | isVisible={isVisible.includes(VISIBLE.IS_USE_CUSTOM_HOOKS_VISIBLE)} 83 | heading="useCustomHooks" 84 | sectionBg="purple-bg" 85 | > 86 | <div> 87 | <h1 className="heading">new way</h1> 88 | <CustomHook /> 89 | </div> 90 | </Section> 91 | <Section 92 | isVisible={isVisible.includes(VISIBLE.IS_USE_REDUCER_VISIBLE)} 93 | heading="useReducer" 94 | sectionBg="white-bg" 95 | > 96 | <div> 97 | <h1 className="heading">Count Example</h1> 98 | <PokemonInfo /> 99 | </div> 100 | </Section> 101 | <Section 102 | isVisible={isVisible.includes(VISIBLE.IS_REACT_SPRING_1_VISIBLE)} 103 | heading="React Spring Progress Bar Example" 104 | sectionBg="gainsboro-bg" 105 | > 106 | <Progress /> 107 | </Section> 108 | <Section 109 | isVisible={isVisible.includes(VISIBLE.IS_REACT_SPRING_2_VISIBLE)} 110 | heading="React Spring Fade Bar Example" 111 | sectionBg="gainsboro-bg" 112 | > 113 | <Fade /> 114 | </Section> 115 | </div> 116 | ); 117 | } 118 | } 119 | 120 | ReactDOM.render(<App />, document.getElementById('app')); 121 | -------------------------------------------------------------------------------- /src/static/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | } -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Comfortaa:400,700'); 2 | 3 | body { 4 | font-family: 'Comfortaa', cursive; 5 | background-color: #FFEB3B; 6 | margin-bottom: 50px; 7 | padding: 0; 8 | margin: 0; 9 | } 10 | 11 | .main-heading { 12 | font-size: 22px; 13 | padding-left: 5px; 14 | } 15 | 16 | .section { 17 | display: flex; 18 | flex-direction: row; 19 | align-items: flex-start; 20 | justify-content: center; 21 | flex-wrap: wrap; 22 | padding: 10px; 23 | } 24 | 25 | .section div { 26 | width: 400px; 27 | } 28 | 29 | .section .heading { 30 | font-size: 45px; 31 | font-weight: 700; 32 | margin: 0 4px; 33 | } 34 | 35 | .row { 36 | display: flex; 37 | flex-direction: column; 38 | max-width: 400px; 39 | margin: 15px 10px; 40 | } 41 | 42 | .row span { 43 | font-size: 18px; 44 | font-weight: 700; 45 | margin-top: 10px; 46 | margin-bottom: 10px; 47 | margin-left: 5px; 48 | } 49 | 50 | .row input { 51 | padding: 10px; 52 | outline: 0; 53 | border: 0; 54 | transition: 150ms ease-in; 55 | background-color: rgba(244, 244, 244, 0.7); 56 | } 57 | 58 | .row input:hover, 59 | .row input:focus, 60 | .row input:active { 61 | cursor: pointer; 62 | background-color: rgba(244, 244, 244, 0.9); 63 | } 64 | 65 | // colors 66 | .purple-bg { 67 | background-color: purple; 68 | color: white; 69 | } 70 | 71 | .gainsboro-bg { 72 | background-color: gainsboro; 73 | } 74 | 75 | .white-bg { 76 | background-color: white; 77 | } --------------------------------------------------------------------------------