From abdda1e8d014ca8c9b5f2cb1acb5b3068874b051 Mon Sep 17 00:00:00 2001
From: Rodrigo Pedroso <>
Date: Sat, 22 Jun 2019 19:57:06 -0400
Subject: [PATCH] React hooks
---
server/api.js | 18 +++---
src/js/components/App.jsx | 42 ++++++-------
src/js/components/Form.css | 7 +++
src/js/components/Form.jsx | 117 +++++++++++++-----------------------
src/js/components/Posts.jsx | 53 ++++++++--------
src/js/reducers/index.js | 8 +++
src/js/sagas/api-saga.js | 92 +++++++++++++++++-----------
7 files changed, 169 insertions(+), 168 deletions(-)
create mode 100644 src/js/components/Form.css
diff --git a/server/api.js b/server/api.js
index c749688..529b6b0 100644
--- a/server/api.js
+++ b/server/api.js
@@ -41,20 +41,18 @@ router.get('/twitter', (req, res) => {
// MARK: - /stream
router.get('/stream', (req, res) => {
+ res.writeHead(200, {
+ 'Content-Type': 'text/plain',
+ 'Transfer-Encoding': 'chunked'
+ })
+
streamClient.stream('statuses/filter', {track: req.query.hashtag}, function(stream) {
stream.on('data', function(tweet) {
- res.status(200).json({
- success: true,
- message: tweets
- })
+ // console.log(tweet.text)
+ res.write(JSON.stringify(tweet, 0, 2))
})
-
stream.on('error', function(error) {
- console.log('Error ' + JSON.stringify(error, 0, 2))
- res.status(400).json({
- success: false,
- message: error
- })
+ console.log(error)
})
})
})
diff --git a/src/js/components/App.jsx b/src/js/components/App.jsx
index 62a7b7a..c7088cd 100644
--- a/src/js/components/App.jsx
+++ b/src/js/components/App.jsx
@@ -3,25 +3,25 @@ import List from './List'
import Form from './Form'
import Post from './Posts'
-const App = () => (
-
-
-
-
Articles
-
-
-
-
-
Add a new article
-
-
-
-
-
Latest tweets
-
+export default function App() {
+ return (
+
+
+
+
Articles
+
+
+
+
+
Add a new article
+
+
+
+
+
-
-
-)
-
-export default App
+ )
+}
diff --git a/src/js/components/Form.css b/src/js/components/Form.css
new file mode 100644
index 0000000..fc0faa2
--- /dev/null
+++ b/src/js/components/Form.css
@@ -0,0 +1,7 @@
+button {
+ margin: 10px;
+}
+
+.clicked {
+ background-color: lightgray;
+}
diff --git a/src/js/components/Form.jsx b/src/js/components/Form.jsx
index f70e28c..8b25154 100644
--- a/src/js/components/Form.jsx
+++ b/src/js/components/Form.jsx
@@ -1,9 +1,48 @@
-import React, { Component } from "react"
+// MARK: Definitions
+import React, { useState, useEffect } from "react"
import { connect } from "react-redux"
-import uuidv1 from "uuid"
import { addArticle } from "../actions/index"
import { setCandidate } from "../actions/index"
+import './Form.css'
+export function ConnectedForm(props) {
+ // MARK: State
+ let [selected, setSelected] = useState('')
+ let { candidate, setCandidate } = props
+
+ // MARK: Effects
+ // MARK: - Set Clinton as initial candidate
+ useEffect(() => {
+ setCandidate('Hillary Clinton')
+ setSelected('Hillary Clinton')
+ }, [setCandidate])
+
+ // MARK: - Set candidate when user clicks on button
+ useEffect(() => {
+ setCandidate(selected)
+ })
+
+ // MARK: Actions
+ let clickTrump = () => {
+ setSelected('Donald Trump')
+ }
+
+ let clickHillary = () => {
+ setSelected('Hillary Clinton')
+ }
+
+ // MARK: Return
+ return (
+
+
+
+
+
{candidate}
+
+ )
+}
+
+// MARK: Redux
function mapDispatchToProps(dispatch) {
return {
addArticle: article => dispatch(addArticle(article)),
@@ -11,80 +50,6 @@ function mapDispatchToProps(dispatch) {
}
}
-class ConnectedForm extends Component {
- constructor() {
- super()
-
- this.state = {
- title: '',
- candidate: ''
- }
-
- this.handleChange = this.handleChange.bind(this)
- this.handleSubmit = this.handleSubmit.bind(this)
- this.clickTrump = this.clickTrump.bind(this)
- this.clickHilary = this.clickHilary.bind(this)
- }
-
- componentDidMount() {
- this.props.setCandidate('Hillary Clinton')
- this.setState({ candidate: 'Hillary Clinton'})
- }
-
- handleChange(event) {
- this.setState({ [event.target.id]: event.target.value })
- }
-
- handleSubmit(event) {
- event.preventDefault()
- const { title } = this.state
- const id = uuidv1()
-
- this.props.addArticle({ title, id })
- this.setState({ title: "" })
- }
-
- clickTrump(event) {
- event.preventDefault()
- this.props.setCandidate('Donald Trump')
- this.setState({ candidate: 'Donald Trump' })
- }
-
- clickHilary(event) {
- event.preventDefault()
- this.props.setCandidate('Hillary Clinton')
- this.setState({ candidate: 'Hillary Clinton' })
- }
-
- render() {
- const { title } = this.state
- return (
-
-
-
-
-
-
-
{this.state.candidate}
-
- )
- }
-}
-
const Form = connect(null, mapDispatchToProps)(ConnectedForm)
export default Form
diff --git a/src/js/components/Posts.jsx b/src/js/components/Posts.jsx
index eee7726..7dda227 100644
--- a/src/js/components/Posts.jsx
+++ b/src/js/components/Posts.jsx
@@ -1,37 +1,36 @@
-import React, { Component } from "react"
+import React, { useEffect } from "react"
import { connect } from "react-redux"
import { getHillaryData } from "../actions/index"
import { getTrumpData } from "../actions/index"
-export class Post extends Component {
- componentWillUpdate(nextProps, nextState) {
- if (nextProps.candidate !== this.props.candidate) {
- if (nextProps.candidate === 'Hillary Clinton') {
- this.props.getHillaryData()
- }
- else if (nextProps.candidate === 'Donald Trump'){
- this.props.getTrumpData()
- }
+export function Post(props) {
+ let { articles, candidate, getHillaryData, getTrumpData } = props
+
+ // Fetch tweets when user clicks on candidate's button
+ useEffect(() => {
+ if (candidate === 'Hillary Clinton') {
+ getHillaryData()
}
- }
+ else if (candidate === 'Donald Trump') {
+ getTrumpData()
+ }
+ }, [candidate, getHillaryData, getTrumpData])
- render() {
- let cand = this.props.candidate === undefined ? 'Not set' : this.props.candidate
+ let cand = candidate === undefined ? 'Not set' : candidate
- return (
-
-
@{cand}
-
- {this.props.articles.map(el => (
- -
-
{el.created_at}
- {el.text}
-
- ))}
-
-
- )
- }
+ return (
+
+
@{cand}
+
+ {articles.map(el => (
+ -
+
{el.created_at}
+ {el.text}
+
+ ))}
+
+
+ )
}
function mapStateToProps(state) {
diff --git a/src/js/reducers/index.js b/src/js/reducers/index.js
index df49aca..dc45d28 100644
--- a/src/js/reducers/index.js
+++ b/src/js/reducers/index.js
@@ -23,12 +23,20 @@ function rootReducer(state = initialState, action) {
}
else if (action.type === DATA_LOADED) {
+ console.log('Payload: ' + action.payload)
return Object.assign({}, state, {
remoteArticles: action.payload.message.statuses
})
}
else if (action.type === DATA_LOADED_TO_ADD) {
+ console.log('Payload: ' + action.payload)
+ return Object.assign({}, state, {
+ remoteArticles: state.remoteArticles.concat(action.payload)
+ })
+ }
+
+ else if (action.type === "DATA_LOADED_TO_ADD_2") {
return Object.assign({}, state, {
remoteArticles: state.remoteArticles.concat(action.payload.message.statuses)
})
diff --git a/src/js/sagas/api-saga.js b/src/js/sagas/api-saga.js
index 35de458..8d1c3ee 100644
--- a/src/js/sagas/api-saga.js
+++ b/src/js/sagas/api-saga.js
@@ -1,69 +1,93 @@
-import { takeEvery, call, put, all } from 'redux-saga/effects'
+import { takeEvery, call, put, all/*, take*/ } from 'redux-saga/effects'
+// import { io, eventChannel } from 'redux-saga'
export default function* watcherSaga() {
yield all([
- worker(),
workerHillary(),
workerTrump()
])
}
-// Data worker
-function* worker() {
- yield takeEvery("DATA_REQUESTED", workerSaga)
-}
-
-function* workerSaga() {
- try {
- const payload = yield call(getData)
- yield put({ type: "DATA_LOADED", payload })
- } catch (e) {
- yield put({ type: "API_ERRORED", payload: e })
- }
-}
-
-function getData() {
- return fetch("https://jsonplaceholder.typicode.com/posts").then(response =>
- response.json()
- )
-}
-
// Hillary worker
function* workerHillary() {
- yield takeEvery("DATA_HILLARY_REQUESTED", workerHillarySaga)
+ yield takeEvery('DATA_HILLARY_REQUESTED', workerHillarySaga)
}
function* workerHillarySaga() {
try {
const payload = yield call(getHillaryData)
- yield put({ type: "DATA_LOADED", payload })
+ yield put({ type: 'DATA_LOADED', payload })
} catch (e) {
- yield put({ type: "API_ERRORED", payload: e })
+ yield put({ type: 'API_ERRORED', payload: e })
}
}
function getHillaryData() {
- return fetch("http://localhost:3030/api/twitter?hashtag=Hillary%20Clinton").then(response =>
- response.json()
- )
+ return fetch('http://localhost:3030/api/twitter?hashtag=Hillary%20Clinton')
+ .then(response => response.json())
}
// Trump worker
function* workerTrump() {
- yield takeEvery("DATA_TRUMP_REQUESTED", workerTrumpSaga)
+ yield takeEvery('DATA_TRUMP_REQUESTED', workerTrumpSaga) // listenServerSaga
}
function* workerTrumpSaga() {
try {
const payload = yield call(getTrumpData)
- yield put({ type: "DATA_LOADED", payload })
+ yield put({ type: 'DATA_LOADED', payload })
} catch (e) {
- yield put({ type: "API_ERRORED", payload: e })
+ yield put({ type: 'API_ERRORED', payload: e })
}
}
function getTrumpData() {
- return fetch("http://localhost:3030/api/twitter?hashtag=Donald%20Trump").then(response =>
- response.json()
- )
+ return fetch('http://localhost:3030/api/twitter?hashtag=Donald%20Trump')
+ .then(response => response.json())
}
+
+
+
+
+
+// ---------- Stream ----------------
+ /*
+const socketServerURL = 'http://localhost:3030/api/stream?hashtag=space'
+let socket;
+
+wrapping function for socket.on
+const connect = () => {
+ socket = io(socketServerURL);
+ return new Promise((resolve) => {
+ socket.on('connect', () => {
+ resolve(socket);
+ });
+ });
+};
+
+// This is how a channel is created
+const createSocketChannel = socket => eventChannel((emit) => {
+ const handler = (data) => {
+ emit(data);
+ };
+ socket.on('newTask', handler);
+ return () => {
+ socket.off('newTask', handler);
+ };
+});
+
+// saga that listens to the socket and puts the new data into the reducer
+const listenServerSaga = function* () {
+ // connect to the server
+ const socket = yield call(connect);
+
+ // then create a socket channel
+ const socketChannel = yield call(createSocketChannel, socket);
+
+ // then put the new data into the reducer
+ while (true) {
+ const payload = yield take(socketChannel);
+ yield put({type: 'DATA_LOADED', payload});
+ }
+}
+*/