├── 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 |
7 |
{props.keys}
8 | 9 |
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 |
11 |
12 | 13 | 14 | 15 |
16 |
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 |
11 |
12 | 13 | 14 | 15 |
16 |
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 |
11 |
12 | 13 | 14 | 15 |
16 |
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 | --------------------------------------------------------------------------------