├── README.md ├── public └── index.html ├── src ├── components │ ├── QuestionCounter.js │ ├── MultipleChoiceQuestion.js │ └── QuestionControls.js ├── index.js ├── reducers.js ├── actions.js └── app.js ├── .gitignore ├── questions.md └── package.json /README.md: -------------------------------------------------------------------------------- 1 | Faiq and I are playing around with React and trying to build a simple application in React that lets you take quizzes. 2 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Quiz App 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/QuestionCounter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class QuestionCounter extends React.Component { 4 | render() { 5 | const { number, total } = this.props 6 | 7 | return ( 8 |

Question {number} of {total}

9 | ) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createStore } from 'redux' 3 | import { Provider } from 'react-redux' 4 | import App from './App' 5 | import reducer from './reducers' 6 | 7 | let store = createStore(reducer) 8 | 9 | React.render( 10 | // The child must be wrapped in a function 11 | // to work around an issue in React 0.13. 12 | 13 | {() => } 14 | , 15 | document.body 16 | ) 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | public/bundle.js 29 | public/styles 30 | -------------------------------------------------------------------------------- /questions.md: -------------------------------------------------------------------------------- 1 | Questions we have about React / Frontend development 2 | 3 | 1. Can we pass backbone models to our React components? Can they only be passed as state? Only as props? Which one is supposed to be immutable again? See [this stackoverflow post](http://stackoverflow.com/questions/21709905/can-i-avoid-forceupdate-when-using-react-with-backbone) 4 | 2. What the hell does Webpack do that Browserify doesn\'t? [Webpack compared link](http://survivejs.com/webpack_react/webpack_compared/) 5 | 3. Why the fuck does React move so fast? 6 | 4. React hot loader - what is this? https://github.com/gaearon/react-transform-boilerplate 7 | 5. What if reducer needs to hit the server before returning new state? 8 | 6. What es isomorphic fetch? 9 | 7. Why can't we dispatch in child components 10 | 11 | Some nice resources: 12 | * http://teropa.info/blog/2015/09/10/full-stack-redux-tutorial.html 13 | * http://survivejs.com/webpack_react/introduction/ 14 | * https://github.com/sprintly/sprintly-kanban 15 | -------------------------------------------------------------------------------- /src/components/MultipleChoiceQuestion.js: -------------------------------------------------------------------------------- 1 | import React, { findDOMNode } from 'react' 2 | 3 | export default class MultipleChoiceQuestion extends React.Component { 4 | 5 | submitQuestion (e) { 6 | const { index, submit } = this.props 7 | const response = findDOMNode(this.refs.options).children[0].value //get the dom element for the input 8 | submit(index, response) 9 | } 10 | 11 | render() { 12 | const {text, choices, index } = this.props 13 | 14 | const choiceHtml = choices.map((choice, i) => { 15 | return ( 16 |
  • 17 | 18 | {choice} 19 | 20 |
  • 21 | ) 22 | }) 23 | 24 | return ( 25 |
    26 |

    {text}

    27 | 28 | 31 | 32 |
    33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/QuestionControls.js: -------------------------------------------------------------------------------- 1 | import React, { findDOMNode } from 'react' 2 | 3 | export default class QuestionControls extends React.Component { 4 | 5 | clickHandler (e) { 6 | const node = findDOMNode(this.refs.markbox) //get the dom element for the input 7 | let value = node.checked 8 | this.props.onMarkQuestion(this.props.number, value) //calls a function set by its parent 9 | } 10 | 11 | render() { 12 | const { number, total, onPrev, onNext } = this.props 13 | 14 | let buttons = [] 15 | 16 | if (number > 0) { 17 | buttons.push( 18 | 19 | ) 20 | } 21 | 22 | if (number < total) { 23 | buttons.push( 24 | 25 | ) 26 | } 27 | 28 | 29 | return ( 30 |
    31 | {buttons} 32 | 33 | this.clickHandler(e)} /> 34 |
    35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quiz-app", 3 | "version": "1.0.0", 4 | "description": "web app that lets you answer questions", 5 | "main": "index.js", 6 | "browserify": { 7 | "transform": [ 8 | "babelify" 9 | ] 10 | }, 11 | "scripts": { 12 | "build": "browserify ./src/index.js > ./public/bundle.js", 13 | "watch": "watchify ./src/index.js -o ./public/bundle.js -dv", 14 | "watch-css": "node-sass -w ./src/styles -o public/styles", 15 | "start": "npm run watch & ecstatic -p 8000 public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/v/quiz-app.git" 20 | }, 21 | "author": "faiq", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/v/quiz-app/issues" 25 | }, 26 | "homepage": "https://github.com/v/quiz-app#readme", 27 | "devDependencies": { 28 | "babelify": "^6.3.0", 29 | "browserify": "^11.2.0", 30 | "node-sass": "^3.3.3", 31 | "watchify": "^3.4.0" 32 | }, 33 | "dependencies": { 34 | "ecstatic": "^1.1.3", 35 | "moment": "^2.10.6", 36 | "react": "^0.13.3", 37 | "react-redux": "^3.1.0", 38 | "redux": "^3.0.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/reducers.js: -------------------------------------------------------------------------------- 1 | import { NEXT_QUESTION, PREV_QUESTION, SUBMIT_QUESTION, MARK_QUESTION } from './actions' 2 | import { combineReducers } from 'redux' 3 | 4 | function currentQuestionIndex (state = 0, action) { 5 | switch (action.type) { 6 | case PREV_QUESTION: 7 | if (state > 0) return state-1 8 | else return state 9 | 10 | case NEXT_QUESTION: 11 | return state+1 12 | default: 13 | return state 14 | } 15 | } 16 | 17 | function questions (state = [], action) { 18 | switch (action.type) { 19 | case MARK_QUESTION: 20 | return [ 21 | ...state.slice(0, action.index), 22 | Object.assign({}, state[action.index], { 23 | marked: action.value 24 | }), 25 | ...state.slice(action.index+1) 26 | ] 27 | case SUBMIT_QUESTION: 28 | return [ 29 | ...state.slice(0, action.index), 30 | Object.assign({}, state[action.index], { 31 | answer: action.value 32 | }), 33 | ...state.slice(action.index+1) 34 | ] 35 | case 'GET_QUESTIONS': 36 | console.log(action.questions) 37 | return action.questions.slice(0) 38 | default: 39 | return state 40 | } 41 | } 42 | 43 | export default combineReducers({questions, currentQuestionIndex}) 44 | -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | // types of actions for reducer 2 | export const NEXT_QUESTION = 'NEXT_QUESTION' 3 | export const PREV_QUESTION = 'PREV_QUESTION' 4 | export const SUBMIT_QUESTION = 'SUBMIT_QUESTION' 5 | export const MARK_QUESTION = 'MARK_QUESTION' 6 | 7 | // action creators for are view controllers 8 | export function nextQuestion (index) { 9 | return { type: NEXT_QUESTION, index } 10 | } 11 | 12 | export function prevQuestion (index) { 13 | return { type: PREV_QUESTION, index } 14 | } 15 | 16 | export function markQuestion (index, value) { 17 | return { type: MARK_QUESTION, value, index } 18 | } 19 | 20 | export function submitQuestion(index, value) { 21 | return { type: SUBMIT_QUESTION, index, value } 22 | } 23 | 24 | export function getQuestions() { 25 | return { 26 | type: 'GET_QUESTIONS', 27 | questions: [ 28 | { 29 | text: "what's the sqrt of 4", 30 | choices: [1, 2, 3, 4], 31 | marked: "", 32 | answer: "" 33 | 34 | }, 35 | { 36 | text: "what's the sqrt of 16", 37 | choices: [1, 2, 3, 4], 38 | marked: "", 39 | answer: "" 40 | 41 | }, 42 | { 43 | text: "what's the sqrt of 9", 44 | choices: [1, 2, 3, 4], 45 | marked: "", 46 | answer: "" 47 | } 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import MultipleChoiceQuestion from './components/MultipleChoiceQuestion' 4 | import QuestionControls from './components/QuestionControls' 5 | import QuestionCounter from './components/QuestionCounter' 6 | import { markQuestion, nextQuestion, prevQuestion, submitQuestion, getQuestions } from './actions' 7 | 8 | class App extends React.Component { 9 | constructor (props) { 10 | super(props) 11 | this.onNext = this.onNext.bind(this) 12 | this.onPrev = this.onPrev.bind(this) 13 | this.onCheck = this.onCheck.bind(this) 14 | this.submitAnswer = this.submitAnswer.bind(this) 15 | } 16 | 17 | componentWillMount () { 18 | const { dispatch } = this.props 19 | dispatch(getQuestions()) 20 | } 21 | 22 | submitAnswer (index, response) { 23 | const { dispatch } = this.props 24 | dispatch(submitQuestion(index, response)) 25 | } 26 | onPrev (number) { 27 | const { dispatch } = this.props 28 | dispatch(prevQuestion(number)) 29 | } 30 | 31 | onNext (number) { 32 | const { dispatch } = this.props 33 | dispatch(nextQuestion(number)) 34 | } 35 | 36 | onCheck(value, questionNumber) { 37 | const { dispatch } = this.props 38 | dispatch(markQuestion(value, questionNumber)) 39 | } 40 | 41 | render() { 42 | const { questions, currentQuestionIndex } = this.props 43 | let currentQuestion = questions.length ? questions[currentQuestionIndex] : { text: '', choices: [] } 44 | return ( 45 |
    46 | 47 | 48 | 49 |
    50 | ) 51 | } 52 | } 53 | 54 | //defines props to inject into App 55 | function select (state) { 56 | return { 57 | currentQuestionIndex: state.currentQuestionIndex, 58 | questions: state.questions 59 | } 60 | } 61 | 62 | export default connect(select)(App) 63 | --------------------------------------------------------------------------------