├── .babelrc ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── config └── eslint.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── Answer.js │ ├── Modal.js │ ├── Question.js │ ├── QuestionList.js │ ├── Quiz.js │ ├── QuizApp.js │ └── Results.js ├── data │ └── quiz-data.js ├── helpers │ ├── shuffleQuestions.js │ └── tally.js ├── index.html ├── main.js └── style.css ├── webpack.config.dev.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react", 5 | "stage-2" 6 | ], 7 | "plugins": [ 8 | "react-hot-loader/babel" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const clientESLintConfig = require('./config/eslint'); 2 | 3 | module.exports = Object.assign({}, clientESLintConfig, { 4 | env: Object.assign({}, clientESLintConfig.env, { 5 | node: true, 6 | }) 7 | }); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 David Ra Youssef 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Quiz 2 | A JavaScript slide quiz built with React. 3 | 4 | [View Demo](http://davidra.co/react-quiz/) 5 | 6 | Getting Started 7 | --------------- 8 | 9 | ```shell 10 | $ git clone https://github.com/davidrayoussef/react-quiz.git 11 | $ cd react-quiz 12 | $ npm install 13 | $ npm start 14 | ``` 15 | 16 | App Structure 17 | ------------- 18 | 19 | ``` 20 | QuizApp 21 | └── Quiz 22 | ├── QuestionList 23 | | └── Question 24 | | └── Answer 25 | └── Results 26 | ``` 27 | -------------------------------------------------------------------------------- /config/eslint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | // Inspired by https://github.com/airbnb/javascript but less opinionated. 11 | 12 | // We use eslint-loader so even warnings are very visibile. 13 | // This is why we only use "WARNING" level for potential errors, 14 | // and we don't use "ERROR" level at all. 15 | 16 | // In the future, we might create a separate list of rules for production. 17 | // It would probably be more strict. 18 | 19 | module.exports = { 20 | root: true, 21 | 22 | parser: 'babel-eslint', 23 | 24 | plugins: ['react'], 25 | 26 | env: { 27 | browser: true, 28 | commonjs: true, 29 | es6: true, 30 | jest: true, 31 | node: true 32 | }, 33 | 34 | parserOptions: { 35 | ecmaVersion: 6, 36 | sourceType: 'module', 37 | ecmaFeatures: { 38 | jsx: true, 39 | generators: true, 40 | experimentalObjectRestSpread: true 41 | } 42 | }, 43 | 44 | extends: [ 45 | 'eslint:recommended', 46 | 'plugin:react/recommended' 47 | ], 48 | 49 | settings: { 50 | 'import/ignore': [ 51 | 'node_modules', 52 | '\\.(json|css|jpg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm)$', 53 | ], 54 | 'import/extensions': ['.js'], 55 | 'import/resolver': { 56 | node: { 57 | extensions: ['.js', '.json'] 58 | } 59 | } 60 | }, 61 | 62 | rules: { 63 | 'react/jsx-key': 'off', 64 | 'react/no-unescaped-entities': 'off', 65 | 66 | // http://eslint.org/docs/rules/ 67 | 'array-callback-return': 'warn', 68 | 'default-case': ['warn', { commentPattern: '^no default$' }], 69 | 'dot-location': ['warn', 'property'], 70 | 'eqeqeq': ['warn', 'allow-null'], 71 | 'guard-for-in': 'warn', 72 | 'new-parens': 'warn', 73 | 'no-array-constructor': 'warn', 74 | 'no-caller': 'warn', 75 | 'no-cond-assign': ['warn', 'always'], 76 | 'no-console': 'off', 77 | 'no-const-assign': 'warn', 78 | 'no-control-regex': 'warn', 79 | 'no-delete-var': 'warn', 80 | 'no-dupe-args': 'warn', 81 | 'no-dupe-class-members': 'warn', 82 | 'no-dupe-keys': 'warn', 83 | 'no-duplicate-case': 'warn', 84 | 'no-empty-character-class': 'warn', 85 | 'no-empty-pattern': 'warn', 86 | 'no-eval': 'warn', 87 | 'no-ex-assign': 'warn', 88 | 'no-extend-native': 'warn', 89 | 'no-extra-bind': 'warn', 90 | 'no-extra-label': 'warn', 91 | 'no-fallthrough': 'warn', 92 | 'no-func-assign': 'warn', 93 | 'no-implied-eval': 'warn', 94 | 'no-invalid-regexp': 'warn', 95 | 'no-iterator': 'warn', 96 | 'no-label-var': 'warn', 97 | 'no-labels': ['warn', { allowLoop: false, allowSwitch: false }], 98 | 'no-lone-blocks': 'warn', 99 | 'no-loop-func': 'warn', 100 | 'no-mixed-operators': ['warn', { 101 | groups: [ 102 | ['&', '|', '^', '~', '<<', '>>', '>>>'], 103 | ['==', '!=', '===', '!==', '>', '>=', '<', '<='], 104 | ['&&', '||'], 105 | ['in', 'instanceof'] 106 | ], 107 | allowSamePrecedence: false 108 | }], 109 | 'no-multi-str': 'warn', 110 | 'no-native-reassign': 'warn', 111 | 'no-negated-in-lhs': 'warn', 112 | 'no-new-func': 'warn', 113 | 'no-new-object': 'warn', 114 | 'no-new-symbol': 'warn', 115 | 'no-new-wrappers': 'warn', 116 | 'no-obj-calls': 'warn', 117 | 'no-octal': 'warn', 118 | 'no-octal-escape': 'warn', 119 | 'no-redeclare': 'warn', 120 | 'no-regex-spaces': 'warn', 121 | 'no-restricted-syntax': [ 122 | 'warn', 123 | 'LabeledStatement', 124 | 'WithStatement', 125 | ], 126 | 'no-return-assign': 'warn', 127 | 'no-script-url': 'warn', 128 | 'no-self-assign': 'warn', 129 | 'no-self-compare': 'warn', 130 | 'no-sequences': 'warn', 131 | 'no-shadow-restricted-names': 'warn', 132 | 'no-this-before-super': 'warn', 133 | 'no-sparse-arrays': 'warn', 134 | 'no-throw-literal': 'warn', 135 | 'no-undef': 'warn', 136 | 'no-unexpected-multiline': 'warn', 137 | 'no-unreachable': 'warn', 138 | 'no-unused-expressions': 'warn', 139 | 'no-unused-labels': 'warn', 140 | 'no-unused-vars': ['warn', { vars: 'local', args: 'none' }], 141 | 'no-use-before-define': ['warn', 'nofunc'], 142 | 'no-useless-computed-key': 'warn', 143 | 'no-useless-concat': 'warn', 144 | 'no-useless-constructor': 'warn', 145 | 'no-useless-escape': 'warn', 146 | 'no-useless-rename': ['warn', { 147 | ignoreDestructuring: false, 148 | ignoreImport: false, 149 | ignoreExport: false, 150 | }], 151 | 'no-with': 'warn', 152 | 'no-whitespace-before-property': 'warn', 153 | 'operator-assignment': ['warn', 'always'], 154 | radix: 'warn', 155 | 'require-yield': 'warn', 156 | 'rest-spread-spacing': ['warn', 'never'], 157 | strict: ['warn', 'never'], 158 | 'unicode-bom': ['warn', 'never'], 159 | 'use-isnan': 'warn', 160 | 'valid-typeof': 'warn', 161 | 162 | // https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules 163 | 'react/jsx-equals-spacing': ['warn', 'never'], 164 | 'react/jsx-no-duplicate-props': ['warn', { ignoreCase: true }], 165 | 'react/jsx-no-undef': 'warn', 166 | 'react/jsx-pascal-case': ['warn', { 167 | allowAllCaps: true, 168 | ignore: [], 169 | }], 170 | 'react/jsx-uses-react': 'warn', 171 | 'react/jsx-uses-vars': 'warn', 172 | 'react/no-deprecated': 'warn', 173 | 'react/no-direct-mutation-state': 'warn', 174 | 'react/no-is-mounted': 'warn', 175 | 'react/react-in-jsx-scope': 'warn', 176 | 'react/require-render-return': 'warn' 177 | } 178 | }; 179 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-quiz", 3 | "version": "1.10.0", 4 | "description": "A JavaScript quiz built with React.", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --mode development --config webpack.config.dev.js --open", 8 | "build": "webpack --mode production --config webpack.config.prod.js --progress" 9 | }, 10 | "repository": "davidrayoussef/react-quiz", 11 | "homepage": "https://github.com/davidrayoussef/react-quiz", 12 | "author": "David Ra Youssef (davidradeveloper@gmail.com)", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "babel-core": "^6.26.0", 16 | "babel-eslint": "^8.2.3", 17 | "babel-loader": "^7.1.4", 18 | "babel-preset-env": "^1.6.1", 19 | "babel-preset-react": "^6.24.1", 20 | "babel-preset-stage-2": "^6.24.1", 21 | "css-loader": "^0.28.11", 22 | "eslint": "^4.19.1", 23 | "eslint-loader": "^2.0.0", 24 | "eslint-plugin-react": "^7.7.0", 25 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 26 | "html-webpack-plugin": "^3.2.0", 27 | "prop-types": "^15.6.1", 28 | "react": "^16.3.2", 29 | "react-dom": "^16.3.2", 30 | "react-hot-loader": "^4.1.1", 31 | "style-loader": "^0.21.0", 32 | "webpack": "^4.6.0", 33 | "webpack-cli": "^2.0.15", 34 | "webpack-dev-server": "^3.1.3" 35 | }, 36 | "keywords": [ 37 | "react", 38 | "quiz", 39 | "test", 40 | "react-component" 41 | ], 42 | "dependencies": {} 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Answer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Answer = ({ answer, handleAnswerClick, handleEnterPress }) => { 5 | return ( 6 |
  • 12 | {answer} 13 |
  • 14 | ); 15 | } 16 | 17 | Answer.propTypes = { 18 | answer: PropTypes.element.isRequired, 19 | handleAnswerClick: PropTypes.func.isRequired, 20 | handleEnterPress: PropTypes.func.isRequired 21 | }; 22 | 23 | export default Answer; 24 | -------------------------------------------------------------------------------- /src/components/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Modal = ({ modal: { state, praise, points } }) => { 5 | return ( 6 |
    7 |
    {praise}
    8 |
    {points}
    9 |
    10 | ); 11 | }; 12 | 13 | Modal.propTypes = { 14 | modal: PropTypes.shape({ 15 | state: PropTypes.string.isRequired, 16 | praise: PropTypes.string.isRequired, 17 | points: PropTypes.string.isRequired 18 | }) 19 | }; 20 | 21 | export default Modal; 22 | -------------------------------------------------------------------------------- /src/components/Question.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Answer from './Answer'; 4 | 5 | const Question = ({ question, answers, handleAnswerClick, handleEnterPress }) => { 6 | return ( 7 |
  • 8 |

    9 | {question} 10 |

    11 | 23 |
  • 24 | ); 25 | } 26 | 27 | Question.propTypes = { 28 | question: PropTypes.element.isRequired, 29 | answers: PropTypes.array.isRequired, 30 | handleAnswerClick: PropTypes.func.isRequired, 31 | handleEnterPress: PropTypes.func.isRequired 32 | }; 33 | 34 | export default Question; 35 | -------------------------------------------------------------------------------- /src/components/QuestionList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Question from './Question'; 4 | 5 | const QuestionList = ({ questions, handleAnswerClick, handleEnterPress }) => { 6 | return ( 7 | 20 | ); 21 | } 22 | 23 | QuestionList.propTypes = { 24 | questions: PropTypes.array.isRequired, 25 | handleAnswerClick: PropTypes.func.isRequired, 26 | handleEnterPress: PropTypes.func.isRequired 27 | }; 28 | 29 | export default QuestionList; 30 | -------------------------------------------------------------------------------- /src/components/Quiz.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import QuestionList from './QuestionList'; 4 | 5 | const Quiz = ({ step, questions, totalQuestions, score, handleAnswerClick, handleEnterPress }) => { 6 | return ( 7 |
    8 |
    9 |
    10 |

    Question

    11 |
    {step}
    12 |
    of {totalQuestions}
    13 |
    14 |

    JavaScript Quiz

    15 |
    16 |

    Score

    17 |
    {score}
    18 |
    points
    19 |
    20 |
    21 | 22 |
    23 | 28 |
    29 |
    30 | ); 31 | } 32 | 33 | Quiz.propTypes = { 34 | step: PropTypes.number.isRequired, 35 | questions: PropTypes.array.isRequired, 36 | totalQuestions: PropTypes.number.isRequired, 37 | score: PropTypes.number.isRequired, 38 | handleAnswerClick: PropTypes.func.isRequired, 39 | handleEnterPress: PropTypes.func.isRequired 40 | }; 41 | 42 | export default Quiz; 43 | -------------------------------------------------------------------------------- /src/components/QuizApp.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Quiz from './Quiz'; 4 | import Modal from './Modal'; 5 | import Results from './Results'; 6 | import shuffleQuestions from '../helpers/shuffleQuestions'; 7 | import QUESTION_DATA from '../data/quiz-data'; 8 | 9 | class QuizApp extends Component { 10 | state = { 11 | ...this.getInitialState(this.props.totalQuestions) 12 | }; 13 | 14 | static propTypes = { 15 | totalQuestions: PropTypes.number.isRequired 16 | }; 17 | 18 | getInitialState(totalQuestions) { 19 | totalQuestions = Math.min(totalQuestions, QUESTION_DATA.length); 20 | const QUESTIONS = shuffleQuestions(QUESTION_DATA).slice(0, totalQuestions); 21 | 22 | return { 23 | questions: QUESTIONS, 24 | totalQuestions: totalQuestions, 25 | userAnswers: QUESTIONS.map(() => { 26 | return { 27 | tries: 0 28 | } 29 | }), 30 | step: 1, 31 | score: 0, 32 | modal: { 33 | state: 'hide', 34 | praise: '', 35 | points: '' 36 | } 37 | }; 38 | } 39 | 40 | handleAnswerClick = (index) => (e) => { 41 | const { questions, step, userAnswers } = this.state; 42 | const isCorrect = questions[0].correct === index; 43 | const currentStep = step - 1; 44 | const tries = userAnswers[currentStep].tries; 45 | 46 | if (isCorrect && e.target.nodeName === 'LI') { 47 | // Prevent other answers from being clicked after correct answer is clicked 48 | e.target.parentNode.style.pointerEvents = 'none'; 49 | 50 | e.target.classList.add('right'); 51 | 52 | userAnswers[currentStep] = { 53 | tries: tries + 1 54 | }; 55 | 56 | this.setState({ 57 | userAnswers: userAnswers 58 | }); 59 | 60 | setTimeout(() => this.showModal(tries), 750); 61 | 62 | setTimeout(this.nextStep, 2750); 63 | } 64 | 65 | else if (e.target.nodeName === 'LI') { 66 | e.target.style.pointerEvents = 'none'; 67 | e.target.classList.add('wrong'); 68 | 69 | userAnswers[currentStep] = { 70 | tries: tries + 1 71 | }; 72 | 73 | this.setState({ 74 | userAnswers: userAnswers 75 | }); 76 | } 77 | }; 78 | 79 | handleEnterPress = (index) => (e) => { 80 | if (e.keyCode === 13) { 81 | this.handleAnswerClick(index)(e); 82 | } 83 | }; 84 | 85 | showModal = (tries) => { 86 | let praise; 87 | let points; 88 | 89 | switch (tries) { 90 | case 0: { 91 | praise = '1st Try!'; 92 | points = '+10'; 93 | break; 94 | } 95 | case 1: { 96 | praise = '2nd Try!'; 97 | points = '+5'; 98 | break; 99 | } 100 | case 2: { 101 | praise = 'Correct!'; 102 | points = '+2'; 103 | break; 104 | } 105 | default: { 106 | praise = 'Correct!'; 107 | points = '+1'; 108 | } 109 | } 110 | 111 | this.setState({ 112 | modal: { 113 | state: 'show', 114 | praise, 115 | points 116 | } 117 | }); 118 | }; 119 | 120 | nextStep = () => { 121 | const { questions, userAnswers, step, score } = this.state; 122 | const restOfQuestions = questions.slice(1); 123 | const currentStep = step - 1; 124 | const tries = userAnswers[currentStep].tries; 125 | 126 | this.setState({ 127 | step: step + 1, 128 | score: this.updateScore(tries, score), 129 | questions: restOfQuestions, 130 | modal: { 131 | state: 'hide' 132 | } 133 | }); 134 | }; 135 | 136 | updateScore(tries, score) { 137 | switch (tries) { 138 | case 1: return score + 10; 139 | case 2: return score + 5; 140 | case 3: return score + 2; 141 | default: return score + 1; 142 | } 143 | } 144 | 145 | restartQuiz = () => { 146 | this.setState({ 147 | ...this.getInitialState(this.props.totalQuestions) 148 | }); 149 | }; 150 | 151 | render() { 152 | const { step, questions, userAnswers, totalQuestions, score, modal } = this.state; 153 | 154 | if (step >= totalQuestions + 1) { 155 | return ( 156 | 161 | ); 162 | } else return ( 163 | 164 | 172 | { modal.state === 'show' && } 173 | 174 | ); 175 | } 176 | } 177 | 178 | export default QuizApp; 179 | -------------------------------------------------------------------------------- /src/components/Results.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import tally from '../helpers/tally'; 4 | 5 | const Results = ({ userAnswers, score, restartQuiz }) => { 6 | const triesTotal = tally(userAnswers); 7 | const oneTry = triesTotal[1] &&
    {triesTotal[1]} on the first try.
    ; 8 | const twoTries = triesTotal[2] &&
    {triesTotal[2]} on the second try.
    ; 9 | const threeTries = triesTotal[3] &&
    {triesTotal[3]} on the third try.
    ; 10 | const fourTries = triesTotal[4] &&
    {triesTotal[4]} on the fourth try.
    ; 11 | 12 | return ( 13 |
    14 |

    Quiz Results

    15 |
    You answered...
    16 | {oneTry} 17 | {twoTries} 18 | {threeTries} 19 | {fourTries} 20 |
    Your Total Score is {score}.
    21 | Restart Quiz 22 |
    23 | ); 24 | } 25 | 26 | Results.propTypes = { 27 | userAnswers: PropTypes.array.isRequired, 28 | score: PropTypes.number.isRequired, 29 | restartQuiz: PropTypes.func.isRequired 30 | }; 31 | 32 | export default Results; 33 | -------------------------------------------------------------------------------- /src/data/quiz-data.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const QUESTION_DATA = [ 4 | { 5 | question: Which of the following is TRUE about reflows and repaints?, 6 | answers: [ 7 | They're the same thing., 8 | Repaints (or redraws) occur when elements change their visual styles but not layout., 9 | Reflows compute layout, are more performance critical, and occur when elements are inserted, removed, moved, animated, etc., 10 | The previous two answers are correct. 11 | ], 12 | correct: 3 13 | }, 14 | { 15 | question: What are the three types of JavaScript errors?, 16 | answers: [ 17 | Parse Errors, Syntax Errors and Runtime Errors., 18 | Loading Errors, Runtime Errors and Logic Errors., 19 | Syntax Errors, Logic Errors and Loading Errors., 20 | Bad Errors, Very Bad Errors, and Fatal Errors. 21 | ], 22 | correct: 1 23 | }, 24 | { 25 | question: What's a closure?, 26 | answers: [ 27 | An inner function that has access to an outer function's variables, even after the outer function has executed., 28 | A stateful function; a function that preserves state., 29 | The combination of a function and the lexical environment within which that function was declared., 30 | All of the above. 31 | ], 32 | correct: 3 33 | }, 34 | { 35 | question: Where might you find, or how might you use a closure in JavaScript?, 36 | answers: [ 37 | When currying or implementing partial application., 38 | To emulate private methods., 39 | In event handlers, timers, and asynchronous callbacks., 40 | All of the above. 41 | ], 42 | correct: 3 43 | }, 44 | { 45 | question: Which of these is a use case for the bind, call, or apply methods?, 46 | answers: [ 47 | You can use call or apply to borrow methods from other objects., 48 | You can use bind for partial function application., 49 | If you're using the map method to run a function on an array and you need to preserve the this context, you can use bind., 50 | All of the above. 51 | ], 52 | correct: 3 53 | }, 54 | { 55 | question: What does the bind method do?, 56 | answers: [ 57 | Returns a function that, when executed, will call the original function with a this context that you pass in., 58 | Prevents the value of this from being overridden by call or apply., 59 | Allows you to implement partial application of a function., 60 | All of the above. 61 | ], 62 | correct: 3 63 | }, 64 | { 65 | question: How do objects inherit methods in JavaScript?, 66 | answers: [ 67 | With Object.create or Object.setPrototypeOf., 68 | With class Sub extends Super in ES2015., 69 | Using Parent.prototype.method.call inside Child.prototype.method., 70 | All of the above. 71 | ], 72 | correct: 3 73 | }, 74 | { 75 | question: What is a promise?, 76 | answers: [ 77 | An object that represents a possible future value., 78 | An object that's used for deferred and asynchronous computations., 79 | A proxy for a value that will eventually become available., 80 | All of the above. 81 | ], 82 | correct: 3 83 | }, 84 | { 85 | question: What is CORS?, 86 | answers: [ 87 | Cross-Origin Resource Sharing, 88 | Allows restricted resources (e.g. fonts) on a web page to be requested from an outside domain., 89 | Allows scripts to interact more openly with content outside of the original domain, leading to better integration between web services., 90 | All of the above. 91 | ], 92 | correct: 3 93 | }, 94 | { 95 | question: What is an Angular expression?, 96 | answers: [ 97 | A JavaScript-like code snippet that is evaluated by Angular., 98 | A code snippet that is evaluated in the context of the current model scope, rather than within the scope of the global context (window)., 99 | A binding in double curly brackets that gets evaluated and the results appear in the DOM in place of it., 100 | All of the above. 101 | ], 102 | correct: 3 103 | }, 104 | { 105 | question: In Angular, what is a directive?, 106 | answers: [ 107 | An Angular feature that takes an element and gives it new functionality., 108 | A reusable component., 109 | A combination of HTML and JavaScript that will execute together., 110 | All of the above. 111 | ], 112 | correct: 3 113 | }, 114 | { 115 | question: What does strict mode do?, 116 | answers: [ 117 | Makes code more optimizable., 118 | Adds restrictions to prevent errors., 119 | Prevents the use of global variables and the use of dangerous code like with and eval., 120 | All of the above. 121 | ], 122 | correct: 3 123 | }, 124 | { 125 | question: What is event bubbling?, 126 | answers: [ 127 | When the browser engine searches down the DOM tree for event handlers., 128 | When the browser engine searches up the DOM tree for event handlers., 129 | When the browser engine searches sideways on sibling elements for event handlers., 130 | None of the above. 131 | ], 132 | correct: 1 133 | }, 134 | { 135 | question: What is event delegation?, 136 | answers: [ 137 | Attaching event handlers to child elements rather than parent elements., 138 | Creating custom event handlers., 139 | Attaching event handlers to a parent element rather than multiple child elements., 140 | None of the above. 141 | ], 142 | correct: 2 143 | }, 144 | { 145 | question: What is dependency injection?, 146 | answers: [ 147 | There's no such thing., 148 | A type of data structure., 149 | A coding pattern in which a class receives the instances of objects it needs from an external source rather than creating them itself., 150 | A new drug the kids are doing nowadays. 151 | ], 152 | correct: 2 153 | }, 154 | { 155 | question: What are the six primitive data types in JavaScript?, 156 | answers: [ 157 | Function, String, Undefined, Digit, Nil, and Double., 158 | Boolean, Null, Undefined, Number, String, and Symbol in ES6., 159 | Number, Class, Object, Hash, String, and Function., 160 | None of the above. 161 | ], 162 | correct: 1 163 | }, 164 | { 165 | question: Which of the following is FALSE about the this keyword?, 166 | answers: [ 167 | Its value is established at the invocation of a function., 168 | When a function is invoked with the new keyword, this is bound to the new object., 169 | Its value is set at the declaration of a function., 170 | None of the above. 171 | ], 172 | correct: 2 173 | }, 174 | { 175 | question: In Angular 2, which of the following are considered Structural Directives (directives that change DOM layout)?, 176 | answers: [ 177 | NgFor and NgIf, 178 | NgStyle and NgClass, 179 | NgModel and NgForm, 180 | None of the above. 181 | ], 182 | correct: 0 183 | }, 184 | { 185 | question: Which is TRUE about the events load and DOMContentLoaded?, 186 | answers: [ 187 | The DOMContentLoaded event comes after the load event., 188 | DOMContentLoaded is fired when the document has been loaded and parsed; load fires when all files have finished loading, including images., 189 | The load event is fired when only the DOM is loaded and parsed. DOMContentLoaded is fired when the document is fully loaded, including images., 190 | They're the same thing. 191 | ], 192 | correct: 1 193 | }, 194 | { 195 | question: Which of the following is NOT an example of a data structure in JavaScript?, 196 | answers: [ 197 | Object, 198 | Array, 199 | Set in ES2015, 200 | Trie 201 | ], 202 | correct: 3 203 | }, 204 | { 205 | question: Which of the following values is truthy?, 206 | answers: [ 207 | 0, 208 | "" (empty string), 209 | 1, 210 | undefined 211 | ], 212 | correct: 2 213 | }, 214 | { 215 | question: What is an IIFE?, 216 | answers: [ 217 | Immediately Iterable Function Evaluation, 218 | Immediately Invoked Function Expression, 219 | Initially Integrated Functional Element, 220 | Internally Indexed File Extension 221 | ], 222 | correct: 1 223 | }, 224 | { 225 | question: In React, what method is used to change state?, 226 | answers: [ 227 | changeState(), 228 | onChange(), 229 | setState(), 230 | stateSet() 231 | ], 232 | correct: 2 233 | }, 234 | { 235 | question: Which of the following is an aspect of a pure function?, 236 | answers: [ 237 | Doesn't have unintended side effects., 238 | Doesn't rely on, or affect, external state., 239 | Given the same input, it'll always return the same output., 240 | All of the above. 241 | ], 242 | correct: 3 243 | }, 244 | { 245 | question: Where is JavaScript used besides the browser?, 246 | answers: [ 247 | Servers, databases, operating systems, 248 | Desktop apps, mobile web apps, mobile hybrid apps, mobile native apps, 249 | Robots, drones, Internet of Things devices, 250 | All of the above. 251 | ], 252 | correct: 3 253 | }, 254 | { 255 | question: What are higher order components in React?, 256 | answers: [ 257 | They're basically wrappers for other components., 258 | They take in another component as an argument., 259 | They're used to extend or modify the behavior of a wrapped component., 260 | All of the above. 261 | ], 262 | correct: 3 263 | }, 264 | { 265 | question: What does the new keyword do?, 266 | answers: [ 267 | Creates a new empty object., 268 | Sets the prototype of the new object to the constructor's prototype and calls the constructor., 269 | Sets the this variable to the new object and returns the new object., 270 | All of the above. 271 | ], 272 | correct: 3 273 | }, 274 | { 275 | question: How is let different from var?, 276 | answers: [ 277 | let is block scoped., 278 | let isn't hoisted., 279 | let can't be redeclared., 280 | All of the above. 281 | ], 282 | correct: 3 283 | }, 284 | { 285 | question: Which of the following is TRUE about the this keyword?, 286 | answers: [ 287 | If a method is called, this refers to the object that the method is a property of. If the new keyword is used, this is the new object instance., 288 | If call, apply or bind are used, this is the object that's passed in as the argument., 289 | If none of the above conditions are met, this refers to the global object (window in the browser in non-strict mode; global in Node)., 290 | All of the above. 291 | ], 292 | correct: 3 293 | }, 294 | { 295 | question: Which of the following is something to look out for when using arrow functions?, 296 | answers: [ 297 | An arrow function receives the this value of its surrounding lexical scope at the time it was created; it behaves as if it had been created using fn.bind(this)., 298 | If used in an event handler attached to a DOM element, the this variable will refer to the parent (usually the window) object rather than the element you're targeting., 299 | An arrow function can't be used as a constructor., 300 | All of the above. 301 | ], 302 | correct: 3 303 | }, 304 | { 305 | question: Which of the following methods can be used to organize/encapsulate code?, 306 | answers: [ 307 | The module pattern in ES5 or Module Import Export in ES6., 308 | An Immediately Invoked Function Expression., 309 | OO or Objects Linked to Other Objects, 310 | All of the above. 311 | ], 312 | correct: 3 313 | }, 314 | { 315 | question: What's an example of a practical use case for recursion?, 316 | answers: [ 317 | Traversing a tree (e.g., Walking the DOM)., 318 | Flattening a deeply nested array., 319 | Deep freezing a deeply nested object., 320 | All of the above. 321 | ], 322 | correct: 3 323 | }, 324 | { 325 | question: What's the difference between =, ==, and ===?, 326 | answers: [ 327 | = is the basic assignment operator. It assigns the value of its right operand to its left operand., 328 | == compares two values for equality AFTER converting both values to a common type., 329 | === is used for strict equality. It checks for equality of both value AND type., 330 | All of the above. 331 | ], 332 | correct: 3 333 | }, 334 | { 335 | question: Which of the following is true about passing by value vs reference?, 336 | answers: [ 337 | Objects, arrays, and functions are passed by reference., 338 | string, number, boolean, symbol, null and undefined are passed by value., 339 | Primitive types are passed by value and non-primitive types are passed by reference., 340 | All of the above. 341 | ], 342 | correct: 3 343 | }, 344 | { 345 | question: What is a static method?, 346 | answers: [ 347 | A function that exists on an instance, not a class., 348 | A method that only takes one argument., 349 | A function that exists on a class, not an instance., 350 | None of the above. 351 | ], 352 | correct: 2 353 | }, 354 | { 355 | question: Which of the following is TRUE about the difference between undefined and null., 356 | answers: [ 357 | A variable is undefined when it's been declared but hasn't been assigned a value., 358 | null is purposely assigned as a representation of "no value.", 359 | undefined is returned when trying to access a non-existant property of an object., 360 | All of the above. 361 | ], 362 | correct: 3 363 | }, 364 | { 365 | question: In React, which of the following is TRUE about the difference between an element and a component?, 366 | answers: [ 367 | A React element is an object representation of a DOM node., 368 | A component is a class or a function., 369 | A component accepts props and returns a React element., 370 | All of the above. 371 | ], 372 | correct: 3 373 | }, 374 | { 375 | question: In React, which is TRUE about the difference between controlled components and uncontrolled components?, 376 | answers: [ 377 | With controlled components, form data is handled by React., 378 | A controlled input accepts its current value as a prop, and a callback to change that value., 379 | With uncontrolled components, form data is handled by the DOM, and input values can be accessed using refs., 380 | All of the above. 381 | ], 382 | correct: 3 383 | }, 384 | { 385 | question: In React, what's the execution order for methods and lifecycle hooks on initial render?, 386 | answers: [ 387 | componentDidMount()render()getDerivedStateFromProps()constructor(), 388 | render()componentDidMount()constructor()getDerivedStateFromProps(), 389 | constructor()getDerivedStateFromProps()render()componentDidMount(), 390 | None of the above. 391 | ], 392 | correct: 2 393 | }, 394 | { 395 | question: In React, which is TRUE about the difference between a class component, a PureComponent, and a functional component?, 396 | answers: [ 397 | Standard class components have state, lifecycle hooks, and refs., 398 | A PureComponent is just like a class component, but handles shouldComponentUpdate for you, with a shallow check for changes in props or state. This allows for a performance boost by preventing unnecessary rerenders., 399 | Functional components are presentational; they don't have access to state or lifecycle hooks, and can't use refs., 400 | All of the above. 401 | ], 402 | correct: 3 403 | } 404 | ]; 405 | 406 | export default QUESTION_DATA; 407 | -------------------------------------------------------------------------------- /src/helpers/shuffleQuestions.js: -------------------------------------------------------------------------------- 1 | const shuffleQuestions = (arr) => { 2 | return arr.sort((a,b) => Math.random() < .5 ? 1 : -1); 3 | }; 4 | 5 | export default shuffleQuestions; 6 | -------------------------------------------------------------------------------- /src/helpers/tally.js: -------------------------------------------------------------------------------- 1 | const tally = arr => { 2 | return arr.map(item => { 3 | return item.tries; 4 | }).reduce((acc, item) => { 5 | acc[item] = (acc[item] || 0) + 1; 6 | return acc; 7 | }, {}); 8 | }; 9 | 10 | export default tally; 11 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | JavaScript Quiz 10 | 24 | 25 | 26 | 27 | 28 | Fork me on GitHub 29 | 30 |
    31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import QuizApp from './components/QuizApp'; 4 | import './style.css'; 5 | 6 | render( 7 | , 8 | document.getElementById('app') 9 | ); 10 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body, #app, div[data-reactroot] { 6 | height: 100%; 7 | } 8 | 9 | body { 10 | width: 100%; 11 | font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; 12 | font-size: 9px; 13 | background: rgb(249,219,61); 14 | color: #222; 15 | margin: 0; 16 | padding: 0; 17 | overflow-y: scroll; 18 | } 19 | 20 | h1 { 21 | font-weight: normal; 22 | font-size: 2.25em; 23 | text-transform: uppercase; 24 | letter-spacing: -1px; 25 | text-align: center; 26 | } 27 | 28 | h2 { 29 | font-size: 1.25em; 30 | margin: 0 0 15px; 31 | } 32 | 33 | header { 34 | display: flex; 35 | justify-content: space-between; 36 | align-items: center; 37 | padding-top: 20px; 38 | } 39 | 40 | ul { 41 | padding: 0; 42 | } 43 | 44 | p { 45 | margin: 0; 46 | } 47 | 48 | span { 49 | pointer-events: none; 50 | } 51 | 52 | code { 53 | font-family: monospace; 54 | font-size: .8em; 55 | font-weight: 100; 56 | pointer-events: none; 57 | background: rgba(0,0,0,.09); 58 | padding: 3px 4px; 59 | margin: 0 2px; 60 | } 61 | 62 | .score-container, .question-count { 63 | width: 250px; 64 | text-align: center; 65 | } 66 | 67 | .score-container h2, .question-count h2 { 68 | margin-bottom: 0; 69 | font-size: 2em; 70 | font-weight: 400; 71 | } 72 | 73 | .score, .question-number { 74 | font-size: 4em; 75 | font-weight: 100; 76 | } 77 | 78 | .description { 79 | font-size: 1.5em; 80 | } 81 | 82 | .questions { 83 | width: 85%; 84 | margin: 35px auto 0; 85 | } 86 | 87 | .question { 88 | font-size: 2em; 89 | } 90 | 91 | .question-answers { 92 | margin-top: .75em; 93 | padding-left: 1.2em; 94 | } 95 | 96 | .question-answer { 97 | list-style-type: lower-alpha; 98 | cursor: pointer; 99 | padding: .3em; 100 | margin-bottom: .3em; 101 | border: 5px solid transparent; 102 | } 103 | 104 | .question-answer span { 105 | line-height: 1.3; 106 | } 107 | 108 | .answer { 109 | font-size: 1em; 110 | } 111 | 112 | @keyframes slide-in { 113 | 0% { opacity: 0; transform: translate3d(40%, 0, 0); } 114 | 100% { opacity: 1; transform: none; } 115 | } 116 | 117 | .question { 118 | display: none; 119 | animation: slide-in .4s ease; 120 | } 121 | 122 | .question:first-child { 123 | display: block; 124 | } 125 | 126 | .results-container { 127 | width: 100%; 128 | margin: 0 auto; 129 | display: flex; 130 | align-items: center; 131 | justify-content: center; 132 | flex-direction: column; 133 | height: 100%; 134 | font-size: 1.75em; 135 | line-height: 1.75em; 136 | animation: slide-in .4s ease; 137 | } 138 | 139 | .results-total { 140 | margin-top: 15px; 141 | font-size: 1.1em; 142 | } 143 | 144 | .results-container a { 145 | position: relative; 146 | padding: 15px 30px; 147 | margin-top: 30px; 148 | border: 3px solid #111; 149 | background: none; 150 | cursor: pointer; 151 | font-size: .75em; 152 | transition: background .2s; 153 | } 154 | 155 | .results-container a:hover { 156 | background: rgba(255,255,255,.1); 157 | } 158 | 159 | .results-container a:active, { 160 | background: rgba(255,255,255,.5); 161 | top: -2px; 162 | } 163 | 164 | .wrong { 165 | background: rgba(236,100,75,.5); 166 | animation: shake 0.5s cubic-bezier(.35,.05,.20,.99) both; 167 | } 168 | 169 | .right { 170 | background: rgba(135,211,124,.5); 171 | } 172 | 173 | @keyframes shake { 174 | 10%, 90% { 175 | transform: translateX(-1px); 176 | } 177 | 20%, 80% { 178 | transform: translateX(1px); 179 | } 180 | 30%, 50%, 70% { 181 | transform: translateX(-2px); 182 | } 183 | 45%, 55% { 184 | transform: translateX(2px); 185 | } 186 | } 187 | 188 | .correct-modal { 189 | font-size: 5em; 190 | text-align: center; 191 | width: 100%; 192 | background: rgb(252, 235, 148); 193 | padding: 5%; 194 | will-change: transform; 195 | transform: scale(4); 196 | z-index: 2; 197 | opacity: 1; 198 | flex-direction: column; 199 | align-items: center; 200 | justify-content: center; 201 | height: 200px; 202 | overflow: auto; 203 | margin: auto; 204 | position: absolute; 205 | top: 0; 206 | left: 0; 207 | bottom: 0; 208 | right: 0; 209 | backface-visibility: hidden; 210 | -webkit-font-smoothing: antialiased; 211 | display: none; 212 | } 213 | 214 | .correct-modal.modal-enter { 215 | display: flex; 216 | animation: modal-enter 2.3s ease-in both; 217 | } 218 | 219 | .praise, .points { 220 | backface-visibility: hidden; 221 | -webkit-font-smoothing: antialiased; 222 | } 223 | 224 | @keyframes modal-enter { 225 | 0 { 226 | visibility: visible; 227 | opacity: 1; 228 | } 229 | 50% { 230 | transform: scale(1); 231 | opacity: 1; 232 | } 233 | 85% { 234 | opacity: 1; 235 | transform: scale(1); 236 | } 237 | 99% { 238 | opacity: .5; 239 | transform: scale(1); 240 | } 241 | 100% { 242 | display: none; 243 | transform: scale(1); 244 | } 245 | } 246 | 247 | @media (min-width: 600px) { 248 | body { 249 | font-size: 12px; 250 | } 251 | } 252 | 253 | @media (min-width: 900px) { 254 | body { 255 | font-size: 14px; 256 | } 257 | 258 | h1 { 259 | font-size: 3em; 260 | } 261 | 262 | header { 263 | padding: 50px 0 30px; 264 | } 265 | 266 | .questions { 267 | width: 75%; 268 | } 269 | 270 | .question-answer:hover { 271 | border-color: rgba(0,0,0,.5); 272 | } 273 | 274 | .question-answer:focus { 275 | outline: gray solid 1px; 276 | } 277 | 278 | .correct-modal { 279 | height: 300px; 280 | } 281 | } 282 | 283 | @media (min-width: 1400px) { 284 | body { 285 | font-size: 16px; 286 | } 287 | 288 | .correct-modal { 289 | height: 400px; 290 | } 291 | } 292 | 293 | @media (min-width: 1600px) { 294 | body { 295 | overflow: hidden; 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | devtool: 'inline-module-source-map', 6 | entry: [ 7 | './src/main.js' 8 | ], 9 | output: { 10 | path: __dirname + '/dist', 11 | publicPath: '/', 12 | filename: 'bundle.js' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | enforce: 'pre', 18 | test: /\.js$/, 19 | exclude: /node_modules/, 20 | use: [ 21 | 'babel-loader', 22 | 'eslint-loader', 23 | ] 24 | }, 25 | { 26 | test: /\.js$/, 27 | exclude: /node_modules/, 28 | use: ['babel-loader'] 29 | }, 30 | { 31 | test: /\.css$/, 32 | use: [ 33 | 'style-loader', 34 | 'css-loader' 35 | ] 36 | } 37 | ] 38 | }, 39 | resolve: { 40 | extensions: ['.js', '.jsx'] 41 | }, 42 | devServer: { 43 | contentBase: './dist', 44 | hot: true 45 | }, 46 | plugins: [ 47 | new webpack.HotModuleReplacementPlugin(), 48 | new HtmlWebpackPlugin({ 49 | template: __dirname + '/src/index.html', 50 | filename: 'index.html', 51 | inject: 'body' 52 | }) 53 | ] 54 | }; 55 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/main.js', 7 | output: { 8 | path: __dirname + '/dist', 9 | filename: 'bundle.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.js$/, 15 | exclude: /node_modules/, 16 | loader: 'babel-loader' 17 | }, 18 | { 19 | test: /\.css$/, 20 | use: ExtractTextPlugin.extract({ 21 | fallback: 'style-loader', 22 | use: 'css-loader' 23 | }) 24 | } 25 | ] 26 | }, 27 | plugins: [ 28 | new ExtractTextPlugin('style.css'), 29 | new HtmlWebpackPlugin({ 30 | inject: true, 31 | template: './src/index.html', 32 | minify: { 33 | removeComments: true, 34 | collapseWhitespace: true, 35 | removeRedundantAttributes: true, 36 | useShortDoctype: true, 37 | removeEmptyAttributes: true, 38 | removeStyleLinkTypeAttributes: true, 39 | keepClosingSlash: true, 40 | minifyJS: true, 41 | minifyCSS: true, 42 | minifyURLs: true 43 | } 44 | }), 45 | new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }) 46 | ] 47 | }; 48 | --------------------------------------------------------------------------------