SolutionBase is a social platform for users to address global sustainability issues like global warming, plastic free etc. Platform allows user to contribute their ideas/solutions to different topics and problems. Read more about sustainable development goals here.
One main feature is users can filter and sort posts in multiple combination. Their preferences are stored so that when they returned to homepage, it is still in sorted order and/or filtered.
-
We have a slice of state to keep track of sorting and filtering, separated by topic page and homepage.
-
For sorting, we use thunk action
updateSort
to update Redux store, and then, depending on thekey
, eitherfetchTopic
orfetchPosts
using the data object, which becomes the parameter Topic or Post controller receives.
// frontend/actions/filter_actions.js
export const updateSort = (id, data, key) => dispatch => {
dispatch(receiveSort(id, data, key));
if (key === 'topic') {
return fetchTopic(id, data)(dispatch);
} else if (key === 'homepage') {
return fetchPosts(data)(dispatch);
}
};
- The data object gets evaluated in the Topic or Post controller in
index
and calls Post's class methodsort_filter
. This method checks the sorting type and calls the responsible class methods, which then uses ActiveRecord to query the database.
# app/models/post.rb
def self.sort_filter(sort)
case sort[:sort]
when 'most recent'
self.most_recent(sort[:topic_id], sort[:post_type])
when 'most comments'
self.most_commented(sort[:topic_id], sort[:post_type])
when 'most votes'
self.most_votes(sort[:topic_id], sort[:post_type])
end
end
-
When posts are received, we also receive a
postOrder
array, which is used to map posts in the sorted order. -
Every sort and filter action is stored and updated in Redux store and passed down to a component with
mapStateToProps
. So component dispatchesupdateSort
on mount with the latest filter and sorting parameters.
To have children comments nested under a parent comment, we use recursion and Javascript object to achieve better time complexity.
If we have 1000 comments c1
, we do not want to iteratre all 1000 comments checking if its c2.parent_comment_id
equals to c1.id
. This will be 1000 * 1000 comparisons and an O(n^2) algorithm.
We can achieve this with O(n) time, where n is total number of comments.
- When we receive a post, we receive an array of
all_comments
, which is stored in the comments slice of Redux store. In a selector function that takes in state and the currently viewed post as arguments, we create an object with keys asparent_comment_id
and values arrays of their children comments. Comments with no parent havenull
as their keys.
// frontend/reducers/selectors.js
export const selectCommentsForPost = ({comments}, post) => {
// handle cases where there are no comments or we have not fetchPost
if (!Object.keys(comments).length) return {};
if (post.commentIds.filter(commentId => !comments[commentId]).length) return {};
// create map-like object
let hash = {};
post.commentIds.map(commentId => comments[commentId])
.forEach((comment) => {
let parent = comment.parent_comment_id;
let children = hash[parent];
let update;
if (children) {
update = { [parent] : [...children, comment] }
} else {
update = { [parent] : [comment] }
}
hash = Object.assign({}, hash, update);
})
return hash;
}
- In the
PostShow
component, we have arenderComments
method that takes a parent comment ID as an argument. This method lookups the children comments in the hash object created above and map over all the children comments. It makes a recursive call with each child comment ID to get its children comments. We call the function withnull
(comments with no parent) first, which will go through all their children and then their grandchildren and so on.
// frontend/components/posts/post_show.jsx
function renderComments(id) {
return (
<>
{comments[id].map(comment => (
<>
{singleComment(comment)} // returns <CommentIndexItem />
<ul>
{comments[comment.id] && <li>{renderComments(comment.id)}</li>}
</ul>
</>
))}
</>
)
}
...
return (
...
renderComments(null)
...
)
-
Front end
Use the npm package manager to install dependencies in root directory.
npm install npm run webpack
-
Back end
Install rails dependencies in root directory, setup Postgres database and start development server.
rails bundle exec install # create the database, load the schema, and initialize it with the seed data rails db:setup rails s
You can view the full application on
localhost:3000
.
Front-end:
- React - Front end framework
- Redux - State management tool used with React
- jQuery - DOM Manipulation
- AJAX - AJAX used for API calls
- Javascript - Language for site functionality
- React-Bootstrap - - Used for advanced styling and design system
- Material-UI - Used for advanced styling
- Styled-Component - Used for custom styling React components
- CSS3 - Used in conjunction with Material-UI for styling
- HTML5 - Used for general structure of webpage
Back-end:
- Lazy loading / Infinite scrolling
- Profile page
- Text based search
- Live time update new posts, comments and their vote counts with WebSockets