React hooks
This commit is contained in:
parent
d545fbe636
commit
abdda1e8d0
7 changed files with 169 additions and 168 deletions
|
@ -41,20 +41,18 @@ router.get('/twitter', (req, res) => {
|
||||||
|
|
||||||
// MARK: - /stream
|
// MARK: - /stream
|
||||||
router.get('/stream', (req, res) => {
|
router.get('/stream', (req, res) => {
|
||||||
streamClient.stream('statuses/filter', {track: req.query.hashtag}, function(stream) {
|
res.writeHead(200, {
|
||||||
stream.on('data', function(tweet) {
|
'Content-Type': 'text/plain',
|
||||||
res.status(200).json({
|
'Transfer-Encoding': 'chunked'
|
||||||
success: true,
|
|
||||||
message: tweets
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
stream.on('error', function(error) {
|
streamClient.stream('statuses/filter', {track: req.query.hashtag}, function(stream) {
|
||||||
console.log('Error ' + JSON.stringify(error, 0, 2))
|
stream.on('data', function(tweet) {
|
||||||
res.status(400).json({
|
// console.log(tweet.text)
|
||||||
success: false,
|
res.write(JSON.stringify(tweet, 0, 2))
|
||||||
message: error
|
|
||||||
})
|
})
|
||||||
|
stream.on('error', function(error) {
|
||||||
|
console.log(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,8 @@ import List from './List'
|
||||||
import Form from './Form'
|
import Form from './Form'
|
||||||
import Post from './Posts'
|
import Post from './Posts'
|
||||||
|
|
||||||
const App = () => (
|
export default function App() {
|
||||||
|
return (
|
||||||
<div className = 'row mt-5'>
|
<div className = 'row mt-5'>
|
||||||
|
|
||||||
<div className = 'col-md-4 offset-md-1'>
|
<div className = 'col-md-4 offset-md-1'>
|
||||||
|
@ -22,6 +23,5 @@ const App = () => (
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
export default App
|
|
||||||
|
|
7
src/js/components/Form.css
Normal file
7
src/js/components/Form.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
button {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clicked {
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
|
@ -1,9 +1,48 @@
|
||||||
import React, { Component } from "react"
|
// MARK: Definitions
|
||||||
|
import React, { useState, useEffect } from "react"
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import uuidv1 from "uuid"
|
|
||||||
import { addArticle } from "../actions/index"
|
import { addArticle } from "../actions/index"
|
||||||
import { setCandidate } 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 (
|
||||||
|
<div className='form'>
|
||||||
|
<button className={selected === 'Donald Trump' ? 'clicked' : null} onClick={clickTrump}>Donald Trump</button>
|
||||||
|
<button className={selected === 'Hillary Clinton' ? 'clicked' : null} onClick={clickHillary}>Hillary Clinton</button>
|
||||||
|
|
||||||
|
<p>{candidate}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Redux
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
addArticle: article => dispatch(addArticle(article)),
|
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 (
|
|
||||||
<div>
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
<div className="form-group">
|
|
||||||
<label htmlFor="title">Title</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
id="title"
|
|
||||||
value={title}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button type="submit" className="btn btn-success btn-lg">
|
|
||||||
SAVE
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<button onClick={this.clickTrump}>Donald Trump</button>
|
|
||||||
<button onClick={this.clickHilary}>Hillary Clinton</button>
|
|
||||||
|
|
||||||
<p>{this.state.candidate}</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Form = connect(null, mapDispatchToProps)(ConnectedForm)
|
const Form = connect(null, mapDispatchToProps)(ConnectedForm)
|
||||||
|
|
||||||
export default Form
|
export default Form
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
import React, { Component } from "react"
|
import React, { useEffect } from "react"
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import { getHillaryData } from "../actions/index"
|
import { getHillaryData } from "../actions/index"
|
||||||
import { getTrumpData } from "../actions/index"
|
import { getTrumpData } from "../actions/index"
|
||||||
|
|
||||||
export class Post extends Component {
|
export function Post(props) {
|
||||||
componentWillUpdate(nextProps, nextState) {
|
let { articles, candidate, getHillaryData, getTrumpData } = props
|
||||||
if (nextProps.candidate !== this.props.candidate) {
|
|
||||||
if (nextProps.candidate === 'Hillary Clinton') {
|
|
||||||
this.props.getHillaryData()
|
|
||||||
}
|
|
||||||
else if (nextProps.candidate === 'Donald Trump'){
|
|
||||||
this.props.getTrumpData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
// Fetch tweets when user clicks on candidate's button
|
||||||
let cand = this.props.candidate === undefined ? 'Not set' : this.props.candidate
|
useEffect(() => {
|
||||||
|
if (candidate === 'Hillary Clinton') {
|
||||||
|
getHillaryData()
|
||||||
|
}
|
||||||
|
else if (candidate === 'Donald Trump') {
|
||||||
|
getTrumpData()
|
||||||
|
}
|
||||||
|
}, [candidate, getHillaryData, getTrumpData])
|
||||||
|
|
||||||
|
let cand = candidate === undefined ? 'Not set' : candidate
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>@{cand}</p>
|
<p>@{cand}</p>
|
||||||
<ul className="list-group list-group-flush">
|
<ul className="list-group list-group-flush">
|
||||||
{this.props.articles.map(el => (
|
{articles.map(el => (
|
||||||
<li className="list-group-item" key={el.id_str}>
|
<li className="list-group-item" key={el.id_str}>
|
||||||
<p>{el.created_at}</p>
|
<p>{el.created_at}</p>
|
||||||
<p>{el.text}</p>
|
<p>{el.text}</p>
|
||||||
|
@ -31,7 +31,6 @@ export class Post extends Component {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
|
|
@ -23,12 +23,20 @@ function rootReducer(state = initialState, action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (action.type === DATA_LOADED) {
|
else if (action.type === DATA_LOADED) {
|
||||||
|
console.log('Payload: ' + action.payload)
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
remoteArticles: action.payload.message.statuses
|
remoteArticles: action.payload.message.statuses
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (action.type === DATA_LOADED_TO_ADD) {
|
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, {
|
return Object.assign({}, state, {
|
||||||
remoteArticles: state.remoteArticles.concat(action.payload.message.statuses)
|
remoteArticles: state.remoteArticles.concat(action.payload.message.statuses)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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() {
|
export default function* watcherSaga() {
|
||||||
yield all([
|
yield all([
|
||||||
worker(),
|
|
||||||
workerHillary(),
|
workerHillary(),
|
||||||
workerTrump()
|
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
|
// Hillary worker
|
||||||
function* workerHillary() {
|
function* workerHillary() {
|
||||||
yield takeEvery("DATA_HILLARY_REQUESTED", workerHillarySaga)
|
yield takeEvery('DATA_HILLARY_REQUESTED', workerHillarySaga)
|
||||||
}
|
}
|
||||||
|
|
||||||
function* workerHillarySaga() {
|
function* workerHillarySaga() {
|
||||||
try {
|
try {
|
||||||
const payload = yield call(getHillaryData)
|
const payload = yield call(getHillaryData)
|
||||||
yield put({ type: "DATA_LOADED", payload })
|
yield put({ type: 'DATA_LOADED', payload })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put({ type: "API_ERRORED", payload: e })
|
yield put({ type: 'API_ERRORED', payload: e })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHillaryData() {
|
function getHillaryData() {
|
||||||
return fetch("http://localhost:3030/api/twitter?hashtag=Hillary%20Clinton").then(response =>
|
return fetch('http://localhost:3030/api/twitter?hashtag=Hillary%20Clinton')
|
||||||
response.json()
|
.then(response => response.json())
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trump worker
|
// Trump worker
|
||||||
function* workerTrump() {
|
function* workerTrump() {
|
||||||
yield takeEvery("DATA_TRUMP_REQUESTED", workerTrumpSaga)
|
yield takeEvery('DATA_TRUMP_REQUESTED', workerTrumpSaga) // listenServerSaga
|
||||||
}
|
}
|
||||||
|
|
||||||
function* workerTrumpSaga() {
|
function* workerTrumpSaga() {
|
||||||
try {
|
try {
|
||||||
const payload = yield call(getTrumpData)
|
const payload = yield call(getTrumpData)
|
||||||
yield put({ type: "DATA_LOADED", payload })
|
yield put({ type: 'DATA_LOADED', payload })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put({ type: "API_ERRORED", payload: e })
|
yield put({ type: 'API_ERRORED', payload: e })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTrumpData() {
|
function getTrumpData() {
|
||||||
return fetch("http://localhost:3030/api/twitter?hashtag=Donald%20Trump").then(response =>
|
return fetch('http://localhost:3030/api/twitter?hashtag=Donald%20Trump')
|
||||||
response.json()
|
.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});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue