diff --git a/client/post/actions.js b/client/post/actions.js index 1966090..26233e5 100644 --- a/client/post/actions.js +++ b/client/post/actions.js @@ -1,17 +1,21 @@ -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) => { return async (dispatch) => { const post = await fetcher.get(`/api/posts/${postId}`); - dispatch(readPostByIdSucceeded({ post })); + dispatch({ + type: readPostByIdSucceeded, + payload: { + post, + }, + }); }; }; @@ -20,7 +24,12 @@ export const readPosts = () => { const body = await fetcher.get('/api/posts'); const posts = body.results; - dispatch(readPostsSucceeded({ posts })); + dispatch({ + type: readPostsSucceeded, + payload: { + posts, + }, + }); }; }; @@ -35,6 +44,11 @@ export const votePostById = (postId, isUpvote) => { body = await fetcher.delete(`/api/posts/${postId}/vote`); } - dispatch(votePostByIdSucceeded({ post: body })); + dispatch({ + type: votePostByIdSucceeded, + payload: { + post: body, + }, + }); }; }; diff --git a/client/post/reducer.js b/client/post/reducer.js index 351db58..fb721ed 100644 --- a/client/post/reducer.js +++ b/client/post/reducer.js @@ -1,11 +1,6 @@ -import { handleActions, combineActions } from 'redux-actions'; -import _ from 'lodash'; import { readPostByIdSucceeded, readPostsSucceeded, votePostByIdSucceeded } from './actions'; -const defaultState = { - byId: {}, - allIds: [], -}; +const defaultState = []; /* We use redux-actions to handle our actions. @@ -24,33 +19,25 @@ 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]), - }; - }, +export default function (state = defaultState, action) { + const payload = action.payload; + switch (action.type) { + case readPostsSucceeded: + return payload.posts; + case readPostByIdSucceeded: + case votePostByIdSucceeded: + const newPost = payload.post; + const currentPosts = state; + const updatedPosts = [newPost]; - [readPostsSucceeded]: (state, { payload }) => { - const posts = payload.posts; - const byId = {}; - const allIds = []; + currentPosts.forEach((post) => { + if (post.id !== newPost.id) { + updatedPosts.push(post); + } + }); - posts.forEach((post) => { - byId[post.id] = post; - allIds.push(post.id); - }); - - return { - ...state, - byId, - allIds, - }; - }, -}, defaultState); + return updatedPosts; + default: + return state; + } +} diff --git a/client/post/scenes/PostInformation/index.js b/client/post/scenes/PostInformation/index.js index 89fdff7..d1d4d21 100644 --- a/client/post/scenes/PostInformation/index.js +++ b/client/post/scenes/PostInformation/index.js @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import _ from 'lodash'; import { readPostById } from '../../actions'; @@ -18,14 +17,14 @@ export class PostInformation extends Component { }), votes: PropTypes.number, }), - readPostById: PropTypes.func, + dispatch: PropTypes.func, } /* Load the data before the actual component is rendered. */ componentDidMount() { - this.props.readPostById(this.props.params.id); + this.props.dispatch(readPostById(this.props.params.id)); } render() { @@ -59,35 +58,22 @@ export class PostInformation extends 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], - }; -} + const posts = state.post || []; + + let post; + + posts.forEach((p) => { + if (p.id.toString() === ownProps.params.id) { + post = p; + } + }); -/* - 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)), + post, }; } + /* We're not exporting PostInformation, but rather a "connected" version of the component. */ -export default connect(mapStateToProps, mapDispatchToProps)(PostInformation); +export default connect(mapStateToProps)(PostInformation); diff --git a/client/post/scenes/PostList/index.js b/client/post/scenes/PostList/index.js index 1abb4ff..d2b415c 100644 --- a/client/post/scenes/PostList/index.js +++ b/client/post/scenes/PostList/index.js @@ -7,19 +7,15 @@ 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, + posts: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.number, + title: PropTypes.string, + user: PropTypes.shape({ + username: PropTypes.string, + }), + votes: PropTypes.number, + })), + dispatch: PropTypes.func, } /* @@ -28,11 +24,11 @@ export class PostList extends Component { it with data */ componentDidMount() { - this.props.readPosts(); + this.props.dispatch(readPosts()); } votePost = (id, isUpvote) => { - this.props.votePostById(id, isUpvote); + this.props.dispatch(votePostById(id, isUpvote)); } render() { @@ -40,12 +36,11 @@ export class PostList extends Component { 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) => ( + { posts.map((p) => ( ... automatically binds 'this' to its parent context. + We also need to wrap these functions in arrows because we want to call votePost with parameters. + - To avoid calling it on each render(), we wrap it */ } } @@ -77,11 +74,4 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return { - readPosts: () => dispatch(readPosts()), - votePostById: (postId, isUpvote) => dispatch(votePostById(postId, isUpvote)), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(PostList); +export default connect(mapStateToProps)(PostList);