├── README.md
├── lecture_1
├── README.md
├── browserify_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── robot.js
├── browserify_server_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ └── package.json
├── node_server_example
│ ├── .gitignore
│ ├── README.md
│ ├── app.js
│ ├── package.json
│ └── public
│ │ └── index.html
├── webpack_dev_server_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
└── webpack_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── Robot.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── lecture_2
├── README.md
├── basic_react_app
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── components_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── components_example_multiple_files
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── components
│ │ ├── CommentBox.js
│ │ ├── CommentForm.js
│ │ └── CommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── props_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── props_validation_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── pure_function_component_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── state_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
└── user_input_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── lecture_3
├── README.md
├── debugging_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── robot.js
├── jest_testing_example
│ ├── .babelrc
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentBox.js
│ │ ├── CommentBoxContainer.js
│ │ ├── CommentForm.js
│ │ ├── CommentList.js
│ │ └── __tests__
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentBox-test.js
│ │ │ ├── CommentBoxContainer-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ └── CommentList-test.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── mocha_testing_example
│ ├── .babelrc
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentBox.js
│ │ ├── CommentBoxContainer.js
│ │ ├── CommentForm.js
│ │ └── CommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── test
│ │ ├── Comment-test.js
│ │ ├── CommentBox-test.js
│ │ ├── CommentBoxContainer-test.js
│ │ ├── CommentForm-test.js
│ │ ├── CommentList-test.js
│ │ ├── mocha.opts
│ │ └── test_helper.js
│ └── webpack.config.js
├── presentational_container_component_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentBox.js
│ │ ├── CommentBoxContainer.js
│ │ ├── CommentForm.js
│ │ └── CommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
└── two_way_databinding_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── components
│ ├── Comment.js
│ ├── CommentBox.js
│ ├── CommentBoxContainer.js
│ ├── CommentForm.js
│ └── CommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── webpack.config.js
├── lecture_4
├── README.md
├── comment_list_redux
│ ├── .babelrc
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentBox.js
│ │ ├── CommentForm.js
│ │ └── CommentList.js
│ ├── containers
│ │ └── CommentBoxContainer.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ └── index.js
│ └── webpack.config.js
└── redux_basics_example
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── components
│ └── Counter.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ └── index.js
│ └── webpack.config.js
├── lecture_5
├── README.md
├── async-design-basics
│ ├── .babelrc
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentApp.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── CommentList.js
│ │ └── Fetcher.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── Fetcher.js
│ │ └── FilteredCommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ ├── FetchReducer.js
│ │ └── index.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── actions
│ │ │ └── FetchAction-test.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentApp-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ ├── CommentList-test.js
│ │ │ └── Fetcher-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ ├── CommentFormReducer-test.js
│ │ │ └── FetchReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
├── combine_reducers_example
│ ├── .babelrc
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentApp.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ └── CommentList.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ └── FilteredCommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ └── index.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentApp-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ └── CommentList-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ └── CommentFormReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
├── comment_list_with_filter
│ ├── .babelrc
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentApp.js
│ │ ├── CommentForm.js
│ │ ├── CommentList.js
│ │ └── Filter.js
│ ├── containers
│ │ └── CommentAppContainer.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ └── index.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentApp-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ ├── CommentList-test.js
│ │ │ └── Filter-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ └── reducers-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
├── ramda-example
│ ├── .babelrc
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentApp.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ └── CommentList.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ └── FilteredCommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ └── index.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentApp-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ └── CommentList-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ └── CommentFormReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
├── redux-thunk-basics
│ ├── .babelrc
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentApp.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── CommentList.js
│ │ └── Fetcher.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── Fetcher.js
│ │ └── FilteredCommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ └── index.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentApp-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ ├── CommentList-test.js
│ │ │ └── Fetcher-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ └── CommentFormReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
└── server-request-basics
│ ├── .babelrc
│ ├── .gitignore
│ ├── Comments.json
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ ├── AjaxRequest.js
│ └── index.js
│ ├── components
│ ├── Comment.js
│ ├── CommentApp.js
│ ├── CommentFilter.js
│ ├── CommentForm.js
│ ├── CommentList.js
│ └── Fetcher.js
│ ├── containers
│ ├── CommentFilter.js
│ ├── CommentForm.js
│ ├── Fetcher.js
│ └── FilteredCommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ ├── CommentFilterReducer.js
│ ├── CommentFormReducer.js
│ ├── FetchReducer.js
│ └── index.js
│ ├── test
│ ├── Wrapper.js
│ ├── actions
│ │ └── FetchAction-test.js
│ ├── components
│ │ ├── Comment-test.js
│ │ ├── CommentApp-test.js
│ │ ├── CommentFilter-test.js
│ │ ├── CommentForm-test.js
│ │ ├── CommentList-test.js
│ │ └── Fetcher-test.js
│ ├── mocha.opts
│ ├── reducers
│ │ ├── CommentFilterReducer-test.js
│ │ ├── CommentFormReducer-test.js
│ │ └── FetchReducer-test.js
│ └── test_helper.js
│ └── webpack.config.js
├── lecture_6
├── comment-mirror
│ ├── .babelrc
│ ├── .gitignore
│ ├── Comments.json
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ ├── AjaxRequest.js
│ │ ├── Websocket.js
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentApp.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ └── CommentList.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── FilteredCommentList.js
│ │ └── RemoteComments.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ ├── RemoteReducer.js
│ │ ├── WebsocketReducer.js
│ │ └── index.js
│ ├── server.js
│ ├── server
│ │ ├── HandleIncomingWebSockets.js
│ │ ├── WebSocketServer.js
│ │ └── WebpackDevServer.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── actions
│ │ │ └── Websocket-test.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentApp-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ └── CommentList-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ └── CommentFormReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
├── middleware-basics
│ ├── .babelrc
│ ├── .gitignore
│ ├── Comments.json
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ ├── AjaxRequest.js
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentApp.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── CommentList.js
│ │ └── Fetcher.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── Fetcher.js
│ │ └── FilteredCommentList.js
│ ├── index.html
│ ├── main.js
│ ├── middleware
│ │ ├── ConstantActionLogger.js
│ │ └── ExampleMiddleware.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ ├── FetchReducer.js
│ │ └── index.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── actions
│ │ │ └── FetchAction-test.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentApp-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ ├── CommentList-test.js
│ │ │ └── Fetcher-test.js
│ │ ├── middleware
│ │ │ └── ConstantActionLogger-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ ├── CommentFormReducer-test.js
│ │ │ └── FetchReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
└── websocket-basics
│ ├── .babelrc
│ ├── .gitignore
│ ├── Comments.json
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ ├── AjaxRequest.js
│ ├── Websocket.js
│ └── index.js
│ ├── components
│ ├── Comment.js
│ ├── CommentApp.js
│ ├── CommentFilter.js
│ ├── CommentForm.js
│ ├── CommentList.js
│ └── Fetcher.js
│ ├── containers
│ ├── CommentFilter.js
│ ├── CommentForm.js
│ ├── Fetcher.js
│ └── FilteredCommentList.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ ├── CommentFilterReducer.js
│ ├── CommentFormReducer.js
│ ├── FetchReducer.js
│ ├── WebsocketReducer.js
│ └── index.js
│ ├── server.js
│ ├── test
│ ├── Wrapper.js
│ ├── actions
│ │ ├── FetchAction-test.js
│ │ └── Websocket-test.js
│ ├── components
│ │ ├── Comment-test.js
│ │ ├── CommentApp-test.js
│ │ ├── CommentFilter-test.js
│ │ ├── CommentForm-test.js
│ │ ├── CommentList-test.js
│ │ └── Fetcher-test.js
│ ├── mocha.opts
│ ├── reducers
│ │ ├── CommentFilterReducer-test.js
│ │ ├── CommentFormReducer-test.js
│ │ └── FetchReducer-test.js
│ └── test_helper.js
│ └── webpack.config.js
├── lecture_7
├── react-global-events
│ ├── .babelrc
│ ├── .gitignore
│ ├── Comments.json
│ ├── LICENSE
│ ├── README.md
│ ├── Routes.js
│ ├── actions
│ │ ├── AjaxRequest.js
│ │ ├── GlobalKeys.js
│ │ ├── Websocket.js
│ │ └── index.js
│ ├── components
│ │ ├── AppWrapper.js
│ │ ├── Comment.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── CommentList.js
│ │ ├── CommentListWithFilter.js
│ │ ├── GlobalKeys.js
│ │ └── KeyPressListener.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── FilteredCommentList.js
│ │ ├── GlobalKeys.js
│ │ └── RemoteComments.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ ├── GlobalKey.js
│ │ ├── RemoteReducer.js
│ │ ├── WebsocketReducer.js
│ │ └── index.js
│ ├── server.js
│ ├── server
│ │ ├── HandleIncomingWebSockets.js
│ │ ├── WebSocketServer.js
│ │ └── WebpackDevServer.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── actions
│ │ │ └── Websocket-test.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ ├── CommentList-test.js
│ │ │ └── KeyPressListener-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ └── CommentFormReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
├── react-router-redux
│ ├── .babelrc
│ ├── .gitignore
│ ├── Comments.json
│ ├── LICENSE
│ ├── README.md
│ ├── Routes.js
│ ├── actions
│ │ ├── AjaxRequest.js
│ │ ├── Websocket.js
│ │ └── index.js
│ ├── components
│ │ ├── AppWrapper.js
│ │ ├── Comment.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── CommentList.js
│ │ └── CommentListWithFilter.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── FilteredCommentList.js
│ │ └── RemoteComments.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ ├── RemoteReducer.js
│ │ ├── WebsocketReducer.js
│ │ └── index.js
│ ├── server.js
│ ├── server
│ │ ├── HandleIncomingWebSockets.js
│ │ ├── WebSocketServer.js
│ │ └── WebpackDevServer.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── actions
│ │ │ └── Websocket-test.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ └── CommentList-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ └── CommentFormReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
├── redux-devtools
│ ├── .babelrc
│ ├── .gitignore
│ ├── Comments.json
│ ├── LICENSE
│ ├── README.md
│ ├── actions
│ │ ├── AjaxRequest.js
│ │ ├── Websocket.js
│ │ └── index.js
│ ├── components
│ │ ├── Comment.js
│ │ ├── CommentApp.js
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ └── CommentList.js
│ ├── containers
│ │ ├── CommentFilter.js
│ │ ├── CommentForm.js
│ │ ├── FilteredCommentList.js
│ │ └── RemoteComments.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ │ ├── CommentFilterReducer.js
│ │ ├── CommentFormReducer.js
│ │ ├── RemoteReducer.js
│ │ ├── WebsocketReducer.js
│ │ └── index.js
│ ├── server.js
│ ├── server
│ │ ├── HandleIncomingWebSockets.js
│ │ ├── WebSocketServer.js
│ │ └── WebpackDevServer.js
│ ├── test
│ │ ├── Wrapper.js
│ │ ├── actions
│ │ │ └── Websocket-test.js
│ │ ├── components
│ │ │ ├── Comment-test.js
│ │ │ ├── CommentApp-test.js
│ │ │ ├── CommentFilter-test.js
│ │ │ ├── CommentForm-test.js
│ │ │ └── CommentList-test.js
│ │ ├── mocha.opts
│ │ ├── reducers
│ │ │ ├── CommentFilterReducer-test.js
│ │ │ └── CommentFormReducer-test.js
│ │ └── test_helper.js
│ └── webpack.config.js
└── router-basics
│ ├── .babelrc
│ ├── .gitignore
│ ├── Comments.json
│ ├── LICENSE
│ ├── README.md
│ ├── Routes.js
│ ├── actions
│ ├── AjaxRequest.js
│ ├── Websocket.js
│ └── index.js
│ ├── components
│ ├── AppWrapper.js
│ ├── Comment.js
│ ├── CommentFilter.js
│ ├── CommentForm.js
│ ├── CommentList.js
│ └── CommentListWithFilter.js
│ ├── containers
│ ├── CommentFilter.js
│ ├── CommentForm.js
│ ├── FilteredCommentList.js
│ └── RemoteComments.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ ├── reducers
│ ├── CommentFilterReducer.js
│ ├── CommentFormReducer.js
│ ├── RemoteReducer.js
│ ├── WebsocketReducer.js
│ └── index.js
│ ├── server.js
│ ├── server
│ ├── HandleIncomingWebSockets.js
│ ├── WebSocketServer.js
│ └── WebpackDevServer.js
│ ├── test
│ ├── Wrapper.js
│ ├── actions
│ │ └── Websocket-test.js
│ ├── components
│ │ ├── Comment-test.js
│ │ ├── CommentFilter-test.js
│ │ ├── CommentForm-test.js
│ │ └── CommentList-test.js
│ ├── mocha.opts
│ ├── reducers
│ │ ├── CommentFilterReducer-test.js
│ │ └── CommentFormReducer-test.js
│ └── test_helper.js
│ └── webpack.config.js
└── lecture_8
├── redux_cache_and_memoize
├── .babelrc
├── .gitignore
├── README.md
├── Words.json
├── css
│ └── base.css
├── index.html
├── js
│ ├── actions
│ │ ├── AjaxRequest.js
│ │ ├── Remote.js
│ │ ├── Websocket.js
│ │ └── index.js
│ ├── components
│ │ ├── Game.js
│ │ ├── Letter.js
│ │ ├── Remote.js
│ │ ├── SpeedTyper.js
│ │ ├── StartButton.js
│ │ ├── Stats.js
│ │ ├── Typing.js
│ │ ├── Word.js
│ │ ├── Words.js
│ │ └── words
│ │ │ ├── CurrentWords.js
│ │ │ ├── NextWords.js
│ │ │ └── PastWords.js
│ ├── containers
│ │ ├── HighScoreContainer.js
│ │ ├── RemoteContainer.js
│ │ ├── RemoteStatsContainer.js
│ │ ├── RemoteWordsContainer.js
│ │ ├── StartButtonContainer.js
│ │ ├── StatsContainer.js
│ │ ├── TickingContainer.js
│ │ ├── TypingContainer.js
│ │ └── words
│ │ │ ├── CurrentWordContainer.js
│ │ │ ├── NextWordsContainer.js
│ │ │ └── PastWordsContainer.js
│ ├── main.js
│ ├── middlewares
│ │ ├── ActionsPerMinuteLogger.js
│ │ └── WebsocketPublisher.js
│ ├── profile.js
│ └── reducers
│ │ ├── Remote.js
│ │ ├── Websocket.js
│ │ ├── currentGame.js
│ │ ├── index.js
│ │ └── pastGames.js
├── package.json
├── server.js
├── server
│ ├── HandleIncomingWebSockets.js
│ ├── WebSocketServer.js
│ └── WebpackDevServer.js
├── test
│ ├── Wrapper.js
│ ├── actions
│ │ ├── Websocket-test.js
│ │ └── actions.spec.js
│ ├── components
│ │ ├── Game.spec.js
│ │ ├── Letter.spec.js
│ │ ├── Remote.spec.js
│ │ ├── SpeedTyper.spec.js
│ │ ├── StartButton.spec.js
│ │ ├── Stats.spec.js
│ │ ├── Typing.spec.js
│ │ ├── Word.spec.js
│ │ └── Words.spec.js
│ ├── containers
│ │ └── HighScoreContainer.spec.js
│ ├── middlewares
│ │ ├── ActionsPerMinuteLogger.spec.js
│ │ └── WebsocketPublisher.spec.js
│ ├── mocha.opts
│ ├── reducers
│ │ ├── Remote.spec.js
│ │ ├── Websocket.spec.js
│ │ ├── currentGame.spec.js
│ │ ├── pastGames.spec.js
│ │ └── selectors.spec.js
│ └── test_helper.js
└── webpack.config.js
├── redux_cache_hit
├── .babelrc
├── .gitignore
├── README.md
├── Words.json
├── css
│ └── base.css
├── index.html
├── js
│ ├── actions
│ │ ├── AjaxRequest.js
│ │ ├── Remote.js
│ │ ├── Websocket.js
│ │ └── index.js
│ ├── components
│ │ ├── Game.js
│ │ ├── Letter.js
│ │ ├── Remote.js
│ │ ├── SpeedTyper.js
│ │ ├── StartButton.js
│ │ ├── Stats.js
│ │ ├── Typing.js
│ │ ├── Word.js
│ │ ├── Words.js
│ │ └── words
│ │ │ ├── CurrentWords.js
│ │ │ ├── NextWords.js
│ │ │ └── PastWords.js
│ ├── containers
│ │ ├── HighScoreContainer.js
│ │ ├── RemoteContainer.js
│ │ ├── RemoteStatsContainer.js
│ │ ├── RemoteWordsContainer.js
│ │ ├── StartButtonContainer.js
│ │ ├── StatsContainer.js
│ │ ├── TickingContainer.js
│ │ ├── TypingContainer.js
│ │ └── words
│ │ │ ├── CurrentWordContainer.js
│ │ │ ├── NextWordsContainer.js
│ │ │ └── PastWordsContainer.js
│ ├── main.js
│ ├── middlewares
│ │ ├── ActionsPerMinuteLogger.js
│ │ └── WebsocketPublisher.js
│ ├── profile.js
│ └── reducers
│ │ ├── Remote.js
│ │ ├── Websocket.js
│ │ ├── currentGame.js
│ │ ├── index.js
│ │ └── pastGames.js
├── package.json
├── server.js
├── server
│ ├── HandleIncomingWebSockets.js
│ ├── WebSocketServer.js
│ └── WebpackDevServer.js
├── test
│ ├── Wrapper.js
│ ├── actions
│ │ ├── Websocket-test.js
│ │ └── actions.spec.js
│ ├── components
│ │ ├── Game.spec.js
│ │ ├── Letter.spec.js
│ │ ├── Remote.spec.js
│ │ ├── SpeedTyper.spec.js
│ │ ├── StartButton.spec.js
│ │ ├── Stats.spec.js
│ │ ├── Typing.spec.js
│ │ ├── Word.spec.js
│ │ └── Words.spec.js
│ ├── containers
│ │ └── HighScoreContainer.spec.js
│ ├── middlewares
│ │ ├── ActionsPerMinuteLogger.spec.js
│ │ └── WebsocketPublisher.spec.js
│ ├── mocha.opts
│ ├── reducers
│ │ ├── Remote.spec.js
│ │ ├── Websocket.spec.js
│ │ ├── currentGame.spec.js
│ │ ├── pastGames.spec.js
│ │ └── selectors.spec.js
│ └── test_helper.js
└── webpack.config.js
└── redux_cache_miss
├── .babelrc
├── .gitignore
├── README.md
├── Words.json
├── css
└── base.css
├── index.html
├── js
├── actions
│ ├── AjaxRequest.js
│ ├── Remote.js
│ ├── Websocket.js
│ └── index.js
├── components
│ ├── Game.js
│ ├── Letter.js
│ ├── Remote.js
│ ├── SpeedTyper.js
│ ├── StartButton.js
│ ├── Stats.js
│ ├── Typing.js
│ ├── Word.js
│ ├── Words.js
│ └── words
│ │ ├── CurrentWords.js
│ │ ├── NextWords.js
│ │ └── PastWords.js
├── containers
│ ├── HighScoreContainer.js
│ ├── RemoteContainer.js
│ ├── RemoteStatsContainer.js
│ ├── RemoteWordsContainer.js
│ ├── StartButtonContainer.js
│ ├── StatsContainer.js
│ ├── TickingContainer.js
│ ├── TypingContainer.js
│ └── words
│ │ ├── CurrentWordContainer.js
│ │ ├── NextWordsContainer.js
│ │ └── PastWordsContainer.js
├── main.js
├── middlewares
│ ├── ActionsPerMinuteLogger.js
│ └── WebsocketPublisher.js
├── profile.js
└── reducers
│ ├── Remote.js
│ ├── Websocket.js
│ ├── currentGame.js
│ ├── index.js
│ └── pastGames.js
├── package.json
├── server.js
├── server
├── HandleIncomingWebSockets.js
├── WebSocketServer.js
└── WebpackDevServer.js
├── test
├── Wrapper.js
├── actions
│ ├── Websocket-test.js
│ └── actions.spec.js
├── components
│ ├── Game.spec.js
│ ├── Letter.spec.js
│ ├── Remote.spec.js
│ ├── SpeedTyper.spec.js
│ ├── StartButton.spec.js
│ ├── Stats.spec.js
│ ├── Typing.spec.js
│ ├── Word.spec.js
│ └── Words.spec.js
├── containers
│ └── HighScoreContainer.spec.js
├── middlewares
│ ├── ActionsPerMinuteLogger.spec.js
│ └── WebsocketPublisher.spec.js
├── mocha.opts
├── reducers
│ ├── Remote.spec.js
│ ├── Websocket.spec.js
│ ├── currentGame.spec.js
│ ├── pastGames.spec.js
│ └── selectors.spec.js
└── test_helper.js
└── webpack.config.js
/lecture_1/README.md:
--------------------------------------------------------------------------------
1 | # Lecture 1
2 |
3 | Lecture 1 gives and overview about build tools and the JavaScript ecosystem.
4 |
5 | After Lecture 1 students should be able to bootstrap trivial JavaScript applications
6 | using modules and modern JavaScript syntax.
7 |
--------------------------------------------------------------------------------
/lecture_1/browserify_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_1/browserify_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lecture_1/browserify_example/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Robot = require('./robot');
4 | var robot = new Robot();
5 | robot.beep();
6 |
7 |
--------------------------------------------------------------------------------
/lecture_1/browserify_example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "javascript",
3 | "description": "",
4 | "main": "main.js",
5 | "dependencies": {
6 | "babel-cli": "^6.5.1",
7 | "babel-preset-es2015": "^6.5.0",
8 | "babelify": "^7.2.0"
9 | },
10 | "devDependencies": {},
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "author": "",
15 | "license": "MIT"
16 | }
17 |
--------------------------------------------------------------------------------
/lecture_1/browserify_example/robot.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | class Robot {
4 | beep() {
5 | console.log("boop");
6 | };
7 | }
8 |
9 | module.exports = Robot;
10 |
11 |
--------------------------------------------------------------------------------
/lecture_1/browserify_server_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_1/browserify_server_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lecture_1/browserify_server_example/main.js:
--------------------------------------------------------------------------------
1 | console.log("Boop!");
2 |
--------------------------------------------------------------------------------
/lecture_1/browserify_server_example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "javascript",
3 | "description": "",
4 | "main": "main.js",
5 | "dependencies": {
6 | "babel-cli": "^6.5.1",
7 | "babel-preset-es2015": "^6.5.0",
8 | "babelify": "^7.2.0",
9 | "browserify": "^13.0.0",
10 | "watchify": "^3.7.0"
11 | },
12 | "devDependencies": {},
13 | "scripts": {
14 | "test": "echo \"Error: no test specified\" && exit 1"
15 | },
16 | "author": "",
17 | "license": "MIT"
18 | }
19 |
--------------------------------------------------------------------------------
/lecture_1/node_server_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_1/node_server_example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-server",
3 | "version": "0.0.0",
4 | "description": "A competitive speed typer.",
5 | "main": "server.js",
6 | "author": "stenver",
7 | "dependencies": {
8 | "body-parser": "^1.15.0",
9 | "express": "^4.13.4",
10 | "path": "^0.12.7"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lecture_1/webpack_dev_server_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_1/webpack_dev_server_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lecture_1/webpack_dev_server_example/main.js:
--------------------------------------------------------------------------------
1 | console.log("Boop!");
2 |
--------------------------------------------------------------------------------
/lecture_1/webpack_dev_server_example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "javascript",
3 | "description": "",
4 | "main": "main.js",
5 | "dependencies": {
6 | "babel-cli": "^6.5.1",
7 | "babel-preset-es2015": "^6.5.0",
8 | "babelify": "^7.2.0"
9 | },
10 | "devDependencies": {},
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "author": "",
15 | "license": "MIT"
16 | }
17 |
--------------------------------------------------------------------------------
/lecture_1/webpack_dev_server_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | devServer: {
7 | port: 3000
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/lecture_1/webpack_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_1/webpack_example/README.md:
--------------------------------------------------------------------------------
1 | # Webpack example
2 |
3 | To use:
4 | ```
5 | npm install
6 | webpack
7 | open index.html // On Mac, this is valid command in terminal
8 | ```
9 |
10 | Open the console in your browser, you should see "boop"
11 |
12 | ## Explanation
13 | npm install installs all the package dependencies
14 |
15 | Webpack bundles the file tree extracted from main.js into a single file - bundle.js. It has a babel-loader loader, which transpiles the es2015 code into standard javascript
16 |
17 | Opening index.html shows the results. The index.html requires the generated bundle.js
18 |
--------------------------------------------------------------------------------
/lecture_1/webpack_example/Robot.js:
--------------------------------------------------------------------------------
1 | define( () => {
2 | "use strict";
3 |
4 | class Robot {
5 | beep() {
6 | console.log("boop");
7 | };
8 | };
9 | return Robot;
10 | });
11 |
--------------------------------------------------------------------------------
/lecture_1/webpack_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lecture_1/webpack_example/main.js:
--------------------------------------------------------------------------------
1 | define(['./Robot'], (Robot) => {
2 | var robot = new Robot();
3 | robot.beep();
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lecture_1/webpack_example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "javascript",
3 | "description": "",
4 | "main": "main.js",
5 | "dependencies": {
6 | "babel-cli": "^6.5.1",
7 | "babel-loader": "^6.2.4",
8 | "babel-preset-es2015": "^6.5.0",
9 | "babelify": "^7.2.0"
10 | },
11 | "devDependencies": {},
12 | "scripts": {
13 | "test": "echo \"Error: no test specified\" && exit 1",
14 | "start": "./node_modules/.bin/webpack-dev-server --port 3000"
15 | },
16 | "author": "",
17 | "license": "MIT"
18 | }
19 |
--------------------------------------------------------------------------------
/lecture_1/webpack_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel-loader',
10 | query: {
11 | presets: 'es2015',
12 | },
13 | }
14 | ]
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/lecture_2/README.md:
--------------------------------------------------------------------------------
1 | # Lecture 2
2 |
3 | Lecture 2 introduces React and its core principles.
4 |
5 | After Lecture 2 you should be able to build trivial React applications.
6 |
7 | Most of the examples borrow heavily from react official tutorial found here:
8 | https://facebook.github.io/react/docs/tutorial.html
9 |
--------------------------------------------------------------------------------
/lecture_2/basic_react_app/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_2/basic_react_app/README.md:
--------------------------------------------------------------------------------
1 | # Basic react example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm start
7 | ```
8 |
9 | Open http://localhost:3000 in your browser
10 |
11 | ## Explanation
12 |
13 | We needed to add a couple of dependencies for react to work with JSX:
14 | ```
15 | react
16 | react-dom
17 | babel-loader
18 | babel-core
19 | babel-preset-es2015
20 | babel-preset-react
21 | ```
22 | The webpack configuration has assed `react` preset for the transpiler.
23 |
24 | Once the DOM has loaded, our react script is loaded and react renders itself on the Div with id "content".
25 |
--------------------------------------------------------------------------------
/lecture_2/basic_react_app/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_2/basic_react_app/main.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom"],
2 | (React, ReactDOM) => {
3 |
4 | ReactDOM.render(
5 | Hello! My first react app!
,
6 | document.getElementById('content')
7 | );
8 | });
9 |
--------------------------------------------------------------------------------
/lecture_2/basic_react_app/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_2/components_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_2/components_example/README.md:
--------------------------------------------------------------------------------
1 | # Components example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm start
7 | ```
8 |
9 | Open http://localhost:3000 in your browser
10 |
11 | ## Explanation
12 |
13 | React renders component CommentBox. CommentBox composes of sub components.
14 |
--------------------------------------------------------------------------------
/lecture_2/components_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_2/components_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_2/components_example_multiple_files/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_2/components_example_multiple_files/README.md:
--------------------------------------------------------------------------------
1 | # Components example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm start
7 | ```
8 |
9 | Open http://localhost:3000 in your browser
10 |
11 | ## Explanation
12 |
13 | React renders component CommentBox. CommentBox composes of sub components.
14 | The comments prop is passed into CommentBox and from there to CommentList.
15 | In CommentList the comments are iterated over and a div is created for each one.
16 |
--------------------------------------------------------------------------------
/lecture_2/components_example_multiple_files/components/CommentBox.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom", "./CommentList", "./CommentForm"],
2 | (React, ReactDOM, CommentList, CommentForm) => {
3 |
4 | var CommentBox = React.createClass({
5 | render: function() {
6 | return (
7 |
8 |
Comments
9 |
10 |
11 |
12 | );
13 | }
14 | });
15 |
16 | return CommentBox;
17 | });
18 |
--------------------------------------------------------------------------------
/lecture_2/components_example_multiple_files/components/CommentForm.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom"],
2 | (React, ReactDOM) => {
3 |
4 | var CommentForm = React.createClass({
5 | render: function() {
6 | return (
7 |
8 | Hello, world! I am a CommentForm.
9 |
10 | );
11 | }
12 | });
13 |
14 | return CommentForm;
15 | })
16 |
--------------------------------------------------------------------------------
/lecture_2/components_example_multiple_files/components/CommentList.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom"],
2 | (React, ReactDOM) => {
3 |
4 | var CommentList = React.createClass({
5 | render: function() {
6 | var comments = this.props.comments
7 | .map((comment, i) => {comment}
)
8 |
9 | return (
10 |
11 | Hello, world! I am a CommentList.
12 | {comments}
13 |
14 | );
15 | }
16 | });
17 |
18 | return CommentList;
19 | });
20 |
--------------------------------------------------------------------------------
/lecture_2/components_example_multiple_files/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_2/components_example_multiple_files/main.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom", "./components/CommentBox"],
2 | (React, ReactDOM, CommentBox) => {
3 |
4 | ReactDOM.render(
5 | React.createElement(CommentBox, {comments: ['Comment 1', 'Comment 2']}),
6 | document.getElementById('content')
7 | );
8 | });
9 |
--------------------------------------------------------------------------------
/lecture_2/components_example_multiple_files/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_2/props_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_2/props_example/README.md:
--------------------------------------------------------------------------------
1 | # Props example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm start
7 | ```
8 |
9 | Open http://localhost:3000 in your browser
10 |
11 | ## Explanation
12 |
13 | We created a Comment component which has props. The values to the props are handed by comment list. The props are immutable in Comment component and cannot be changed.
14 |
15 | The props.children in the end of Comment renders whatever child nodes were passed down from the parent. In this case the comment text.
16 |
17 | The CommentForm has #getDefaultProps which specifies the default props value for this component. This can be overwritten by a parent component.
18 |
--------------------------------------------------------------------------------
/lecture_2/props_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_2/props_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_2/props_validation_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_2/props_validation_example/README.md:
--------------------------------------------------------------------------------
1 | # Props validation example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm start
7 | ```
8 |
9 | Open http://localhost:3000 in your browser
10 |
11 | Open console
12 |
13 | ## Explanation
14 |
15 | The propTypes checks that the props that are passed from the parent always are of correct type. The isRequired means that if the property value is missing, an error is thrown.
16 |
17 | These checks are only made in development mode and you can check them out if you open the console.
18 |
--------------------------------------------------------------------------------
/lecture_2/props_validation_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_2/props_validation_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_2/pure_function_component_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_2/pure_function_component_example/README.md:
--------------------------------------------------------------------------------
1 | # Pure function example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm start
7 | ```
8 |
9 | Open http://localhost:3000 in your browser
10 |
11 | Open console
12 |
13 | ## Explanation
14 |
15 | The comment was turned into pure function
16 | ```
17 | A pure function is a function where the return value is only determined by its input values, without observable side effects.
18 | ```
19 |
20 | You can even add validation to props.
21 |
--------------------------------------------------------------------------------
/lecture_2/pure_function_component_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_2/pure_function_component_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_2/state_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_2/state_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_2/state_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_2/user_input_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_2/user_input_example/README.md:
--------------------------------------------------------------------------------
1 | # User input example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm start
7 | ```
8 |
9 | Open http://localhost:3000 in your browser
10 |
11 | ## Explanation
12 |
13 | Look at CommentForm. There are 2 input fields. First one has static value which cannot be changed by user. Console also prints a warning to explicitly set it to read-only. Other has dynamic value which can be changed by the user. The value change function is handed on onChange attribute
14 |
15 |
--------------------------------------------------------------------------------
/lecture_2/user_input_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_2/user_input_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_3/README.md:
--------------------------------------------------------------------------------
1 | # Lecture 3
2 |
3 | Lecture 3 introduces Presentational-Container pattern, 2-way data flow and testing.
4 |
5 | After Lecture 3 you should be able to build React applications which can contain functionality.
6 |
7 | The examples continue from where lecture 2 left off
8 |
--------------------------------------------------------------------------------
/lecture_3/debugging_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_3/debugging_example/README.md:
--------------------------------------------------------------------------------
1 | # Browserify example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm start
7 | ```
8 |
9 | Open the console in your browser, you should see a breakpoint on `debugger`
10 |
11 | ## Explanation
12 |
13 | Sourcemaps map the bundle.js back to the bundled files. This allows us to see the code in Robot.js.
14 | We can also set breakpoints by clicking on line numbers on the left.
15 |
--------------------------------------------------------------------------------
/lecture_3/debugging_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lecture_3/debugging_example/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Robot = require('./robot');
4 | var robot = new Robot();
5 | robot.beep();
6 |
7 |
--------------------------------------------------------------------------------
/lecture_3/debugging_example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "javascript",
3 | "description": "",
4 | "main": "main.js",
5 | "dependencies": {
6 | "babel-cli": "^6.5.1",
7 | "babel-preset-es2015": "^6.5.0",
8 | "babelify": "^7.2.0",
9 | "beefy": "^2.1.6",
10 | "browserify": "^13.0.0",
11 | "watchify": "^3.7.0"
12 | },
13 | "devDependencies": {},
14 | "scripts": {
15 | "test": "echo \"Error: no test specified\" && exit 1",
16 | "start": "beefy main.js:bundle.js 3000 --live"
17 | },
18 | "author": "",
19 | "license": "MIT"
20 | }
21 |
--------------------------------------------------------------------------------
/lecture_3/debugging_example/robot.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | class Robot {
4 | beep() {
5 | debugger
6 | console.log("boop");
7 | };
8 | }
9 |
10 | module.exports = Robot;
11 |
12 |
--------------------------------------------------------------------------------
/lecture_3/jest_testing_example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_3/jest_testing_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_3/jest_testing_example/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_3/jest_testing_example/components/CommentBox.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import CommentList from "./CommentList";
4 | import CommentForm from "./CommentForm";
5 |
6 | const CommentBox = (props) => {
7 | return (
8 |
9 |
Comments
10 |
11 |
12 |
13 | );
14 | };
15 | CommentBox.propTypes = {
16 | comments: React.PropTypes.array.isRequired,
17 | onCommentSubmit: React.PropTypes.func.isRequired,
18 | };
19 | export { CommentBox as default };
20 |
--------------------------------------------------------------------------------
/lecture_3/jest_testing_example/components/CommentList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import Comment from "./Comment";
4 |
5 | const CommentList = (props) => {
6 | var comments = props.comments.map((comment) => {
7 | return {comment.text};
8 | })
9 | return (
10 |
11 | {comments}
12 |
13 | );
14 | };
15 | CommentList.propTypes = {
16 | comments: React.PropTypes.array.isRequired,
17 | };
18 | export { CommentList as default };
19 |
--------------------------------------------------------------------------------
/lecture_3/jest_testing_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_3/jest_testing_example/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import CommentBoxContainer from "./components/CommentBoxContainer";
4 |
5 | ReactDOM.render(
6 | ,
7 | document.getElementById('content')
8 | );
9 |
10 |
--------------------------------------------------------------------------------
/lecture_3/jest_testing_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/components/CommentBox.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import CommentList from "./CommentList";
4 | import CommentForm from "./CommentForm";
5 |
6 | const CommentBox = (props) => {
7 | return (
8 |
9 |
Comments
10 |
11 |
12 |
13 | );
14 | };
15 | CommentBox.propTypes = {
16 | comments: React.PropTypes.array.isRequired,
17 | onCommentSubmit: React.PropTypes.func.isRequired,
18 | };
19 | export { CommentBox as default };
20 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/components/CommentList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import Comment from "./Comment";
4 |
5 | const CommentList = (props) => {
6 | var comments = props.comments.map((comment) => {
7 | return {comment.text};
8 | })
9 | return (
10 |
11 | {comments}
12 |
13 | );
14 | };
15 | CommentList.propTypes = {
16 | comments: React.PropTypes.array.isRequired,
17 | };
18 | export { CommentList as default };
19 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import CommentBoxContainer from "./components/CommentBoxContainer";
4 |
5 | ReactDOM.render(
6 | ,
7 | document.getElementById('content')
8 | );
9 |
10 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_3/mocha_testing_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_3/presentational_container_component_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_3/presentational_container_component_example/components/Comment.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom"],
2 | (React, ReactDOM) => {
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | return Comment;
19 | });
20 |
--------------------------------------------------------------------------------
/lecture_3/presentational_container_component_example/components/CommentBox.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom", "./CommentList.js", "./CommentForm.js"],
2 | (React, ReactDOM, CommentList, CommentForm) => {
3 |
4 | const CommentBox = (props) => {
5 | return (
6 |
7 |
Comments
8 |
9 |
10 |
11 | );
12 | };
13 | CommentBox.propTypes = {
14 | comments: React.PropTypes.array.isRequired,
15 | onCommentSubmit: React.PropTypes.func.isRequired,
16 | };
17 |
18 | return CommentBox;
19 | });
20 |
--------------------------------------------------------------------------------
/lecture_3/presentational_container_component_example/components/CommentList.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom", "./Comment.js"],
2 | (React, ReactDOM, Comment) => {
3 |
4 | const CommentList = (props) => {
5 | var comments = props.comments.map((comment) => {
6 | return {comment.text};
7 | })
8 | return (
9 |
10 | {comments}
11 |
12 | );
13 | };
14 | CommentList.propTypes = {
15 | comments: React.PropTypes.array.isRequired,
16 | };
17 | return CommentList;
18 | });
19 |
--------------------------------------------------------------------------------
/lecture_3/presentational_container_component_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_3/presentational_container_component_example/main.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom", "./components/CommentBoxContainer.js"],
2 | (React, ReactDOM, CommentBoxContainer) => {
3 |
4 | ReactDOM.render(
5 | ,
6 | document.getElementById('content')
7 | );
8 | });
9 |
10 |
--------------------------------------------------------------------------------
/lecture_3/presentational_container_component_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_3/two_way_databinding_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_3/two_way_databinding_example/components/Comment.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom"],
2 | (React, ReactDOM) => {
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | return Comment;
19 | });
20 |
--------------------------------------------------------------------------------
/lecture_3/two_way_databinding_example/components/CommentBox.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom", "./CommentList.js", "./CommentForm.js"],
2 | (React, ReactDOM, CommentList, CommentForm) => {
3 |
4 | const CommentBox = (props) => {
5 | return (
6 |
7 |
Comments
8 |
9 |
10 |
11 | );
12 | };
13 | CommentBox.propTypes = {
14 | comments: React.PropTypes.array.isRequired,
15 | onCommentSubmit: React.PropTypes.func.isRequired,
16 | };
17 |
18 | return CommentBox;
19 | });
20 |
--------------------------------------------------------------------------------
/lecture_3/two_way_databinding_example/components/CommentList.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom", "./Comment.js"],
2 | (React, ReactDOM, Comment) => {
3 |
4 | const CommentList = (props) => {
5 | var comments = props.comments.map((comment) => {
6 | return {comment.text};
7 | })
8 | return (
9 |
10 | {comments}
11 |
12 | );
13 | };
14 | CommentList.propTypes = {
15 | comments: React.PropTypes.array.isRequired,
16 | };
17 | return CommentList;
18 | });
19 |
--------------------------------------------------------------------------------
/lecture_3/two_way_databinding_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_3/two_way_databinding_example/main.js:
--------------------------------------------------------------------------------
1 | define(["react", "react-dom", "./components/CommentBoxContainer.js"],
2 | (React, ReactDOM, CommentBoxContainer) => {
3 |
4 | ReactDOM.render(
5 | ,
6 | document.getElementById('content')
7 | );
8 | });
9 |
10 |
--------------------------------------------------------------------------------
/lecture_3/two_way_databinding_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './main.js',
5 | output: {
6 | filename: 'bundle.js'
7 | },
8 | module: {
9 | loaders: [
10 | {
11 | loader: 'babel',
12 | query: {
13 | presets: ['react', 'es2015'],
14 | },
15 | }
16 | ]
17 | },
18 | devServer: {
19 | port: 3000
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/lecture_4/README.md:
--------------------------------------------------------------------------------
1 | # State and Redux
2 |
3 | This lecture we will put a big focus on [Redux](https://github.com/reactjs/redux). It is a great library for handling state in front-end application which works great with React.
4 |
5 | The examples borrow heavily from [Redux official guides](http://redux.js.org/).
6 |
7 | The comment list example builds on top of [React official guide](https://facebook.github.io/react/docs/tutorial.html)
8 |
--------------------------------------------------------------------------------
/lecture_4/comment_list_redux/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_4/comment_list_redux/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_4/comment_list_redux/actions/index.js:
--------------------------------------------------------------------------------
1 | // Action creators can have side-effects
2 | let nextId = 1;
3 |
4 | export const addComment = (author, text) => {
5 | return {
6 | type: "ADD_COMMENT",
7 | payload: { author: author, text: text, id: nextId++}
8 | }
9 | }
10 |
11 | export const setAuthor = (author) => {
12 | return {
13 | type: "SET_AUTHOR",
14 | payload: author
15 | }
16 | }
17 |
18 | export const setText = (text) => {
19 | return {
20 | type: "SET_TEXT",
21 | payload: text
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lecture_4/comment_list_redux/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_4/comment_list_redux/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_4/comment_list_redux/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from 'react-redux'
4 | import { createStore } from 'redux'
5 | import commentsApp from './reducers'
6 | import CommentBoxContainer from "./containers/CommentBoxContainer";
7 |
8 | let store = createStore(commentsApp)
9 |
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('content')
15 | );
16 |
17 |
--------------------------------------------------------------------------------
/lecture_4/comment_list_redux/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_4/redux_basics_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_4/redux_basics_example/components/Counter.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 |
3 | const Counter = (props) => {
4 | const { value, onIncrement, onDecrement } = props
5 | return (
6 |
7 | Clicked: {value} times
8 | {' '}
9 |
12 | {' '}
13 |
16 |
17 | )
18 | }
19 |
20 | Counter.propTypes = {
21 | value: PropTypes.number.isRequired,
22 | onIncrement: PropTypes.func.isRequired,
23 | onDecrement: PropTypes.func.isRequired
24 | }
25 |
26 | export { Counter as default }
27 |
28 |
--------------------------------------------------------------------------------
/lecture_4/redux_basics_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_4/redux_basics_example/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { createStore } from 'redux'
4 | import Counter from './components/Counter'
5 | import counter from './reducers'
6 |
7 | const store = createStore(counter)
8 |
9 | function render() {
10 | ReactDOM.render(
11 | store.dispatch({ type: 'INCREMENT' })}
14 | onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
15 | />,
16 | document.getElementById('content')
17 | )
18 | }
19 |
20 | render()
21 | store.subscribe(render)
22 |
23 |
--------------------------------------------------------------------------------
/lecture_4/redux_basics_example/reducers/index.js:
--------------------------------------------------------------------------------
1 | export default function counter(state = 0, action) {
2 | switch (action.type) {
3 | case 'INCREMENT':
4 | return state + 1
5 | case 'DECREMENT':
6 | return state - 1
7 | default:
8 | return state
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/lecture_4/redux_basics_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_5/README.md:
--------------------------------------------------------------------------------
1 | # Splitting App state and asynchronous actions
2 |
3 | This lecture will dig deeper into [Redux](https://github.com/reactjs/redux).
4 | We'll look into splitting the app state using reducer composition and creating asynchronous actions.
5 |
6 | The examples borrow heavily from [Redux official guides](http://redux.js.org/).
7 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 | import Fetcher from "../containers/Fetcher";
7 |
8 | const CommentApp = () => {
9 | return (
10 |
11 |
Comments
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default CommentApp;
21 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from 'react-redux'
4 | import { createStore, applyMiddleware } from 'redux'
5 | import thunkMiddleware from 'redux-thunk'
6 | import commentsApp from './reducers'
7 | import CommentApp from "./components/CommentApp";
8 |
9 | let store = createStore(commentsApp, applyMiddleware(thunkMiddleware))
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | document.getElementById('content')
16 | );
17 |
18 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/reducers/FetchReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | inProgress: false,
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'COMMENT_FETCH_REQUESTED':
10 | return R.merge(state, {inProgress: true})
11 | case 'COMMENT_FETCH_STOPPED':
12 | return R.merge(state, {inProgress: false})
13 | default:
14 | return state
15 | }
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import fetchReducer from './FetchReducer'
6 |
7 | const reducer = combineReducers({
8 | filter: commentFilterReducer,
9 | form: commentFormReducer,
10 | fetching: fetchReducer
11 | })
12 |
13 | export default reducer;
14 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_5/async-design-basics/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/README.md:
--------------------------------------------------------------------------------
1 | # Redux comment list example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm test
7 | ```
8 |
9 | open http://localhost:3000
10 |
11 | ## Explanation
12 |
13 | We will split up the reducers and use combineReducers to combine them back together.
14 | This allows separation of concerns between filtering and inserting comments.
15 | The state object also is cleaner, values like 'author' are namespaced to either the filter or the form.
16 |
17 | ## Additional resources
18 | [Redux official guides](redux.js.org)
19 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/actions/index.js:
--------------------------------------------------------------------------------
1 | // Action creators can have side-effects
2 | let nextId = 1;
3 |
4 | export const addComment = (author, text) => {
5 | return {
6 | type: "ADD_COMMENT",
7 | payload: { author: author, text: text, id: nextId++}
8 | }
9 | }
10 |
11 | export const setAuthor = (author) => {
12 | return {
13 | type: "SET_AUTHOR",
14 | payload: author
15 | }
16 | }
17 |
18 | export const setText = (text) => {
19 | return {
20 | type: "SET_TEXT",
21 | payload: text
22 | }
23 | }
24 |
25 | export const setAuthorFilter = (author) => {
26 | return {
27 | type: "SET_AUTHOR_FILTER",
28 | payload: author
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 |
7 | const CommentApp = () => {
8 | return (
9 |
10 |
Comments
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default CommentApp;
19 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 |
3 | import CommentList from '../components/CommentList'
4 |
5 | const mapStateToProps = (state) => {
6 | let filteredComments;
7 | if (!state.filter.author) {
8 | filteredComments = state.form.comments;
9 | } else {
10 | filteredComments = state.form.comments.filter(
11 | (comment) => comment.author === state.filter.author
12 | )
13 | }
14 |
15 | return {
16 | comments: filteredComments
17 | }
18 | }
19 |
20 | export default connect(mapStateToProps)(CommentList)
21 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from 'react-redux'
4 | import { createStore } from 'redux'
5 | import commentsApp from './reducers'
6 | import CommentApp from "./components/CommentApp";
7 |
8 | let store = createStore(commentsApp)
9 |
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('content')
15 | );
16 |
17 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | author: ''
3 | }
4 |
5 | const merge = (obj1, obj2) => Object.assign({}, obj1, obj2)
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 |
6 | const reducer = combineReducers({
7 | filter: commentFilterReducer,
8 | form: commentFormReducer
9 | })
10 |
11 | export default reducer;
12 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_5/combine_reducers_example/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/README.md:
--------------------------------------------------------------------------------
1 | # Redux comment list example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm test
7 | ```
8 |
9 | open http://localhost:3000
10 |
11 | ## Explanation
12 |
13 | We have added a select element to filter by author to the comment application.
14 |
15 | First we thought what the state would look like.
16 | In this case just the `filteredByAuthor` value was added to state.
17 |
18 | It works fine, but all the reducers are in one function and the CommentBoxContainer is getting larger and larger.
19 | The next example will work on refactoring.
20 |
21 | ## Additional resources
22 | [Redux official guides](redux.js.org)
23 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/actions/index.js:
--------------------------------------------------------------------------------
1 | // Action creators can have side-effects
2 | let nextId = 1;
3 |
4 | export const addComment = (author, text) => {
5 | return {
6 | type: "ADD_COMMENT",
7 | payload: { author: author, text: text, id: nextId++}
8 | }
9 | }
10 |
11 | export const setAuthor = (author) => {
12 | return {
13 | type: "SET_AUTHOR",
14 | payload: author
15 | }
16 | }
17 |
18 | export const setText = (text) => {
19 | return {
20 | type: "SET_TEXT",
21 | payload: text
22 | }
23 | }
24 |
25 | export const setAuthorFilter = (author) => {
26 | return {
27 | type: "SET_AUTHOR_FILTER",
28 | payload: author
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from 'react-redux'
4 | import { createStore } from 'redux'
5 | import commentsApp from './reducers'
6 | import CommentAppContainer from "./containers/CommentAppContainer";
7 |
8 | let store = createStore(commentsApp)
9 |
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('content')
15 | );
16 |
17 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_5/comment_list_with_filter/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/README.md:
--------------------------------------------------------------------------------
1 | # Redux comment list example
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm test
7 | ```
8 |
9 | open http://localhost:3000
10 |
11 | ## Explanation
12 |
13 | In this example we are using Ramda to perform simple tasks in a functional way.
14 |
15 | We are using
16 |
17 | * `R.filter`
18 | * `R.map`
19 | * `R.uniq`
20 | * `R.prop`
21 | * `R.propEq`
22 | * `R.identity`
23 | * `R.compose`
24 |
25 | All those functions are described in ramda [documentation](http://ramdajs.com/0.21.0/docs/)
26 |
27 |
28 | ## Additional resources
29 | [Redux official guides](redux.js.org)
30 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/actions/index.js:
--------------------------------------------------------------------------------
1 | // Action creators can have side-effects
2 | let nextId = 1;
3 |
4 | export const addComment = (author, text) => {
5 | return {
6 | type: "ADD_COMMENT",
7 | payload: { author: author, text: text, id: nextId++}
8 | }
9 | }
10 |
11 | export const setAuthor = (author) => {
12 | return {
13 | type: "SET_AUTHOR",
14 | payload: author
15 | }
16 | }
17 |
18 | export const setText = (text) => {
19 | return {
20 | type: "SET_TEXT",
21 | payload: text
22 | }
23 | }
24 |
25 | export const setAuthorFilter = (author) => {
26 | return {
27 | type: "SET_AUTHOR_FILTER",
28 | payload: author
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 |
7 | const CommentApp = () => {
8 | return (
9 |
10 |
Comments
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default CommentApp;
19 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from 'react-redux'
4 | import { createStore } from 'redux'
5 | import commentsApp from './reducers'
6 | import CommentApp from "./components/CommentApp";
7 |
8 | let store = createStore(commentsApp)
9 |
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('content')
15 | );
16 |
17 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 |
6 | const reducer = combineReducers({
7 | filter: commentFilterReducer,
8 | form: commentFormReducer
9 | })
10 |
11 | export default reducer;
12 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_5/ramda-example/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 | import Fetcher from "../containers/Fetcher";
7 |
8 | const CommentApp = () => {
9 | return (
10 |
11 |
Comments
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default CommentApp;
21 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/components/Fetcher.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Fetcher = ({onFetch}) => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 | Fetcher.propTypes = {
12 | onFetch: React.PropTypes.func.isRequired
13 | };
14 | export { Fetcher as default };
15 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/containers/Fetcher.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import Fetcher from "../components/Fetcher";
4 | import { connect } from 'react-redux'
5 | import { fetchPost } from '../actions'
6 |
7 | const mapDispatchToProps = (dispatch) => {
8 | return {
9 | onFetch: () => dispatch(fetchPost())
10 | }
11 | };
12 |
13 | export default connect(undefined, mapDispatchToProps)(Fetcher);
14 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from 'react-redux'
4 | import { createStore, applyMiddleware } from 'redux'
5 | import thunkMiddleware from 'redux-thunk'
6 | import commentsApp from './reducers'
7 | import CommentApp from "./components/CommentApp";
8 |
9 | let store = createStore(commentsApp, applyMiddleware(thunkMiddleware))
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | document.getElementById('content')
16 | );
17 |
18 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 |
6 | const reducer = combineReducers({
7 | filter: commentFilterReducer,
8 | form: commentFormReducer
9 | })
10 |
11 | export default reducer;
12 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_5/redux-thunk-basics/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/Comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"author": "Scooby", "text": "Doo" },
3 | {"author": "Emma", "text": "No!" },
4 | {"author": "Scooby", "text": "Yes!" }
5 | ]
6 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 | import Fetcher from "../containers/Fetcher";
7 |
8 | const CommentApp = () => {
9 | return (
10 |
11 |
Comments
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default CommentApp;
21 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from 'react-redux'
4 | import { createStore, applyMiddleware } from 'redux'
5 | import thunkMiddleware from 'redux-thunk'
6 | import commentsApp from './reducers'
7 | import CommentApp from "./components/CommentApp";
8 |
9 | let store = createStore(commentsApp, applyMiddleware(thunkMiddleware))
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | document.getElementById('content')
16 | );
17 |
18 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/reducers/FetchReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | inProgress: false,
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'COMMENT_FETCH_REQUESTED':
10 | return R.merge(state, {inProgress: true})
11 | case 'COMMENT_FETCH_STOPPED':
12 | return R.merge(state, {inProgress: false})
13 | default:
14 | return state
15 | }
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import fetchReducer from './FetchReducer'
6 |
7 | const reducer = combineReducers({
8 | filter: commentFilterReducer,
9 | form: commentFormReducer,
10 | fetching: fetchReducer
11 | })
12 |
13 | export default reducer;
14 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_5/server-request-basics/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/Comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"author": "Scooby", "text": "Doo" },
3 | {"author": "Emma", "text": "No!" },
4 | {"author": "Scooby", "text": "Yes!" }
5 | ]
6 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 | import RemoteComments from "../containers/RemoteComments";
7 |
8 | const CommentApp = () => {
9 | return (
10 |
11 |
Comments
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default CommentApp;
21 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/reducers/RemoteReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | comments: []
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'RECEIVED_REMOTE_COMMENTS':
10 | return R.merge(state, {comments: action.payload.comments})
11 | default:
12 | return state
13 | }
14 | };
15 |
16 | export default reducer;
17 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/reducers/WebsocketReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | case 'WEBSOCKET_CONNECTION_UNAVAILABLE':
14 | return R.merge(state, {connected: false})
15 | default:
16 | return state
17 | }
18 | };
19 |
20 | export default reducer;
21 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import websocketReducer from './WebsocketReducer'
6 | import remoteReducer from './RemoteReducer'
7 |
8 | const reducer = combineReducers({
9 | filter: commentFilterReducer,
10 | form: commentFormReducer,
11 | websocket: websocketReducer,
12 | remote: remoteReducer
13 | })
14 |
15 | export default reducer;
16 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/server.js:
--------------------------------------------------------------------------------
1 | const WebSocketServer = require('./server/WebSocketServer')
2 | const WebpackDevServer = require('./server/WebpackDevServer')
3 |
4 | WebpackDevServer.start()
5 | WebSocketServer.start()
6 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/server/WebpackDevServer.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('../webpack.config');
4 |
5 | var port = 3000;
6 | var host = 'localhost';
7 |
8 | const server = new WebpackDevServer(webpack(config), {
9 | publicPath: config.output.publicPath,
10 | historyApiFallback: true
11 | })
12 |
13 | module.exports.start = () => {
14 | server.listen(port, host, (err, result) => {
15 | if (err) {
16 | console.log(err);
17 | } else {
18 | console.log(`Listening at ${host}:${port}`);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_6/comment-mirror/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/Comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"author": "Scooby", "text": "Doo" },
3 | {"author": "Emma", "text": "No!" },
4 | {"author": "Scooby", "text": "Yes!" }
5 | ]
6 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 | import Fetcher from "../containers/Fetcher";
7 |
8 | const CommentApp = () => {
9 | return (
10 |
11 |
Comments
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default CommentApp;
21 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/middleware/ConstantActionLogger.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | let constantActionLogger = (logger) => (store) => (next) => (action) => {
4 | const stateBefore = store.getState()
5 | const result = next(action);
6 | const stateAfter = store.getState()
7 | if (R.equals(stateBefore, stateAfter)) {
8 | logger.warn("An action was dispatched that did not change state", action);
9 | }
10 | }
11 |
12 | export default constantActionLogger
13 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/middleware/ExampleMiddleware.js:
--------------------------------------------------------------------------------
1 | // Middleware is a curried function
2 |
3 | let myMiddleware = (id) => (store) => (next) => (action) => {
4 | console.log(store)
5 | console.trace("My middleware before calling next", id, action);
6 | const result = next(action);
7 | console.trace("My middleware after calling next", id, action);
8 | return result;
9 | }
10 |
11 | export default myMiddleware
12 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/reducers/FetchReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | inProgress: false,
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'COMMENT_FETCH_REQUESTED':
10 | return R.merge(state, {inProgress: true})
11 | case 'COMMENT_FETCH_STOPPED':
12 | return R.merge(state, {inProgress: false})
13 | default:
14 | return state
15 | }
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import fetchReducer from './FetchReducer'
6 |
7 | const reducer = combineReducers({
8 | filter: commentFilterReducer,
9 | form: commentFormReducer,
10 | fetching: fetchReducer
11 | })
12 |
13 | export default reducer;
14 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_6/middleware-basics/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './main.js',
3 | output: {
4 | filename: 'bundle.js'
5 | },
6 | module: {
7 | loaders: [
8 | {
9 | loader: 'babel',
10 | query: {
11 | presets: ['react', 'es2015'],
12 | },
13 | }
14 | ]
15 | },
16 | devServer: {
17 | port: 3000
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/Comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"author": "Scooby", "text": "Doo" },
3 | {"author": "Emma", "text": "No!" },
4 | {"author": "Scooby", "text": "Yes!" }
5 | ]
6 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 | import Fetcher from "../containers/Fetcher";
7 |
8 | const CommentApp = () => {
9 | return (
10 |
11 |
Comments
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default CommentApp;
21 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/reducers/FetchReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | inProgress: false,
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'COMMENT_FETCH_REQUESTED':
10 | return R.merge(state, {inProgress: true})
11 | case 'COMMENT_FETCH_STOPPED':
12 | return R.merge(state, {inProgress: false})
13 | default:
14 | return state
15 | }
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/reducers/WebsocketReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | case 'WEBSOCKET_CONNECTION_UNAVAILABLE':
14 | return R.merge(state, {connected: false})
15 | default:
16 | return state
17 | }
18 | };
19 |
20 | export default reducer;
21 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import fetchReducer from './FetchReducer'
6 | import websocketReducer from './WebsocketReducer'
7 |
8 | const reducer = combineReducers({
9 | filter: commentFilterReducer,
10 | form: commentFormReducer,
11 | fetching: fetchReducer,
12 | websocket: websocketReducer
13 | })
14 |
15 | export default reducer;
16 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_6/websocket-basics/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/Comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"author": "Scooby", "text": "Doo" },
3 | {"author": "Emma", "text": "No!" },
4 | {"author": "Scooby", "text": "Yes!" }
5 | ]
6 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/actions/GlobalKeys.js:
--------------------------------------------------------------------------------
1 | export const keyPressed = (key) => {
2 | return {
3 | type: 'GLOBAL_KEY_PRESSED',
4 | payload: {key: key}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/components/CommentListWithFilter.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import FilteredCommentList from "../containers/FilteredCommentList";
3 | import CommentFilter from "../containers/CommentFilter";
4 |
5 | export default (_) => {
6 | return (
7 |
8 |
Local comments
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/components/GlobalKeys.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import KeyPressListener from './KeyPressListener'
3 |
4 | const GlobalKeys = (props) => {
5 | return (
6 |
10 | )
11 | }
12 |
13 | export default GlobalKeys;
14 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/containers/GlobalKeys.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import GlobalKeys from '../components/GlobalKeys'
3 | import { keyPressed } from '../actions/GlobalKeys'
4 |
5 | const mapStateToProps = (state) => {
6 | return {
7 | keys: state.global.input
8 | }
9 | }
10 |
11 | const mapDispatchToProps = (dispatch) => {
12 | return {
13 | handleKeyPress: (key) => dispatch(keyPressed(key))
14 | }
15 | }
16 |
17 | export default connect(mapStateToProps, mapDispatchToProps)(GlobalKeys)
18 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/reducers/GlobalKey.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | input: ""
5 | }
6 |
7 | const createReducer = (initialState, actionEvolveMap) => {
8 | return (state=initialState, action) => {
9 | const evolver = actionEvolveMap[action.type];
10 | if (evolver) {
11 | return R.evolve(evolver(action))(state)
12 | } else {
13 | return state;
14 | }
15 | }
16 | }
17 |
18 | const reducer = createReducer(initialState, {
19 | 'GLOBAL_KEY_PRESSED': (action) => {
20 | return {
21 | input: R.append(action.payload.key)
22 | }
23 | }
24 | });
25 |
26 | export default reducer;
27 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/reducers/RemoteReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | comments: []
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'RECEIVED_REMOTE_COMMENTS':
10 | return R.merge(state, {comments: action.payload.comments})
11 | default:
12 | return state
13 | }
14 | };
15 |
16 | export default reducer;
17 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/reducers/WebsocketReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | case 'WEBSOCKET_CONNECTION_UNAVAILABLE':
14 | return R.merge(state, {connected: false})
15 | default:
16 | return state
17 | }
18 | };
19 |
20 | export default reducer;
21 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import websocketReducer from './WebsocketReducer'
6 | import remoteReducer from './RemoteReducer'
7 | import { routerReducer } from 'react-router-redux'
8 | import globalReducer from './GlobalKey'
9 |
10 | const reducer = combineReducers({
11 | routing: routerReducer,
12 | filter: commentFilterReducer,
13 | form: commentFormReducer,
14 | websocket: websocketReducer,
15 | remote: remoteReducer,
16 | global: globalReducer
17 | })
18 |
19 | export default reducer;
20 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/server.js:
--------------------------------------------------------------------------------
1 | const WebSocketServer = require('./server/WebSocketServer')
2 | const WebpackDevServer = require('./server/WebpackDevServer')
3 |
4 | WebpackDevServer.start()
5 | WebSocketServer.start()
6 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/server/WebpackDevServer.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('../webpack.config');
4 |
5 | var port = 3000;
6 | var host = 'localhost';
7 |
8 | const server = new WebpackDevServer(webpack(config), {
9 | publicPath: config.output.publicPath,
10 | historyApiFallback: true
11 | })
12 |
13 | module.exports.start = () => {
14 | server.listen(port, host, (err, result) => {
15 | if (err) {
16 | console.log(err);
17 | } else {
18 | console.log(`Listening at ${host}:${port}`);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_7/react-global-events/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/Comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"author": "Scooby", "text": "Doo" },
3 | {"author": "Emma", "text": "No!" },
4 | {"author": "Scooby", "text": "Yes!" }
5 | ]
6 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/components/AppWrapper.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from 'react-router';
3 |
4 | const LinkToComments = (_) => View comments
5 | const LinkToAddComment = (_) => Add comment
6 | const LinkToRemoteComments = (_) => Remote comments
7 |
8 | export default (props) => {
9 | return(
10 |
11 |
Comments App
12 |
13 |
14 |
15 | {props.children}
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/components/CommentListWithFilter.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import FilteredCommentList from "../containers/FilteredCommentList";
3 | import CommentFilter from "../containers/CommentFilter";
4 |
5 | export default (_) => {
6 | return (
7 |
8 |
Local comments
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/reducers/RemoteReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | comments: []
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'RECEIVED_REMOTE_COMMENTS':
10 | return R.merge(state, {comments: action.payload.comments})
11 | default:
12 | return state
13 | }
14 | };
15 |
16 | export default reducer;
17 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/reducers/WebsocketReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | case 'WEBSOCKET_CONNECTION_UNAVAILABLE':
14 | return R.merge(state, {connected: false})
15 | default:
16 | return state
17 | }
18 | };
19 |
20 | export default reducer;
21 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import websocketReducer from './WebsocketReducer'
6 | import remoteReducer from './RemoteReducer'
7 | import { routerReducer } from 'react-router-redux'
8 |
9 | const reducer = combineReducers({
10 | routing: routerReducer,
11 | filter: commentFilterReducer,
12 | form: commentFormReducer,
13 | websocket: websocketReducer,
14 | remote: remoteReducer
15 | })
16 |
17 | export default reducer;
18 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/server.js:
--------------------------------------------------------------------------------
1 | const WebSocketServer = require('./server/WebSocketServer')
2 | const WebpackDevServer = require('./server/WebpackDevServer')
3 |
4 | WebpackDevServer.start()
5 | WebSocketServer.start()
6 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/server/WebpackDevServer.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('../webpack.config');
4 |
5 | var port = 3000;
6 | var host = 'localhost';
7 |
8 | const server = new WebpackDevServer(webpack(config), {
9 | publicPath: config.output.publicPath,
10 | historyApiFallback: true
11 | })
12 |
13 | module.exports.start = () => {
14 | server.listen(port, host, (err, result) => {
15 | if (err) {
16 | console.log(err);
17 | } else {
18 | console.log(`Listening at ${host}:${port}`);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_7/react-router-redux/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/Comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"author": "Scooby", "text": "Doo" },
3 | {"author": "Emma", "text": "No!" },
4 | {"author": "Scooby", "text": "Yes!" }
5 | ]
6 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/components/CommentApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import FilteredCommentList from "../containers/FilteredCommentList";
4 | import CommentForm from "../containers/CommentForm";
5 | import CommentFilter from "../containers/CommentFilter";
6 | import RemoteComments from "../containers/RemoteComments";
7 |
8 | const CommentApp = () => {
9 | return (
10 |
11 |
Comments
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default CommentApp;
21 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/reducers/RemoteReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | comments: []
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'RECEIVED_REMOTE_COMMENTS':
10 | return R.merge(state, {comments: action.payload.comments})
11 | default:
12 | return state
13 | }
14 | };
15 |
16 | export default reducer;
17 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/reducers/WebsocketReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | case 'WEBSOCKET_CONNECTION_UNAVAILABLE':
14 | return R.merge(state, {connected: false})
15 | default:
16 | return state
17 | }
18 | };
19 |
20 | export default reducer;
21 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import websocketReducer from './WebsocketReducer'
6 | import remoteReducer from './RemoteReducer'
7 |
8 | const reducer = combineReducers({
9 | filter: commentFilterReducer,
10 | form: commentFormReducer,
11 | websocket: websocketReducer,
12 | remote: remoteReducer
13 | })
14 |
15 | export default reducer;
16 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/server.js:
--------------------------------------------------------------------------------
1 | const WebSocketServer = require('./server/WebSocketServer')
2 | const WebpackDevServer = require('./server/WebpackDevServer')
3 |
4 | WebpackDevServer.start()
5 | WebSocketServer.start()
6 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/server/WebpackDevServer.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('../webpack.config');
4 |
5 | var port = 3000;
6 | var host = 'localhost';
7 |
8 | const server = new WebpackDevServer(webpack(config), {
9 | publicPath: config.output.publicPath,
10 | historyApiFallback: true
11 | })
12 |
13 | module.exports.start = () => {
14 | server.listen(port, host, (err, result) => {
15 | if (err) {
16 | console.log(err);
17 | } else {
18 | console.log(`Listening at ${host}:${port}`);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_7/redux-devtools/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | bundle.js
3 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/Comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {"author": "Scooby", "text": "Doo" },
3 | {"author": "Emma", "text": "No!" },
4 | {"author": "Scooby", "text": "Yes!" }
5 | ]
6 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/components/AppWrapper.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from 'react-router';
3 |
4 | const LinkToComments = (_) => View comments
5 | const LinkToAddComment = (_) => Add comment
6 | const LinkToRemoteComments = (_) => Remote comments
7 |
8 | export default (props) => {
9 | return(
10 |
11 |
Comments App
12 |
13 |
14 |
15 | {props.children}
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/components/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | const Comment = (props) => {
5 | return (
6 |
7 |
8 | {props.author}
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 | Comment.propTypes = {
15 | author: React.PropTypes.string.isRequired,
16 | children: React.PropTypes.node.isRequired,
17 | };
18 | export { Comment as default };
19 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/components/CommentListWithFilter.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import FilteredCommentList from "../containers/FilteredCommentList";
3 | import CommentFilter from "../containers/CommentFilter";
4 |
5 | export default (_) => {
6 | return (
7 |
8 |
Local comments
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/containers/FilteredCommentList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CommentList from '../components/CommentList'
3 | import R from 'ramda'
4 |
5 | const mapStateToProps = (state) => {
6 |
7 | const filteredComments = R.ifElse(
8 | R.always(state.filter.author),
9 | R.filter(R.propEq('author', state.filter.author)),
10 | R.identity
11 | )(state.form.comments)
12 |
13 | return {
14 | comments: filteredComments
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps)(CommentList)
19 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | title
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/reducers/CommentFilterReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | author: ''
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'SET_AUTHOR_FILTER':
10 | return R.merge(state, { author: action.payload })
11 | default:
12 | return state
13 | }
14 |
15 |
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/reducers/RemoteReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | comments: []
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'RECEIVED_REMOTE_COMMENTS':
10 | return R.merge(state, {comments: action.payload.comments})
11 | default:
12 | return state
13 | }
14 | };
15 |
16 | export default reducer;
17 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/reducers/WebsocketReducer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | case 'WEBSOCKET_CONNECTION_UNAVAILABLE':
14 | return R.merge(state, {connected: false})
15 | default:
16 | return state
17 | }
18 | };
19 |
20 | export default reducer;
21 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import commentFilterReducer from './CommentFilterReducer'
4 | import commentFormReducer from './CommentFormReducer'
5 | import websocketReducer from './WebsocketReducer'
6 | import remoteReducer from './RemoteReducer'
7 |
8 | const reducer = combineReducers({
9 | filter: commentFilterReducer,
10 | form: commentFormReducer,
11 | websocket: websocketReducer,
12 | remote: remoteReducer
13 | })
14 |
15 | export default reducer;
16 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/server.js:
--------------------------------------------------------------------------------
1 | const WebSocketServer = require('./server/WebSocketServer')
2 | const WebpackDevServer = require('./server/WebpackDevServer')
3 |
4 | WebpackDevServer.start()
5 | WebSocketServer.start()
6 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/server/WebpackDevServer.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('../webpack.config');
4 |
5 | var port = 3000;
6 | var host = 'localhost';
7 |
8 | const server = new WebpackDevServer(webpack(config), {
9 | publicPath: config.output.publicPath,
10 | historyApiFallback: true
11 | })
12 |
13 | module.exports.start = () => {
14 | server.listen(port, host, (err, result) => {
15 | if (err) {
16 | console.log(err);
17 | } else {
18 | console.log(`Listening at ${host}:${port}`);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const renderWithWrapperAndFind = (element, className) => {
13 | let dom = TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 |
19 | return TestUtils.findRenderedDOMComponentWithClass(dom, className)
20 | };
21 |
22 | module.exports = { renderWithWrapperAndFind };
23 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/test/reducers/CommentFilterReducer-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import reduce from '../../reducers/CommentFilterReducer'
4 |
5 | describe('CommentFilterReducer', () => {
6 | describe('SET_AUTHOR_FILTER', () => {
7 | it('sets filteredByAuthor', () => {
8 | let action = {type: 'SET_AUTHOR_FILTER', payload: 'filtered-author'}
9 | const newState = reduce(undefined, action)
10 | expect(newState.author).to.eq('filtered-author')
11 | });
12 | });
13 |
14 | it('has initial filter', () => {
15 | const initialState = reduce(undefined, {})
16 | expect(initialState).to.eql({
17 | author: ''
18 | })
19 | })
20 | });
21 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
--------------------------------------------------------------------------------
/lecture_7/router-basics/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/.babelrc:
--------------------------------------------------------------------------------
1 | { "presets": ["es2015", "react"] }
2 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | npm-debug.log
3 | node_modules
4 | .DS_Store
5 | public/dist/bundle.js
6 | public/dist/bundle.js.map
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/README.md:
--------------------------------------------------------------------------------
1 | # Redux cache miss
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm test
7 | ```
8 |
9 | open http://localhost:3000
10 |
11 | ## Explanation
12 |
13 | Let's memoize the individual calls to NextWord and PastWord.
14 | Even if the collection of PastWords and NextWords stays the same, all but one of the individual elements actually stay the same.
15 |
16 | We need to reverse the index of a NextWord, otherwise it keeps changing for all the elements when an element is prepended to the list.
17 | We could reverse nextwords and append to it to avoid this, but reversing the index is a simpler solution.
18 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Speed typing
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/actions/Remote.js:
--------------------------------------------------------------------------------
1 | export const receivedState = (state) => {
2 | return {
3 | type: 'RECEIVED_REMOTE_STATE',
4 | payload: state
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/components/Letter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Letter = (props) => {
4 | return(
5 | {props.letter}
6 | );
7 | };
8 | Letter.propTypes = {
9 | color: React.PropTypes.string.isRequired,
10 | letter: React.PropTypes.string.isRequired
11 | };
12 |
13 | export { Letter as default };
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/components/StartButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const StartButton = (props) => {
4 | if (props.isStarted) {
5 | return();
6 | }else{
7 | return();
8 | }
9 | };
10 |
11 | StartButton.propTypes = {
12 | onStartClick: React.PropTypes.func.isRequired,
13 | onEndClick: React.PropTypes.func.isRequired,
14 | isStarted: React.PropTypes.bool.isRequired
15 | };
16 |
17 | export default StartButton;
18 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/components/Words.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import R from 'ramda'
3 | import PastWords from "../containers/words/PastWordsContainer"
4 | import CurrentWords from "../containers/words/CurrentWordContainer"
5 | import NextWords from "../containers/words/NextWordsContainer"
6 |
7 |
8 | const Words = (props) => {
9 | return(
10 |
17 | );
18 | };
19 |
20 | export { Words as default };
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/containers/RemoteContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Remote from "../components/Remote";
3 | import { connect } from 'react-redux'
4 | import R from 'ramda'
5 |
6 | const mapStateToProps = (state) => {
7 | return {
8 | opponentPresent: !R.isNil(state.remote.currentGame)
9 | }
10 | }
11 |
12 | export default connect(mapStateToProps)(Remote);
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/containers/RemoteWordsContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Words from "../components/Words";
3 | import { connect } from 'react-redux'
4 |
5 | const mapStateToProps = (state) => {
6 | return {
7 | words: state.remote.currentGame.words,
8 | currentInput: state.remote.currentGame.currentInput,
9 | pastInput: state.remote.currentGame.pastInput
10 | }
11 | }
12 |
13 | export default connect(mapStateToProps)(Words);
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/containers/TypingContainer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Typing from '../components/Typing';
3 | import { setCurrentInput } from '../actions';
4 | import { isStarted } from '../reducers';
5 | import { connect } from 'react-redux'
6 |
7 | const mapStateToProps = (state) => {
8 | return {
9 | currentInput: state.currentGame.currentInput,
10 | disabled: !isStarted(state)
11 | }
12 | };
13 |
14 | const mapDispatchToProps = (dispatch) => {
15 | return {
16 | onUserInput: (currentInput) => dispatch(setCurrentInput(currentInput))
17 | }
18 | };
19 |
20 | export default connect(mapStateToProps, mapDispatchToProps)(Typing);
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/containers/words/CurrentWordContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import R from 'ramda'
3 | import CurrentWords from "../../components/words/CurrentWords"
4 |
5 | const mapStateToProps = (state) => {
6 | const words = state.currentGame.words
7 | const pastInput = state.currentGame.pastInput
8 |
9 | const currentRequiredWords = R.compose(
10 | R.take(1),
11 | R.drop(pastInput.length)
12 | )(words)
13 |
14 | return {
15 | currentInput: state.currentGame.currentInput,
16 | currentRequiredWords: currentRequiredWords
17 | }
18 | }
19 |
20 | export default connect(mapStateToProps)(CurrentWords)
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/containers/words/NextWordsContainer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 | import { connect } from 'react-redux'
3 | import { createSelector } from 'reselect'
4 | import NextWords from "../../components/words/NextWords"
5 |
6 | const getPastInput = R.path(['currentGame', 'pastInput'])
7 | const getWords = R.path(['currentGame', 'words'])
8 |
9 | const mapStateToProps = createSelector(
10 | [ getPastInput, getWords ],
11 | (pastInput, words) => {
12 | const nextRequiredWords = R.drop(pastInput.length + 1)(words)
13 |
14 | return { nextRequiredWords }
15 | }
16 | )
17 |
18 | export default connect(mapStateToProps)(NextWords)
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/containers/words/PastWordsContainer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 | import { connect } from 'react-redux'
3 | import { createSelector } from 'reselect'
4 | import PastWords from '../../components/words/PastWords'
5 |
6 | const getPastInput = R.path(['currentGame', 'pastInput'])
7 | const getWords = R.path(['currentGame', 'words'])
8 |
9 | const mapStateToProps = createSelector(
10 | [ getPastInput, getWords ],
11 | (pastInput, words) => {
12 | const pastRequiredWords = words.slice(0, pastInput.length)
13 |
14 | return { pastInput, pastRequiredWords }
15 | }
16 | )
17 |
18 | export default connect(mapStateToProps)(PastWords)
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/reducers/Remote.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda';
2 |
3 | const initialState = {}
4 |
5 | export const remote = (state=initialState, action) => {
6 | switch (action.type) {
7 | case 'RECEIVED_REMOTE_STATE':
8 | return action.payload;
9 | default:
10 | return state;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/reducers/Websocket.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | default:
14 | return state
15 | }
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/js/reducers/pastGames.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda';
2 |
3 | export const pastGames = (state = [], action) => {
4 | switch (action.type) {
5 | case 'END_GAME':
6 | return R.concat(state, {
7 | words: action.payload.words,
8 | pastInput: action.payload.pastInput,
9 | startTime: action.payload.startTime,
10 | endTime: action.payload.endTime
11 | });
12 | default:
13 | return state;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/server.js:
--------------------------------------------------------------------------------
1 | const WebSocketServer = require('./server/WebSocketServer')
2 | const WebpackDevServer = require('./server/WebpackDevServer')
3 |
4 | WebpackDevServer.start()
5 | WebSocketServer.start()
6 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/server/WebpackDevServer.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('../webpack.config');
4 |
5 | var port = 3000;
6 | var host = 'localhost';
7 |
8 | const server = new WebpackDevServer(webpack(config), {
9 | publicPath: config.output.publicPath,
10 | historyApiFallback: true
11 | })
12 |
13 | module.exports.start = () => {
14 | server.listen(port, host, (err, result) => {
15 | if (err) {
16 | console.log(err);
17 | } else {
18 | console.log(`Listening at ${host}:${port}`);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const wrap = (element) => {
13 | return TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 | };
19 |
20 | const wrapAndFindByTag = (element, tagName) => {
21 | const dom = wrap(element)
22 | return TestUtils.findRenderedDOMComponentWithTag(dom, tagName)
23 | }
24 |
25 | module.exports = { wrap, wrapAndFindByTag };
26 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_and_memoize/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './js/main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel-loader',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
27 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/.babelrc:
--------------------------------------------------------------------------------
1 | { "presets": ["es2015", "react"] }
2 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | npm-debug.log
3 | node_modules
4 | .DS_Store
5 | public/dist/bundle.js
6 | public/dist/bundle.js.map
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/README.md:
--------------------------------------------------------------------------------
1 | # Redux cache miss
2 |
3 | To use:
4 | ```
5 | npm install
6 | npm test
7 | ```
8 |
9 | open http://localhost:3000
10 |
11 | ## Explanation
12 |
13 | Let's use _reselect_ to implement memoization for our `mapStateToProps` selector functions.
14 |
15 | `npm install --save reselect`
16 |
17 | Let's use reselect in `PastWordsContainer` and `NextWordsContainer`
18 |
19 | Now we are only re-rendering all the words when moving on to a new word.
20 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Speed typing
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/actions/Remote.js:
--------------------------------------------------------------------------------
1 | export const receivedState = (state) => {
2 | return {
3 | type: 'RECEIVED_REMOTE_STATE',
4 | payload: state
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/components/Letter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Letter = (props) => {
4 | return(
5 | {props.letter}
6 | );
7 | };
8 | Letter.propTypes = {
9 | color: React.PropTypes.string.isRequired,
10 | letter: React.PropTypes.string.isRequired
11 | };
12 |
13 | export { Letter as default };
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/components/Remote.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import RemoteWordsContainer from "../containers/RemoteWordsContainer";
3 | import RemoteStatsContainer from "../containers/RemoteStatsContainer";
4 |
5 | const Remote = (props) => {
6 | if (props.opponentPresent) {
7 | return (
8 |
9 |
Remote Player
10 |
11 |
12 |
13 | );
14 | } else {
15 | return
16 | }
17 | };
18 | Remote.propTypes = {
19 | opponentPresent: React.PropTypes.bool.isRequired,
20 | }
21 |
22 | export { Remote as default }
23 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/components/StartButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const StartButton = (props) => {
4 | if (props.isStarted) {
5 | return();
6 | }else{
7 | return();
8 | }
9 | };
10 |
11 | StartButton.propTypes = {
12 | onStartClick: React.PropTypes.func.isRequired,
13 | onEndClick: React.PropTypes.func.isRequired,
14 | isStarted: React.PropTypes.bool.isRequired
15 | };
16 |
17 | export default StartButton;
18 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/components/Words.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import R from 'ramda'
3 | import PastWords from "../containers/words/PastWordsContainer"
4 | import CurrentWords from "../containers/words/CurrentWordContainer"
5 | import NextWords from "../containers/words/NextWordsContainer"
6 |
7 |
8 | const Words = (props) => {
9 | return(
10 |
17 | );
18 | };
19 |
20 | export { Words as default };
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/containers/RemoteContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Remote from "../components/Remote";
3 | import { connect } from 'react-redux'
4 | import R from 'ramda'
5 |
6 | const mapStateToProps = (state) => {
7 | return {
8 | opponentPresent: !R.isNil(state.remote.currentGame)
9 | }
10 | }
11 |
12 | export default connect(mapStateToProps)(Remote);
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/containers/RemoteWordsContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Words from "../components/Words";
3 | import { connect } from 'react-redux'
4 |
5 | const mapStateToProps = (state) => {
6 | return {
7 | words: state.remote.currentGame.words,
8 | currentInput: state.remote.currentGame.currentInput,
9 | pastInput: state.remote.currentGame.pastInput
10 | }
11 | }
12 |
13 | export default connect(mapStateToProps)(Words);
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/containers/TypingContainer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Typing from '../components/Typing';
3 | import { setCurrentInput } from '../actions';
4 | import { isStarted } from '../reducers';
5 | import { connect } from 'react-redux'
6 |
7 | const mapStateToProps = (state) => {
8 | return {
9 | currentInput: state.currentGame.currentInput,
10 | disabled: !isStarted(state)
11 | }
12 | };
13 |
14 | const mapDispatchToProps = (dispatch) => {
15 | return {
16 | onUserInput: (currentInput) => dispatch(setCurrentInput(currentInput))
17 | }
18 | };
19 |
20 | export default connect(mapStateToProps, mapDispatchToProps)(Typing);
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/containers/words/CurrentWordContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import R from 'ramda'
3 | import CurrentWords from "../../components/words/CurrentWords"
4 |
5 | const mapStateToProps = (state) => {
6 | const words = state.currentGame.words
7 | const pastInput = state.currentGame.pastInput
8 |
9 | const currentRequiredWords = R.compose(
10 | R.take(1),
11 | R.drop(pastInput.length)
12 | )(words)
13 |
14 | return {
15 | currentInput: state.currentGame.currentInput,
16 | currentRequiredWords: currentRequiredWords
17 | }
18 | }
19 |
20 | export default connect(mapStateToProps)(CurrentWords)
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/containers/words/NextWordsContainer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 | import { connect } from 'react-redux'
3 | import { createSelector } from 'reselect'
4 | import NextWords from "../../components/words/NextWords"
5 |
6 | const getPastInput = R.path(['currentGame', 'pastInput'])
7 | const getWords = R.path(['currentGame', 'words'])
8 |
9 | const mapStateToProps = createSelector(
10 | [ getPastInput, getWords ],
11 | (pastInput, words) => {
12 | const nextRequiredWords = R.drop(pastInput.length + 1)(words)
13 |
14 | return { nextRequiredWords }
15 | }
16 | )
17 |
18 | export default connect(mapStateToProps)(NextWords)
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/containers/words/PastWordsContainer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 | import { connect } from 'react-redux'
3 | import { createSelector } from 'reselect'
4 | import PastWords from '../../components/words/PastWords'
5 |
6 | const getPastInput = R.path(['currentGame', 'pastInput'])
7 | const getWords = R.path(['currentGame', 'words'])
8 |
9 | const mapStateToProps = createSelector(
10 | [ getPastInput, getWords ],
11 | (pastInput, words) => {
12 | const pastRequiredWords = words.slice(0, pastInput.length)
13 |
14 | return { pastInput, pastRequiredWords }
15 | }
16 | )
17 |
18 | export default connect(mapStateToProps)(PastWords)
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/reducers/Remote.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda';
2 |
3 | const initialState = {}
4 |
5 | export const remote = (state=initialState, action) => {
6 | switch (action.type) {
7 | case 'RECEIVED_REMOTE_STATE':
8 | return action.payload;
9 | default:
10 | return state;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/reducers/Websocket.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | default:
14 | return state
15 | }
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/js/reducers/pastGames.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda';
2 |
3 | export const pastGames = (state = [], action) => {
4 | switch (action.type) {
5 | case 'END_GAME':
6 | return R.concat(state, {
7 | words: action.payload.words,
8 | pastInput: action.payload.pastInput,
9 | startTime: action.payload.startTime,
10 | endTime: action.payload.endTime
11 | });
12 | default:
13 | return state;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/server.js:
--------------------------------------------------------------------------------
1 | const WebSocketServer = require('./server/WebSocketServer')
2 | const WebpackDevServer = require('./server/WebpackDevServer')
3 |
4 | WebpackDevServer.start()
5 | WebSocketServer.start()
6 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/server/WebpackDevServer.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('../webpack.config');
4 |
5 | var port = 3000;
6 | var host = 'localhost';
7 |
8 | const server = new WebpackDevServer(webpack(config), {
9 | publicPath: config.output.publicPath,
10 | historyApiFallback: true
11 | })
12 |
13 | module.exports.start = () => {
14 | server.listen(port, host, (err, result) => {
15 | if (err) {
16 | console.log(err);
17 | } else {
18 | console.log(`Listening at ${host}:${port}`);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const wrap = (element) => {
13 | return TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 | };
19 |
20 | const wrapAndFindByTag = (element, tagName) => {
21 | const dom = wrap(element)
22 | return TestUtils.findRenderedDOMComponentWithTag(dom, tagName)
23 | }
24 |
25 | module.exports = { wrap, wrapAndFindByTag };
26 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_hit/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './js/main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel-loader',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
27 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/.babelrc:
--------------------------------------------------------------------------------
1 | { "presets": ["es2015", "react"] }
2 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | npm-debug.log
3 | node_modules
4 | .DS_Store
5 | public/dist/bundle.js
6 | public/dist/bundle.js.map
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Speed typing
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/actions/AjaxRequest.js:
--------------------------------------------------------------------------------
1 | export default (url, method, onSuccess, onFailure) => {
2 | var xmlhttp = new XMLHttpRequest();
3 | xmlhttp.onreadystatechange = () => {
4 | if (xmlhttp.readyState == XMLHttpRequest.DONE) {
5 | if (xmlhttp.status >= 200 && xmlhttp.status < 300) {
6 | try {
7 | onSuccess(JSON.parse(xmlhttp.responseText));
8 | } catch (error) {
9 | onFailure(error)
10 | }
11 | } else {
12 | onFailure(xmlhttp.error);
13 | }
14 | }
15 | };
16 | xmlhttp.open(method, url, true);
17 | xmlhttp.send();
18 | };
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/actions/Remote.js:
--------------------------------------------------------------------------------
1 | export const receivedState = (state) => {
2 | return {
3 | type: 'RECEIVED_REMOTE_STATE',
4 | payload: state
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/components/Letter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Letter = (props) => {
4 | return(
5 | {props.letter}
6 | );
7 | };
8 | Letter.propTypes = {
9 | color: React.PropTypes.string.isRequired,
10 | letter: React.PropTypes.string.isRequired
11 | };
12 |
13 | export { Letter as default };
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/components/Remote.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import RemoteWordsContainer from "../containers/RemoteWordsContainer";
3 | import RemoteStatsContainer from "../containers/RemoteStatsContainer";
4 |
5 | const Remote = (props) => {
6 | if (props.opponentPresent) {
7 | return (
8 |
9 |
Remote Player
10 |
11 |
12 |
13 | );
14 | } else {
15 | return
16 | }
17 | };
18 | Remote.propTypes = {
19 | opponentPresent: React.PropTypes.bool.isRequired,
20 | }
21 |
22 | export { Remote as default }
23 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/components/StartButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const StartButton = (props) => {
4 | if (props.isStarted) {
5 | return();
6 | }else{
7 | return();
8 | }
9 | };
10 |
11 | StartButton.propTypes = {
12 | onStartClick: React.PropTypes.func.isRequired,
13 | onEndClick: React.PropTypes.func.isRequired,
14 | isStarted: React.PropTypes.bool.isRequired
15 | };
16 |
17 | export default StartButton;
18 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/components/Words.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import R from 'ramda'
3 | import PastWords from "../containers/words/PastWordsContainer"
4 | import CurrentWords from "../containers/words/CurrentWordContainer"
5 | import NextWords from "../containers/words/NextWordsContainer"
6 |
7 |
8 | const Words = (props) => {
9 | return(
10 |
17 | );
18 | };
19 |
20 | export { Words as default };
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/containers/RemoteContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Remote from "../components/Remote";
3 | import { connect } from 'react-redux'
4 | import R from 'ramda'
5 |
6 | const mapStateToProps = (state) => {
7 | return {
8 | opponentPresent: !R.isNil(state.remote.currentGame)
9 | }
10 | }
11 |
12 | export default connect(mapStateToProps)(Remote);
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/containers/RemoteWordsContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Words from "../components/Words";
3 | import { connect } from 'react-redux'
4 |
5 | const mapStateToProps = (state) => {
6 | return {
7 | words: state.remote.currentGame.words,
8 | currentInput: state.remote.currentGame.currentInput,
9 | pastInput: state.remote.currentGame.pastInput
10 | }
11 | }
12 |
13 | export default connect(mapStateToProps)(Words);
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/containers/TypingContainer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Typing from '../components/Typing';
3 | import { setCurrentInput } from '../actions';
4 | import { isStarted } from '../reducers';
5 | import { connect } from 'react-redux'
6 |
7 | const mapStateToProps = (state) => {
8 | return {
9 | currentInput: state.currentGame.currentInput,
10 | disabled: !isStarted(state)
11 | }
12 | };
13 |
14 | const mapDispatchToProps = (dispatch) => {
15 | return {
16 | onUserInput: (currentInput) => dispatch(setCurrentInput(currentInput))
17 | }
18 | };
19 |
20 | export default connect(mapStateToProps, mapDispatchToProps)(Typing);
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/containers/words/CurrentWordContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import R from 'ramda'
3 | import CurrentWords from "../../components/words/CurrentWords"
4 |
5 | const mapStateToProps = (state) => {
6 | const words = state.currentGame.words
7 | const pastInput = state.currentGame.pastInput
8 |
9 | const currentRequiredWords = R.compose(
10 | R.take(1),
11 | R.drop(pastInput.length)
12 | )(words)
13 |
14 | return {
15 | currentInput: state.currentGame.currentInput,
16 | currentRequiredWords: currentRequiredWords
17 | }
18 | }
19 |
20 | export default connect(mapStateToProps)(CurrentWords)
21 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/containers/words/NextWordsContainer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 | import { connect } from 'react-redux'
3 | import NextWords from "../../components/words/NextWords"
4 |
5 | const mapStateToProps = (state) => {
6 | const pastInput = state.currentGame.pastInput
7 | const words = state.currentGame.words
8 | const nextRequiredWords = R.drop(pastInput.length + 1)(words)
9 |
10 | return { nextRequiredWords }
11 | }
12 |
13 | export default connect(mapStateToProps)(NextWords)
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/containers/words/PastWordsContainer.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 | import { connect } from 'react-redux'
3 | import PastWords from '../../components/words/PastWords'
4 |
5 | const mapStateToProps = (state) => {
6 | const pastInput = state.currentGame.pastInput
7 | const words = state.currentGame.words
8 | const pastRequiredWords = words.slice(0, pastInput.length)
9 |
10 | return { pastInput, pastRequiredWords }
11 | }
12 |
13 | export default connect(mapStateToProps)(PastWords)
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/reducers/Remote.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda';
2 |
3 | const initialState = {}
4 |
5 | export const remote = (state=initialState, action) => {
6 | switch (action.type) {
7 | case 'RECEIVED_REMOTE_STATE':
8 | return action.payload;
9 | default:
10 | return state;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/reducers/Websocket.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 |
3 | const initialState = {
4 | connected: false
5 | }
6 |
7 | const reducer = (state=initialState, action) => {
8 | switch (action.type) {
9 | case 'WEBSOCKET_CONNECTION_ESTABLISHED':
10 | return R.merge(state, {connected: true})
11 | case 'WEBSOCKET_CONNECTION_DROPPED':
12 | return R.merge(state, {connected: false})
13 | default:
14 | return state
15 | }
16 | };
17 |
18 | export default reducer;
19 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/js/reducers/pastGames.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda';
2 |
3 | export const pastGames = (state = [], action) => {
4 | switch (action.type) {
5 | case 'END_GAME':
6 | return R.concat(state, {
7 | words: action.payload.words,
8 | pastInput: action.payload.pastInput,
9 | startTime: action.payload.startTime,
10 | endTime: action.payload.endTime
11 | });
12 | default:
13 | return state;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/server.js:
--------------------------------------------------------------------------------
1 | const WebSocketServer = require('./server/WebSocketServer')
2 | const WebpackDevServer = require('./server/WebpackDevServer')
3 |
4 | WebpackDevServer.start()
5 | WebSocketServer.start()
6 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/server/WebpackDevServer.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('../webpack.config');
4 |
5 | var port = 3000;
6 | var host = 'localhost';
7 |
8 | const server = new WebpackDevServer(webpack(config), {
9 | publicPath: config.output.publicPath,
10 | historyApiFallback: true
11 | })
12 |
13 | module.exports.start = () => {
14 | server.listen(port, host, (err, result) => {
15 | if (err) {
16 | console.log(err);
17 | } else {
18 | console.log(`Listening at ${host}:${port}`);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/test/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TestUtils from 'react-addons-test-utils';
3 |
4 | const Wrapper = React.createClass({
5 | render: function() {
6 | return (
7 | {this.props.children}
8 | );
9 | }
10 | });
11 |
12 | const wrap = (element) => {
13 | return TestUtils.renderIntoDocument(
14 |
15 | {element}
16 |
17 | );
18 | };
19 |
20 | const wrapAndFindByTag = (element, tagName) => {
21 | const dom = wrap(element)
22 | return TestUtils.findRenderedDOMComponentWithTag(dom, tagName)
23 | }
24 |
25 | module.exports = { wrap, wrapAndFindByTag };
26 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require test/test_helper
2 | --compilers js:babel-core/register
3 | --recursive
4 | --reporter spec
5 | --ui bdd
6 |
7 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/test/test_helper.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | global.document = jsdom.jsdom('');
4 | global.window = document.defaultView;
5 | global.navigator = {userAgent: 'node.js'};
6 |
7 | global.chai = require('chai');
8 | global.expect = chai.expect;
9 | global.sinon = require('sinon');
10 |
11 | const sinonChai = require('sinon-chai');
12 | chai.use(sinonChai);
13 |
14 |
--------------------------------------------------------------------------------
/lecture_8/redux_cache_miss/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var port = process.env.port || 3000;
4 | var host = 'localhost';
5 |
6 | module.exports = {
7 | entry: [
8 | `webpack-dev-server/client?http://${host}:${port}`,
9 | './js/main.js',
10 | ],
11 | output: {
12 | filename: 'bundle.js',
13 | path: path.join(__dirname, '/'),
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | loader: 'babel-loader',
19 | query: {
20 | presets: ['react', 'es2015'],
21 | },
22 | }
23 | ]
24 | }
25 | };
26 |
27 |
--------------------------------------------------------------------------------