diff --git a/package.json b/package.json index a1b4c2d..c254e71 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,9 @@ "babel-polyfill": "^6.26.0", "react": "^16.4.1", "react-dom": "^16.4.1", - "react-redux": "^5.0.7", "react-router-dom": "^4.3.1", - "redux": "^4.0.0", "snuownd": "git+https://github.com/JordanMilne/snuownd.git", + "unstated": "^2.1.1", "whatwg-fetch": "^2.0.4" }, "devDependencies": { diff --git a/src/index.js b/src/index.js index 008dbde..67cdda6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,18 +1,17 @@ import 'babel-polyfill' import React from 'react' import ReactDOM from 'react-dom' -import { Provider } from 'react-redux' import { BrowserRouter, Switch, Route } from 'react-router-dom' - -import { store } from './state' +import { Provider } from 'unstated' import Header from './pages/common/Header' import About from './pages/about' import Subreddit from './pages/subreddit' import Thread from './pages/thread' +import NotFound from './pages/notFound' ReactDOM.render( - +
@@ -23,6 +22,7 @@ ReactDOM.render( + diff --git a/src/pages/common/StatusBox.js b/src/pages/common/StatusBox.js index e1df31e..a7a048a 100644 --- a/src/pages/common/StatusBox.js +++ b/src/pages/common/StatusBox.js @@ -1,18 +1,13 @@ import React from 'react' -import { connect } from 'react-redux' +import {connect} from '../../state' const StatusBox = props => (
{props.text && -

{props.text}

} +

{props.gloabl.state.statusText}

} {props.image && - status} + status}
) -const mapStateToProps = state => ({ - text: state.status.text, - image: state.status.image -}) - -export default connect(mapStateToProps)(StatusBox) +export default connect(StatusBox) diff --git a/src/pages/notFound/index.js b/src/pages/notFound/index.js new file mode 100644 index 0000000..c3eb8ea --- /dev/null +++ b/src/pages/notFound/index.js @@ -0,0 +1,5 @@ +import React from 'react' + +export default () => ( +

404 - Not found

+) diff --git a/src/pages/subreddit/index.js b/src/pages/subreddit/index.js index 472de16..1a52aac 100644 --- a/src/pages/subreddit/index.js +++ b/src/pages/subreddit/index.js @@ -1,7 +1,7 @@ import React from 'react' import { Link } from 'react-router-dom' -import { getRemovedThreadIDs } from 'api/removeddit' -import { getThreads } from 'api/reddit' +import { getRemovedThreadIDs } from '../../api/removeddit' +import { getThreads } from '../../api/reddit' import Post from '../common/Post' const getSubredditForAPI = props => { @@ -21,19 +21,20 @@ export default class Subreddit extends React.Component { subreddit: '' } } + componentDidMount () { - this.updateThreads(this.props) + this.getRemovedThreads(this.props) } componentWillReceiveProps (nextProps) { const newSubreddit = getSubredditForAPI(nextProps) if (this.state.subreddit !== newSubreddit) { - this.updateThreads(nextProps) + this.getRemovedThreads(nextProps) } } - updateThreads (props) { + getRemovedThreads (props) { const subreddit = getSubredditForAPI(props) getRemovedThreadIDs(subreddit) .then(threadIDs => getThreads(threadIDs)) diff --git a/src/pages/thread/Comment.js b/src/pages/thread/Comment.js index e93d827..8a601b5 100644 --- a/src/pages/thread/Comment.js +++ b/src/pages/thread/Comment.js @@ -1,5 +1,5 @@ import React from 'react' -import { prettyScore, prettyDate, parse } from 'utils' +import { prettyScore, prettyDate, parse } from '../../utils' const Comment = (props) => { let commentStyle = 'comment ' diff --git a/src/pages/thread/CommentSection.js b/src/pages/thread/CommentSection.js index 6a38849..f557c0e 100644 --- a/src/pages/thread/CommentSection.js +++ b/src/pages/thread/CommentSection.js @@ -2,11 +2,7 @@ import React from 'react' import Comment from './Comment' import CommentInfo from './CommentInfo' import SortBy from './SortBy' -import { connect } from 'react-redux' -import { - SORT_TOP, SORT_BOTTOM, SORT_NEW, SORT_OLD, - SHOW_ALL, SHOW_REMOVED_DELETED, SHOW_REMOVED, SHOW_DELETED -} from 'state' +import {connect, sort, filter} from '../../state' import { topSort, bottomSort, newSort, oldSort, showRemovedAndDeleted, showRemoved, showDeleted @@ -94,22 +90,23 @@ const filterCommentTree = (comments, filterFunction) => { const commentSection = (props) => { console.time('render comment section') const commentTree = unflatten(props.comments, props.root, props.removed, props.deleted) + const {commentFilter, commentSort} = props.global.state - if (props.show === SHOW_REMOVED_DELETED) { + if (commentFilter === filter.removedDeleted) { filterCommentTree(commentTree, showRemovedAndDeleted) - } else if (props.show === SHOW_REMOVED) { + } else if (commentFilter === filter.removed) { filterCommentTree(commentTree, showRemoved) - } else if (props.show === SHOW_DELETED) { + } else if (commentFilter === filter.deleted) { filterCommentTree(commentTree, showDeleted) } - if (props.sort === SORT_TOP) { + if (commentSort === sort.top) { sortCommentTree(commentTree, topSort) - } else if (props.sort === SORT_BOTTOM) { + } else if (commentSort === sort.bottom) { sortCommentTree(commentTree, bottomSort) - } else if (props.sort === SORT_NEW) { + } else if (commentSort === sort.new) { sortCommentTree(commentTree, newSort) - } else if (props.sort === SORT_OLD) { + } else if (commentSort === sort.old) { sortCommentTree(commentTree, oldSort) } console.timeEnd('render comment section') @@ -132,9 +129,4 @@ const commentSection = (props) => { ) } -const mapStateToProps = state => ({ - sort: state.commentSection.sort, - show: state.commentSection.show -}) - -export default connect(mapStateToProps)(commentSection) +export default connect(commentSection) diff --git a/src/pages/thread/SortBy.js b/src/pages/thread/SortBy.js index 6c859c9..be5e8a4 100644 --- a/src/pages/thread/SortBy.js +++ b/src/pages/thread/SortBy.js @@ -1,45 +1,26 @@ import React from 'react' -import { - setCommentSort, - setCommentShow, - SORT_TOP, SORT_BOTTOM, SORT_NEW, SORT_OLD, - SHOW_ALL, SHOW_REMOVED_DELETED, SHOW_REMOVED, SHOW_DELETED -} from 'state' -import { connect } from 'react-redux' +import {connect, sort, filter} from '../../state' const sortBy = props => (
sorted by: - props.global.setCommentSort(e.target.value)} defaultValue={props.global.state.commentSort}> + + + + show: - props.global.setCommentFilter(e.target.value)} defaultValue={props.global.state.commentFilter}> + + + +
) -const mapStateToProps = state => ({ - sort: state.commentSection.sort, - show: state.commentSection.show -}) - -const mapDispatchToProps = dispatch => ({ - setSort: sortString => dispatch(setCommentSort(sortString)), - setShow: showString => dispatch(setCommentShow(showString)) -}) - -export default connect( - mapStateToProps, - mapDispatchToProps -)(sortBy) +export default connect(sortBy) diff --git a/src/pages/thread/ThreadInfo.js b/src/pages/thread/ThreadInfo.js deleted file mode 100644 index 120d8a7..0000000 --- a/src/pages/thread/ThreadInfo.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' - -export default (props) => ( - -
- removed comments: {props.removedComments}/${props.totalComments} - ({ ((100 * props.removedComments) / props.totalComments).toFixed(1) }%) -
-
sorted by: top
-
-) diff --git a/src/state/commentSection.js b/src/state/commentSection.js deleted file mode 100644 index 756f8f0..0000000 --- a/src/state/commentSection.js +++ /dev/null @@ -1,49 +0,0 @@ -import { get, put } from 'utils' - -// Sort types -export const SORT_TOP = 'SORT_TOP' -export const SORT_BOTTOM = 'SORT_BOTTOM' -export const SORT_NEW = 'SORT_NEW' -export const SORT_OLD = 'SORT_OLD' - -// Filter types -export const SHOW_ALL = 'SHOW_ALL' -export const SHOW_REMOVED_DELETED = 'SHOW_REMOVED_DELETED' -export const SHOW_REMOVED = 'SHOW_REMOVED' -export const SHOW_DELETED = 'SHOW_DELETED' - -// Action types -export const COMMENT_SORT = 'COMMENT_SORT' -export const COMMENT_SHOW = 'COMMENT_SHOW' - -// Action creators -export const setCommentSort = payload => ({ type: COMMENT_SORT, payload }) -export const setCommentShow = payload => ({ type: COMMENT_SHOW, payload }) - -const sortKey = 'commentSort' -const filterKey = 'commentFilter' - -// Init state -const initialStatusState = { - sort: get(sortKey, SORT_TOP), - show: get(filterKey, SHOW_REMOVED_DELETED) -} - -export const commentSectionReducer = (state = initialStatusState, action) => { - switch (action.type) { - case COMMENT_SORT: - put(sortKey, action.payload) - return { - ...state, - sort: action.payload - } - case COMMENT_SHOW: - put(filterKey, action.payload) - return { - ...state, - show: action.payload - } - default: - return state - } -} diff --git a/src/state/index.js b/src/state/index.js index 639760c..21037ff 100644 --- a/src/state/index.js +++ b/src/state/index.js @@ -1,14 +1,71 @@ -import { createStore, combineReducers, applyMiddleware } from 'redux' -import logger from 'redux-logger' -import { statusReducer } from './status' -import { commentSectionReducer } from './commentSection' +import React from 'react' +import { Subscribe, Container } from 'unstated' +import { get, put } from 'utils' -export { setStatusLoading, setStatusSuccess, setStatusError } from './status' -export * from './commentSection' +// Sort types for comments +export const sort = { + top: 'SORT_TOP', + bottom: 'SORT_BOTTOM', + new: 'SORT_NEW', + old: 'SORT_OLD' +} -const reducer = combineReducers({ - status: statusReducer, - commentSection: commentSectionReducer -}) +// Filter types for comments +export const filter = { + all: 'SHOW_ALL', + removedDeleted: 'SHOW_REMOVED_DELETED', + removed: 'SHOW_REMOVED', + deleted: 'SHOW_DELETED' +} -export const store = createStore(reducer, applyMiddleware(logger)) +// Keys for localStorage +const sortKey = 'commentSort' +const filterKey = 'commentFilter' + +class GlobalState extends Container { + constructor () { + super() + + this.state = { + commentSort: get(sortKey, sort.top), + commentFilter: get(filterKey, filter.removedDeleted), + statusText: '', + statusImage: undefined + } + } + + setCommentSort (sortType) { + put(sortKey, sortType) + this.setState({commentSort: sortType}) + } + + setCommentFilter (filterType) { + put(filterKey, filterType) + this.setState({commentFilter: filterType}) + } + + setErrorText (text) { + this.setState({statusText: text, statusImage: '/images/error.png'}) + } + + setSuccessText (text) { + this.setState({statusText: text, statusImage: '/images/done.png'}) + } + + setLoadingText (text) { + this.setState({statusText: text, statusImage: '/images/loading.gif'}) + } + + clearStatusText () { + this.setState({statusText: '', statusImage: undefined}) + } +} + +// A redux-like connect function for Unstated +export const connect = Component => { + return props => ( + + {gloablState => } + + ) +} diff --git a/src/state/status.js b/src/state/status.js deleted file mode 100644 index 35290df..0000000 --- a/src/state/status.js +++ /dev/null @@ -1,46 +0,0 @@ -const images = { - loading: '/images/loading.gif', - error: '/images/error.png', - success: '/images/done.png' -} - -// Action types -const STATUS_SET_LOADING = 'STATUS_SET_LOADING' -const STATUS_SET_SUCCESS = 'STATUS_SET_SUCCESS' -const STATUS_SET_ERROR = 'STATUS_SET_ERROR' - -// Action creators -export const setStatusLoading = (payload = '') => ({ type: STATUS_SET_LOADING, payload }) -export const setStatusSuccess = (payload = '') => ({ type: STATUS_SET_SUCCESS, payload }) -export const setStatusError = (payload = '') => ({ type: STATUS_SET_ERROR, payload }) - -// Init state -const initialStatusState = { - text: null, - image: null -} - -export const statusReducer = (state = initialStatusState, action) => { - switch (action.type) { - case STATUS_SET_SUCCESS: - return { - ...state, - text: action.payload, - image: images.success - } - case STATUS_SET_LOADING: - return { - ...state, - text: action.payload, - image: images.loading - } - case STATUS_SET_ERROR: - return { - ...state, - text: action.payload, - image: images.error - } - default: - return state - } -} diff --git a/src/utils/index.js b/src/utils/index.js index 27ac69b..d661481 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -23,9 +23,6 @@ export const chunk = (arr, size) => { return chunks } -// JSON parsing for fetch -export const json = x => x.json() - // Make multiple requests to the same url, with an array of data (usually comment IDs) // This is needed since there is a limit on how long a url can be export const fetchMultiple = (url, arr, header, size = 100) => { @@ -34,7 +31,7 @@ export const fetchMultiple = (url, arr, header, size = 100) => { return Promise.all(subArrays.map(subArr => window.fetch(url + subArr.join(), header))) } -export const jsonMultiple = responses => Promise.all(responses.map(json)) +export const jsonMultiple = responses => Promise.all(responses.map(response => response.json())) // Change bases export const toBase36 = number => parseInt(number, 10).toString(36) @@ -85,9 +82,10 @@ export const prettyScore = score => { } // Retrieve, store and delete stuff in the local storage -export const get = (key, defaultValue) => ( - window.localStorage.getItem(key) !== null ? JSON.parse(window.localStorage.getItem(key)) : defaultValue -) +export const get = (key, defaultValue) => { + const value = window.localStorage.getItem(key) + return value !== null ? JSON.parse(value) : defaultValue +} export const put = (key, value) => window.localStorage.setItem(key, JSON.stringify(value)) diff --git a/yarn.lock b/yarn.lock index 08d1802..c44e385 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1532,6 +1532,10 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-context@^0.1.5: + version "0.1.6" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.1.6.tgz#0f425931d907741127acc6e31acb4f9015dd9fdc" + cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -2915,7 +2919,7 @@ interpret@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" -invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -3352,10 +3356,6 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash-es@^4.17.5: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" - lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -4379,17 +4379,6 @@ react-dom@^16.4.1: object-assign "^4.1.1" prop-types "^15.6.0" -react-redux@^5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8" - dependencies: - hoist-non-react-statics "^2.5.0" - invariant "^2.0.0" - lodash "^4.17.5" - lodash-es "^4.17.5" - loose-envify "^1.1.0" - prop-types "^15.6.0" - react-router-dom@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" @@ -4486,13 +4475,6 @@ redux-logger@^3.0.6: dependencies: deep-diff "^0.3.5" -redux@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.0.tgz#aa698a92b729315d22b34a0553d7e6533555cc03" - dependencies: - loose-envify "^1.1.0" - symbol-observable "^1.2.0" - regenerate@^1.2.1: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" @@ -5204,10 +5186,6 @@ supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0: dependencies: has-flag "^3.0.0" -symbol-observable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - table@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" @@ -5419,6 +5397,12 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +unstated@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/unstated/-/unstated-2.1.1.tgz#36b124dfb2e7a12d39d0bb9c46dfb6e51276e3a2" + dependencies: + create-react-context "^0.1.5" + upath@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"