├── 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 |
this.submitQuestion(e)}>Submit Question
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 | onPrev(number) }> Previous
19 | )
20 | }
21 |
22 | if (number < total) {
23 | buttons.push(
24 | onNext(number) }> Next
25 | )
26 | }
27 |
28 |
29 | return (
30 |
31 | {buttons}
32 | Mark question for later?
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 |
--------------------------------------------------------------------------------