Browse Source

Added react componets to display fetched data.

Snow 8 years ago
parent
commit
a62678c873

+ 3 - 1
package.json

@@ -20,7 +20,8 @@
       ]
     ],
     "plugins": [
-      "react-hot-loader/babel"
+      "react-hot-loader/babel",
+      "transform-class-properties"
     ]
   },
   "eslintConfig": {
@@ -35,6 +36,7 @@
     "babel-core": "^6.25.0",
     "babel-jest": "^20.0.3",
     "babel-loader": "^7.1.0",
+    "babel-plugin-transform-class-properties": "^6.24.1",
     "babel-preset-env": "^1.5.2",
     "babel-preset-es2015": "^6.24.1",
     "babel-preset-es2016": "^6.24.1",

+ 15 - 1
src/__test__/actions.test.js

@@ -1,13 +1,27 @@
+import fetch from 'isomorphic-fetch';
 import actions from '../actions/';
 import * as c from '../actions/constants';
 
 describe('Check action creator', () => {
   const testPayload = 'testPayload';
 
-  it('Check actions are correctly setup', () => {
+  it('Check select subreddit action is correctly setup', () => {
     expect(actions.selectSubreddit(testPayload)).toEqual({
       type: c.SELECT_SUBREDDIT,
       payload: testPayload
     });
   });
+
+  it('Check receive posts action is working', () => {
+    const targetReddit = 'darksouls';
+    const targetURL = `https://www.reddit.com/r/${targetReddit}.json`;
+    const json = fetch(targetURL)
+      .then(response => response.json())
+      .then(json => {
+        const postLength = json.data.children.length;
+        expect(
+          actions.receivePosts(targetReddit, json).payload.posts.length
+        ).toBe(postLength);
+      });
+  });
 });

+ 23 - 0
src/components/RedditFetcher/FunctionalComponents/Picker.js

@@ -0,0 +1,23 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+export default function Picker({ value, onChange, options }) {
+  return (
+    <span>
+      <h1>{value.replace(/^./g, x => x.toUpperCase())}</h1>
+      <select onChange={e => onChange(e.target.value)} value={value}>
+        {options.map(option =>
+          <option value={option} key={option}>
+            {option}
+          </option>
+        )}
+      </select>
+    </span>
+  );
+}
+
+Picker.propTypes = {
+  options: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
+  value: PropTypes.string.isRequired,
+  onChange: PropTypes.func.isRequired
+};

+ 20 - 0
src/components/RedditFetcher/FunctionalComponents/Posts.js

@@ -0,0 +1,20 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+export default function Posts({ posts }) {
+  return (
+    <ul>
+      {posts.map((post, i) =>
+        <li key={i}>
+          <a href={post.url} target="_blank">
+            {post.title}
+          </a>
+        </li>
+      )}
+    </ul>
+  );
+}
+
+Posts.propTypes = {
+  posts: PropTypes.array.isRequired
+};

+ 4 - 0
src/components/RedditFetcher/FunctionalComponents/index.js

@@ -0,0 +1,4 @@
+import Picker from './Picker.js';
+import Posts from './Posts.js';
+
+export { Picker, Posts };

+ 89 - 0
src/components/RedditFetcher/RedditFetcher.js

@@ -0,0 +1,89 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import actions from '../../actions/';
+import { Picker, Posts } from '../RedditFetcher/FunctionalComponents';
+
+function mapStateToProps(state) {
+  const { selectedSubreddit, postsBySubreddit } = state;
+  const { isFetching, lastUpdated, items: posts } = postsBySubreddit[
+    selectedSubreddit
+  ] || {
+    isFetching: true,
+    items: []
+  };
+  return {
+    selectedSubreddit,
+    posts,
+    isFetching,
+    lastUpdated
+  };
+}
+
+class RedditFetcher extends Component {
+  componentDidMount() {
+    const { dispatch, selectedSubreddit } = this.props;
+    dispatch(actions.fetchPosts(selectedSubreddit));
+  }
+
+  componentDidUpdate(prevProps) {
+    if (this.props.selectedSubreddit !== prevProps.selectedSubreddit) {
+      const { dispatch, selectedSubreddit } = this.props;
+      dispatch(actions.fetchPosts(selectedSubreddit));
+    }
+  }
+
+  handleChange = nextSubreddit => {
+    this.props.dispatch(actions.selectSubreddit(nextSubreddit));
+    this.props.dispatch(actions.fetchPosts(nextSubreddit));
+  };
+
+  handleRefreshClick = e => {
+    e.preventDefault();
+
+    const { dispatch, selectedSubreddit } = this.props;
+    dispatch(actions.invalidateSubreddit(selectedSubreddit));
+    dispatch(actions.fetchPosts(selectedSubreddit));
+  };
+
+  propTypes = {
+    selectedSubreddit: PropTypes.string.isRequired,
+    posts: PropTypes.array.isRequired,
+    isFetching: PropTypes.bool.isRequired,
+    lastUpdated: PropTypes.number,
+    dispatch: PropTypes.func.isRequired
+  };
+
+  render() {
+    const { selectedSubreddit, posts, isFetching, lastUpdated } = this.props;
+    const options = ['Reactjs', 'Darksouls'];
+    return (
+      <div>
+        <Picker
+          value={selectedSubreddit}
+          onChange={this.handleChange}
+          options={options}
+        />
+        <p>
+          {lastUpdated &&
+            <span>
+              Last updated at {new Date(lastUpdated).toLocaleTimeString()}.
+              {' '}
+            </span>}
+          {!isFetching &&
+            <a href="#" onClick={this.handleRefreshClick}>
+              Refresh
+            </a>}
+        </p>
+        {isFetching && posts.length === 0 && <h2>Loading...</h2>}
+        {!isFetching && posts.length === 0 && <h2>Empty.</h2>}
+        {posts.length > 0 &&
+          <div style={{ opacity: isFetching ? 0.5 : 1 }}>
+            <Posts posts={posts} />
+          </div>}
+      </div>
+    );
+  }
+}
+
+export default connect(mapStateToProps)(RedditFetcher);

+ 17 - 0
src/components/RedditFetcher/index.js

@@ -0,0 +1,17 @@
+import React, { Component } from 'react';
+import { Provider } from 'react-redux';
+
+import configureStore from '../../configureStore';
+import RedditFetcher from './RedditFetcher';
+
+const store = configureStore();
+
+export default class Root extends Component {
+  render() {
+    return (
+      <Provider store={store}>
+        <RedditFetcher />
+      </Provider>
+    );
+  }
+}

+ 19 - 0
src/configureStore.js

@@ -0,0 +1,19 @@
+import { createLogger } from 'redux-logger';
+import { createStore, applyMiddleware } from 'redux';
+import createSagaMiddleware from 'redux-saga';
+
+import reducer, { watchFetchPosts } from './reducers/index';
+import actions from './actions';
+
+const loggerMiddleware = createLogger();
+const sagaMiddleware = createSagaMiddleware();
+
+export default function configureStore(initialState) {
+  const store = createStore(
+    reducer,
+    initialState,
+    applyMiddleware(sagaMiddleware, loggerMiddleware)
+  );
+  sagaMiddleware.run(watchFetchPosts);
+  return store;
+}

+ 5 - 18
src/index.js

@@ -1,23 +1,10 @@
+import 'babel-polyfill';
+
 import React from 'react';
 import ReactDOM from 'react-dom';
-import { createLogger } from 'redux-logger';
-import createSagaMiddleware from 'redux-saga';
-import { createStore, applyMiddleware } from 'redux';
-import reducer, { fetchPost, watchFetchPosts } from './reducers/index';
-import actions from './actions';
-
-const loggerMiddleware = createLogger();
-const sagaMiddleware = createSagaMiddleware();
-const store = createStore(
-  reducer,
-  applyMiddleware(sagaMiddleware, loggerMiddleware)
-);
-
-sagaMiddleware.run(watchFetchPosts);
-store.dispatch(actions.fetchPosts('darksouls'));
-
-const title = <h2>Test setup</h2>;
+import configureStore from './configureStore';
+import Root from './components/RedditFetcher';
 
-ReactDOM.render(<div>{title}</div>, document.getElementById('app'));
+ReactDOM.render(<Root />, document.getElementById('app'));
 
 module.hot.accept();