From 76c9cc6d613b82983e908cc24646484fa201d874 Mon Sep 17 00:00:00 2001 From: alexander-lee Date: Tue, 26 Sep 2017 10:59:28 -0700 Subject: [PATCH 1/5] ready for onboarding --- client/post/actions.js | 23 +----- client/post/components/Post/index.js | 39 --------- client/post/reducer.js | 35 +------- client/post/scenes/PostInformation/index.js | 91 --------------------- client/post/scenes/PostList/index.js | 85 ------------------- 5 files changed, 5 insertions(+), 268 deletions(-) diff --git a/client/post/actions.js b/client/post/actions.js index 1966090..01e8fbc 100644 --- a/client/post/actions.js +++ b/client/post/actions.js @@ -8,33 +8,12 @@ export const votePostByIdSucceeded = createAction('VOTE_POST_BY_ID_SUCCEEDED'); // Action Creators export const readPostById = (postId) => { - return async (dispatch) => { - const post = await fetcher.get(`/api/posts/${postId}`); - dispatch(readPostByIdSucceeded({ post })); - }; }; export const readPosts = () => { - return async (dispatch) => { - const body = await fetcher.get('/api/posts'); - const posts = body.results; - dispatch(readPostsSucceeded({ posts })); - }; }; export const votePostById = (postId, isUpvote) => { - return async (dispatch) => { - let body; - - if (isUpvote) { - body = await fetcher.post(`/api/posts/${postId}/vote`); - } - else { - body = await fetcher.delete(`/api/posts/${postId}/vote`); - } - - dispatch(votePostByIdSucceeded({ post: body })); - }; -}; +} diff --git a/client/post/components/Post/index.js b/client/post/components/Post/index.js index c65875f..005a8de 100644 --- a/client/post/components/Post/index.js +++ b/client/post/components/Post/index.js @@ -1,41 +1,2 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router'; -import s from './styles.scss'; - -const propTypes = { - id: PropTypes.number, - title: PropTypes.string, - user: PropTypes.string, - votes: PropTypes.number, - upvotePost: PropTypes.func, - downvotePost: PropTypes.func, -}; - -function Post({ id, title, user, votes, upvotePost, downvotePost }) { - return ( -
-
- -
{votes}
- -
- -

{title}

- by {user} - -
- ); -} - -/* - Since we're making a functional stateless component, we can't actually write: - static propTypes = { - ... - }; - - We have to manually set it outside the function. -*/ - -Post.propTypes = propTypes; -export default Post; diff --git a/client/post/reducer.js b/client/post/reducer.js index 351db58..1a581b3 100644 --- a/client/post/reducer.js +++ b/client/post/reducer.js @@ -1,5 +1,3 @@ -import { handleActions, combineActions } from 'redux-actions'; -import _ from 'lodash'; import { readPostByIdSucceeded, readPostsSucceeded, votePostByIdSucceeded } from './actions'; const defaultState = { @@ -24,33 +22,8 @@ const defaultState = { } */ -export default handleActions({ - [combineActions(readPostByIdSucceeded, votePostByIdSucceeded)]: (state, { payload }) => { - const post = payload.post; - return { - ...state, - byId: { - ...state.byId, - [post.id]: post, - }, - allIds: _.union(state.allIds, [post.id]), - }; - }, +function (state = defaultState, action) { + switch (action.type) { - [readPostsSucceeded]: (state, { payload }) => { - const posts = payload.posts; - const byId = {}; - const allIds = []; - - posts.forEach((post) => { - byId[post.id] = post; - allIds.push(post.id); - }); - - return { - ...state, - byId, - allIds, - }; - }, -}, defaultState); + } +} diff --git a/client/post/scenes/PostInformation/index.js b/client/post/scenes/PostInformation/index.js index 89fdff7..36bc15b 100644 --- a/client/post/scenes/PostInformation/index.js +++ b/client/post/scenes/PostInformation/index.js @@ -1,93 +1,2 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import _ from 'lodash'; - -import { readPostById } from '../../actions'; - -export class PostInformation extends Component { - static propTypes = { - params: PropTypes.shape({ - id: PropTypes.string, - }), - post: PropTypes.shape({ - id: PropTypes.number, - title: PropTypes.string, - user: PropTypes.shape({ - username: PropTypes.string, - }), - votes: PropTypes.number, - }), - readPostById: PropTypes.func, - } - - /* - Load the data before the actual component is rendered. - */ - componentDidMount() { - this.props.readPostById(this.props.params.id); - } - - render() { - const { post = {} } = this.props; - /* - This is shorthand for saying: const post = this.props.post || {}; - By wrapping the 'post' variable with curly braces, we destructuring the - 'this.props' object and extracting the 'post' attribute out of it. - NOTE: This only works if the name of your local variable is the same as the - key within the object. - */ - - return ( -
-

{post.title}

-
{post.user && post.user.username}
-
- ); - } -} - -/* - mapStateToProps is a mandatory parameter is in react-redux's connect() function. - It takes two parameters, your current Redux State, and your ownProps is the current - state of your component's props (ownProps === this.props) - This function is the core of how Redux actually integrates with your React components. - Since your Redux Store is supposed to act as an "overall data store", you use the - mapStateToProps function to cherry-pick out which slices of the state you want to put - in your component. - - Here, this.props.post will be populated with 'posts[ownProps.params.id]'. -*/ -function mapStateToProps(state, ownProps) { - const posts = _.get(state, 'post.byId'); - /* - Here, we're using Lodash's get function to retrieve state.post.byId. - We use this to guard against undefined. Since we're reaching two levels deep in the - object, we want to be sure that state.post is defined before we try to reach state.post.byId. - If post === undefined or null, then an error is thrown. - */ - return { - post: posts[ownProps.params.id], - }; -} - -/* - mapDispatchToProps is an OPTIONAL secondary parameter passed into connect(). It essentially injects - functions into 'this.props' that make dispatching Redux actions much easier. In this case, if we call - this.props.readPostById(postId), we will actually dispatch the 'readPostById' action with the parameter - postId. This is convenient because otherwise, we would have to manually write: - this.props.dispatch(readPostById(postId)) - versus: - this.props.readPostById(postId). - Although this may not seem like much of an improvement, it is important if we want to modify some of the parameters - being passed into our function before we dispatch our action. Think of it as a layer of abstraction. -*/ -function mapDispatchToProps(dispatch) { - return { - readPostById: (postId) => dispatch(readPostById(postId)), - }; -} -/* - We're not exporting PostInformation, but rather a "connected" version of the component. -*/ -export default connect(mapStateToProps, mapDispatchToProps)(PostInformation); diff --git a/client/post/scenes/PostList/index.js b/client/post/scenes/PostList/index.js index 1abb4ff..36bc15b 100644 --- a/client/post/scenes/PostList/index.js +++ b/client/post/scenes/PostList/index.js @@ -1,87 +1,2 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; - -import Post from '../../components/Post'; -import { readPosts, votePostById } from '../../actions'; - -export class PostList extends Component { - static propTypes = { - posts: PropTypes.shape({ - byId: PropTypes.objectOf(PropTypes.shape({ - id: PropTypes.number, - title: PropTypes.string, - user: PropTypes.shape({ - username: PropTypes.string, - }), - votes: PropTypes.number, - })), - allIds: PropTypes.arrayOf(PropTypes.number), - }), - readPosts: PropTypes.func, - votePostById: PropTypes.func, - } - - /* - Always make calls to retrieve initial data on this lifecycle hook. - We want to make sure that the component exists before we even attempt to populate - it with data - */ - componentDidMount() { - this.props.readPosts(); - } - - votePost = (id, isUpvote) => { - this.props.votePostById(id, isUpvote); - } - - render() { - /* - Object destructuring. This is equivalent to: const posts = this.props.posts; - */ - const { posts } = this.props; - const postList = posts.allIds.map((id) => posts.byId[id]); - - return ( -
-

Posts

- { postList.map((p) => ( - this.votePost(p.id, true)} - downvotePost={() => this.votePost(p.id, false)} - /> - )) } -
- ); - /* - In this render function, we're iterating over the entire array 'postList' and creating a - 'Post' component for each of them. Notice that the upvotePost and downvotePost are also being passed in - an anonymous arrow function. We have to do this because we need to bind each function to the context of its own - 'Post' component. - - For example, if we had Post1 and Post2, we need the upvote and downvote functions to only apply to their specific posts. - Any function of the signature () => ... automatically binds 'this' to its parent context. - */ - } -} - -/* Notice how we don't pass the second parameter ownProps here, because it's not necessary. */ -function mapStateToProps(state) { - return { - posts: state.post, - }; -} - -function mapDispatchToProps(dispatch) { - return { - readPosts: () => dispatch(readPosts()), - votePostById: (postId, isUpvote) => dispatch(votePostById(postId, isUpvote)), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(PostList); From 4758e05c60bea6229f0d99402a89f86825ffa58f Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Wed, 27 Sep 2017 15:41:35 -0700 Subject: [PATCH 2/5] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4944ab6..ff75343 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ All of our frontend files are located in the `/client` folder. This folder also We will be using **CSS modules** for our styling. All **GLOBAL** styles should be kept under the `/styles` folder. Our app is separated into "domains" with the following folder structure: - - Dumb Components live in `/components` - - Smart Containers live in `/scenes` - - Reducers & Actions live in `/` + +- Dumb Components live in `/components` +- Smart Containers live in `/scenes` +- Reducers & Actions live in `/` From beee9d9fc36a15a44fbc639506db8d59ddc74c11 Mon Sep 17 00:00:00 2001 From: alexander-lee Date: Wed, 27 Sep 2017 20:58:27 -0700 Subject: [PATCH 3/5] initial post --- client/post/components/Post/index.js | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/client/post/components/Post/index.js b/client/post/components/Post/index.js index 005a8de..61d8f23 100644 --- a/client/post/components/Post/index.js +++ b/client/post/components/Post/index.js @@ -1,2 +1,32 @@ import React from 'react'; import PropTypes from 'prop-types'; + +class Post extends React.Component { + // PropTypes + static propTypes = { + title: PropTypes.string, + }; + // State + state = { + votes: 0, + test: 1, + }; + + onUpvote() { + this.setState({ + votes: this.state.votes + 1, + }); + } + // Render + render() { + return ( +
+ +
{this.state.votes}
+

{this.props.title}

+
+ ); + } +} + +export default Post; From 2c24683203c354cd9fe848780adeedd4f954ef7b Mon Sep 17 00:00:00 2001 From: alexander-lee Date: Wed, 27 Sep 2017 21:13:25 -0700 Subject: [PATCH 4/5] post list and stuff --- client/app/index.js | 3 +- client/post/components/Post/index.js | 9 ++++- client/post/reducer.js | 58 ++++++++++++++-------------- client/post/scenes/PostList/index.js | 14 +++++++ 4 files changed, 51 insertions(+), 33 deletions(-) diff --git a/client/app/index.js b/client/app/index.js index f2de3ec..ae9bc9b 100644 --- a/client/app/index.js +++ b/client/app/index.js @@ -4,7 +4,7 @@ import { Router, Route, IndexRoute } from 'react-router'; import { Provider } from 'react-redux'; import PostList from 'post/scenes/PostList'; -import PostInformation from 'post/scenes/PostInformation'; +// import PostInformation from 'post/scenes/PostInformation'; import App from './components/App'; import { store, history } from './createStore'; @@ -26,7 +26,6 @@ ReactDOM.render( - , diff --git a/client/post/components/Post/index.js b/client/post/components/Post/index.js index 61d8f23..a5fbbb4 100644 --- a/client/post/components/Post/index.js +++ b/client/post/components/Post/index.js @@ -9,7 +9,6 @@ class Post extends React.Component { // State state = { votes: 0, - test: 1, }; onUpvote() { @@ -17,11 +16,17 @@ class Post extends React.Component { votes: this.state.votes + 1, }); } + onDownvote = () => { + this.setState({ + votes: this.state.votes - 1, + }); + } // Render render() { return (
- + +
{this.state.votes}

{this.props.title}

diff --git a/client/post/reducer.js b/client/post/reducer.js index 1a581b3..f7cbc9f 100644 --- a/client/post/reducer.js +++ b/client/post/reducer.js @@ -1,29 +1,29 @@ -import { readPostByIdSucceeded, readPostsSucceeded, votePostByIdSucceeded } from './actions'; - -const defaultState = { - byId: {}, - allIds: [], -}; - -/* - We use redux-actions to handle our actions. - Normally this would look like: - - function (state = defaultState, action) { - switch (action.type) { - case readPostsByIdSucceeded: - return {...}; - case readPostsSucceeded: - return {...}; - default: - return state; - } - } -} -*/ - -function (state = defaultState, action) { - switch (action.type) { - - } -} +// import { readPostByIdSucceeded, readPostsSucceeded, votePostByIdSucceeded } from './actions'; +// +// const defaultState = { +// byId: {}, +// allIds: [], +// }; +// +// /* +// We use redux-actions to handle our actions. +// Normally this would look like: +// +// function (state = defaultState, action) { +// switch (action.type) { +// case readPostsByIdSucceeded: +// return {...}; +// case readPostsSucceeded: +// return {...}; +// default: +// return state; +// } +// } +// } +// */ +// +// function (state = defaultState, action) { +// switch (action.type) { +// +// } +// } diff --git a/client/post/scenes/PostList/index.js b/client/post/scenes/PostList/index.js index 36bc15b..8de608c 100644 --- a/client/post/scenes/PostList/index.js +++ b/client/post/scenes/PostList/index.js @@ -1,2 +1,16 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import Post from '../../components/Post'; + +class PostList extends Component { + // state + // render + render() { + return ( +
+ +
+ ); + } +} +export default PostList; From 464720a25c47a49f74f241cd1608028890c3ffe5 Mon Sep 17 00:00:00 2001 From: alexander-lee Date: Fri, 13 Oct 2017 20:14:10 -0700 Subject: [PATCH 5/5] postlist --- client/app/index.js | 1 + client/app/rootReducer.js | 5 +- client/post/actions.js | 41 +++++++++++++- client/post/components/Post/index.js | 61 +++++++++++---------- client/post/reducer.js | 59 ++++++++++---------- client/post/scenes/PostInformation/index.js | 53 ++++++++++++++++++ client/post/scenes/PostList/index.js | 60 ++++++++++++++++++-- 7 files changed, 214 insertions(+), 66 deletions(-) diff --git a/client/app/index.js b/client/app/index.js index ae9bc9b..9bb8ffe 100644 --- a/client/app/index.js +++ b/client/app/index.js @@ -26,6 +26,7 @@ ReactDOM.render( + { /* /post/3 */ } , diff --git a/client/app/rootReducer.js b/client/app/rootReducer.js index 92b3b86..274738c 100644 --- a/client/app/rootReducer.js +++ b/client/app/rootReducer.js @@ -3,4 +3,7 @@ import { routerReducer as routing } from 'react-router-redux'; import post from 'post/reducer'; -export default combineReducers({ post, routing }); +export default combineReducers({ + post: post, + routing: routing +}); diff --git a/client/post/actions.js b/client/post/actions.js index 01e8fbc..d239c68 100644 --- a/client/post/actions.js +++ b/client/post/actions.js @@ -2,9 +2,9 @@ import { createAction } from 'redux-actions'; import fetcher from 'utils/fetcher'; // Actions we dispatch in our Action Creators -export const readPostByIdSucceeded = createAction('READ_POST_BY_ID_SUCCEEDED'); -export const readPostsSucceeded = createAction('READ_POSTS_SUCCEEDED'); -export const votePostByIdSucceeded = createAction('VOTE_POST_BY_ID_SUCCEEDED'); +export const readPostByIdSucceeded = 'READ_POST_BY_ID_SUCCEEDED'; +export const readPostsSucceeded = 'READ_POSTS_SUCCEEDED'; +export const votePostByIdSucceeded = 'VOTE_POST_BY_ID_SUCCEEDED'; // Action Creators export const readPostById = (postId) => { @@ -12,8 +12,43 @@ export const readPostById = (postId) => { }; export const readPosts = () => { + return async (dispatch) => { + try { + const response = await fetcher.get('/api/posts'); + dispatch({ + type: readPostsSucceeded, + payload: { + posts: response.results + }, + }); + } + catch (e) { + console.error(e); + } + }; }; export const votePostById = (postId, isUpvote) => { + return async (dispatch) => { + try { + let response; + if (isUpvote) { + response = await fetcher.post(`/api/posts/${postId}/vote`); + } + else { + response = await fetcher.delete(`/api/posts/${postId}/vote`); + } + + dispatch({ + type: votePostByIdSucceeded, + payload: { + post: response, + }, + }); + } + catch (e) { + console.error(e); + } + }; } diff --git a/client/post/components/Post/index.js b/client/post/components/Post/index.js index a5fbbb4..255df81 100644 --- a/client/post/components/Post/index.js +++ b/client/post/components/Post/index.js @@ -1,37 +1,40 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { Link } from 'react-router'; +import s from './styles.scss'; -class Post extends React.Component { - // PropTypes - static propTypes = { - title: PropTypes.string, - }; - // State - state = { - votes: 0, - }; +const propTypes = { + id: PropTypes.number, + title: PropTypes.string, + user: PropTypes.string, + votes: PropTypes.number, + upvotePost: PropTypes.func, + downvotePost: PropTypes.func, +}; - onUpvote() { - this.setState({ - votes: this.state.votes + 1, - }); - } - onDownvote = () => { - this.setState({ - votes: this.state.votes - 1, - }); - } - // Render - render() { - return ( -
- - -
{this.state.votes}
-

{this.props.title}

+function Post({ id, title, user, votes, upvotePost, downvotePost }) { + return ( +
+
+ +
{votes}
+
- ); - } + +

{title}

+ by {user} + +
+ ); } +/* + Since we're making a functional stateless component, we can't actually write: + static propTypes = { + ... + }; + We have to manually set it outside the function. +*/ + +Post.propTypes = propTypes; export default Post; diff --git a/client/post/reducer.js b/client/post/reducer.js index f7cbc9f..d0cf469 100644 --- a/client/post/reducer.js +++ b/client/post/reducer.js @@ -1,29 +1,30 @@ -// import { readPostByIdSucceeded, readPostsSucceeded, votePostByIdSucceeded } from './actions'; -// -// const defaultState = { -// byId: {}, -// allIds: [], -// }; -// -// /* -// We use redux-actions to handle our actions. -// Normally this would look like: -// -// function (state = defaultState, action) { -// switch (action.type) { -// case readPostsByIdSucceeded: -// return {...}; -// case readPostsSucceeded: -// return {...}; -// default: -// return state; -// } -// } -// } -// */ -// -// function (state = defaultState, action) { -// switch (action.type) { -// -// } -// } +import { readPostByIdSucceeded, readPostsSucceeded, votePostByIdSucceeded } from './actions'; + +const defaultState = []; + +export default function postReducer(state = defaultState, action) { + switch (action.type) { + case readPostsSucceeded: + return action.payload.posts; + case votePostByIdSucceeded: + let foundPost = false; + const newState = [...state]; + + for (let k = 0; k < newState.length; ++k) { + const post = newState[k]; + if (post.id === action.payload.post.id) { + foundPost = true; + newState[k] = action.payload.post; + break; + } + } + + if (!foundPost) { + newState.push(action.payload.post); + } + + return newState; + default: + return state; + } +} diff --git a/client/post/scenes/PostInformation/index.js b/client/post/scenes/PostInformation/index.js index 36bc15b..cdec0db 100644 --- a/client/post/scenes/PostInformation/index.js +++ b/client/post/scenes/PostInformation/index.js @@ -1,2 +1,55 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import { readPostById } from '../../actions'; + +class PostInformation extends Component { + static propTypes = { + params: PropTypes.shape({ + id: PropTypes.string, + }), + post: PropTypes.shape({ + id: PropTypes.number, + title: PropTypes.string, + user: PropTypes.shape({ + username: PropTypes.string, + }), + votes: PropTypes.number, + }), + dispatch: PropTypes.func, + } + + /* + Load the data before the actual component is rendered. + */ + componentDidMount() { + } + + render() { + const { post = {} } = this.props; + + return ( +
+

{post.title}

+
{post.user && post.user.username}
+
+ ); + } +} + +/* + mapStateToProps is a mandatory parameter is in react-redux's connect() function. + It takes two parameters, your current Redux State, and your ownProps is the current + state of your component's props (ownProps === this.props) + This function is the core of how Redux actually integrates with your React components. + Since your Redux Store is supposed to act as an "overall data store", you use the + mapStateToProps function to cherry-pick out which slices of the state you want to put + in your component. + Here, this.props.post will be populated with 'posts[ownProps.params.id]'. +*/ +function mapStateToProps(state, ownProps) { + return { + + }; +} diff --git a/client/post/scenes/PostList/index.js b/client/post/scenes/PostList/index.js index 8de608c..da9a3a4 100644 --- a/client/post/scenes/PostList/index.js +++ b/client/post/scenes/PostList/index.js @@ -1,16 +1,68 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + import Post from '../../components/Post'; +import { readPosts, votePostById } from '../../actions'; class PostList extends Component { - // state - // render + static propTypes = { + posts: PropTypes.shape({ + byId: PropTypes.objectOf(PropTypes.shape({ + id: PropTypes.number, + title: PropTypes.string, + user: PropTypes.shape({ + username: PropTypes.string, + }), + votes: PropTypes.number, + })), + allIds: PropTypes.arrayOf(PropTypes.number), + }), + dispatch: PropTypes.func, + } + + /* + Always make calls to retrieve initial data on this lifecycle hook. + We want to make sure that the component exists before we even attempt to populate + it with data + */ + componentDidMount() { + this.props.dispatch(readPosts()); + } + + votePost = (id, isUpvote) => { + this.props.dispatch(votePostById(id, isUpvote)); + } + render() { + /* + Object destructuring. This is equivalent to: const posts = this.props.posts; + */ + const posts = this.props.posts; + return (
- +

Posts

+ { posts.map((p) => ( + this.votePost(p.id, true)} + downvotePost={() => this.votePost(p.id, false)} + /> + )) }
); } } -export default PostList; + +function mapStateToProps(state) { + return { + posts: state.post, + }; +} + +export default connect(mapStateToProps)(PostList);