React hooks

This commit is contained in:
Rodrigo Pedroso 2019-06-22 19:57:06 -04:00
commit abdda1e8d0
7 changed files with 169 additions and 168 deletions

View file

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

View file

@ -3,25 +3,25 @@ import List from './List'
import Form from './Form'
import Post from './Posts'
const App = () => (
<div className = 'row mt-5'>
export default function App() {
return (
<div className = 'row mt-5'>
<div className = 'col-md-4 offset-md-1'>
<h2>Articles</h2>
<List />
</div>
<div className = 'col-md-4 offset-md-1'>
<h2>Articles</h2>
<List />
</div>
<div className = 'col-md-4 offset-md-1'>
<h2>Add a new article</h2>
<Form />
</div>
<div className = 'col-md-4 offset-md-1'>
<h2>Add a new article</h2>
<Form />
</div>
<div className="col-md-4 offset-md-1">
<h2>Latest tweets</h2>
<Post />
</div>
<div className="col-md-4 offset-md-1">
<h2>Latest tweets</h2>
<Post />
</div>
</div>
)
export default App
)
}

View file

@ -0,0 +1,7 @@
button {
margin: 10px;
}
.clicked {
background-color: lightgray;
}

View file

@ -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 (
<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) {
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 (
<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)
export default Form

View file

@ -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 (
<div>
<p>@{cand}</p>
<ul className="list-group list-group-flush">
{this.props.articles.map(el => (
<li className="list-group-item" key={el.id_str}>
<p>{el.created_at}</p>
<p>{el.text}</p>
</li>
))}
</ul>
</div>
)
}
return (
<div>
<p>@{cand}</p>
<ul className="list-group list-group-flush">
{articles.map(el => (
<li className="list-group-item" key={el.id_str}>
<p>{el.created_at}</p>
<p>{el.text}</p>
</li>
))}
</ul>
</div>
)
}
function mapStateToProps(state) {

View file

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

View file

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