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

+ +
+ +
+

Latest tweets

+ +
+
- -
-) - -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}

- -
- ) - } + return ( +
+

@{cand}

+ +
+ ) } 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}); + } +} +*/