From 565b8c0dd5292eb66172342f72ac6226f9f47749 Mon Sep 17 00:00:00 2001 From: Michael Murray Date: Tue, 18 Jul 2017 20:25:39 -0700 Subject: [PATCH 1/4] intial redux refactor --- package-lock.json | 58 +++++++++++ package.json | 6 +- src/Api.js | 12 +++ src/App.js | 146 +-------------------------- src/actions/index.js | 183 ++++++++++++++++++++++++++++++++++ src/components/Message.js | 48 ++++----- src/components/MessageList.js | 44 ++++++-- src/components/Toolbar.js | 62 +++++++++--- src/index.js | 25 +++-- src/reducers/index.js | 88 ++++++++++++++++ src/store.js | 13 +++ 11 files changed, 480 insertions(+), 205 deletions(-) create mode 100644 src/Api.js create mode 100644 src/actions/index.js create mode 100644 src/reducers/index.js create mode 100644 src/store.js diff --git a/package-lock.json b/package-lock.json index eda071e..584e859 100755 --- a/package-lock.json +++ b/package-lock.json @@ -2184,6 +2184,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "deep-diff": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", + "integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=" + }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", @@ -4290,6 +4295,11 @@ "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" }, + "hoist-non-react-statics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", + "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" + }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -5487,6 +5497,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" }, + "lodash-es": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", + "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -7721,6 +7736,20 @@ } } }, + "react-redux": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.5.tgz", + "integrity": "sha1-+OjHsjlCJXblLWt9sGQ5RpvphGo=", + "requires": { + "create-react-class": "15.6.0", + "hoist-non-react-statics": "1.2.0", + "invariant": "2.2.2", + "lodash": "4.17.4", + "lodash-es": "4.17.4", + "loose-envify": "1.3.1", + "prop-types": "15.5.10" + } + }, "react-scripts": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-1.0.10.tgz", @@ -7924,6 +7953,30 @@ } } }, + "redux": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "requires": { + "lodash": "4.17.4", + "lodash-es": "4.17.4", + "loose-envify": "1.3.1", + "symbol-observable": "1.0.4" + } + }, + "redux-logger": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz", + "integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=", + "requires": { + "deep-diff": "0.3.8" + } + }, + "redux-thunk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz", + "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=" + }, "regenerate": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz", @@ -8712,6 +8765,11 @@ "serviceworker-cache-polyfill": "4.0.0" } }, + "symbol-observable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", + "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" + }, "symbol-tree": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", diff --git a/package.json b/package.json index 40e27bc..5c406f1 100755 --- a/package.json +++ b/package.json @@ -7,7 +7,11 @@ "font-awesome": "^4.7.0", "react": "^15.6.1", "react-dom": "^15.6.1", - "react-scripts": "1.0.10" + "react-redux": "^5.0.5", + "react-scripts": "1.0.10", + "redux": "^3.7.2", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.2.0" }, "scripts": { "start": "react-scripts start", diff --git a/src/Api.js b/src/Api.js new file mode 100644 index 0000000..c579aba --- /dev/null +++ b/src/Api.js @@ -0,0 +1,12 @@ +export default class Api { + static fetchMessages() { + return fetch("http://localhost:8181/api/messages") + .then(response => response.json()) + .then(json => { + return json._embedded.messages; + }) + .catch(err => { + throw err; + }); + } +} diff --git a/src/App.js b/src/App.js index c4642e2..2aca735 100755 --- a/src/App.js +++ b/src/App.js @@ -15,61 +15,14 @@ class App extends Component { }; this.updateRemovedMessages = this.updateRemovedMessages.bind(this); - this.updateLabelState = this.updateLabelState.bind(this); - this.updateAll = this.updateAll.bind(this); - this.updateMultipleMessages = this.updateMultipleMessages.bind(this); this.updateState = this.updateState.bind(this); this.renderCompose = this.renderCompose.bind(this); this.submitForm = this.submitForm.bind(this); } - async componentDidMount() { - const apiData = await this.getMessages(); - apiData.forEach(message => { - message.checked = false; - message.selected = false; - }); - this.setState({ - messages: apiData - }); - } - async getMessages() { - const response = await fetch("http://localhost:8181/api/messages"); - const json = await response.json(); - return json._embedded.messages; - } + renderCompose() { this.setState({ showCompose: !this.state.showCompose }); } - unreadMessageCount() { - let unreadMsgs = this.state.messages.filter(msg => msg.read === false); - return unreadMsgs.length; - } - - calculateSelected() { - let selectAll = "minus-"; - let selectedMsgs = this.state.messages.filter(msg => msg.selected === true); - - if (!selectedMsgs.length) { - selectAll = ""; - } else if (selectedMsgs.length === this.state.messages.length) { - selectAll = "check-"; - } - - return selectAll; - } - - addNewLabel(msg, newLabel) { - if (!msg.labels.includes(newLabel)) { - msg.labels.push(newLabel); - } - return { labels: msg.labels }; - } - - deleteSelectedLabel(msg, newLabel) { - let index = msg.labels.indexOf(newLabel); - msg.labels.splice(index, 1); - return { labels: msg.labels }; - } updateRemovedMessages() { let messages = []; @@ -97,91 +50,6 @@ class App extends Component { }); } - updateLabelState(newLabel, add) { - let messages = []; - let messageIds = []; - let msgLabels; - - this.state.messages.forEach(msg => { - if (msg.selected === true) { - if (add === "add") { - msgLabels = this.addNewLabel(msg, newLabel); - messageIds.push(msg.id); - } else { - msgLabels = this.deleteSelectedLabel(msg, newLabel); - messageIds.push(msg.id); - } - messages.push(Object.assign({}, msg, msgLabels)); - } else { - messages.push(msg); - } - }); - if (add === "add") { - fetch(`http://localhost:8181/api/messages`, { - method: "PATCH", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - messageIds: messageIds, - command: "addLabel", - label: newLabel - }) - }).then(() => { - this.setState({ messages }); - }); - } else { - fetch(`http://localhost:8181/api/messages`, { - method: "PATCH", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - messageIds: messageIds, - command: "removeLabel", - label: newLabel - }) - }).then(() => { - this.setState({ messages }); - }); - } - } - - updateAll(update) { - let messages = this.state.messages.map(msg => Object.assign(msg, update)); - this.setState({ messages }); - } - - updateMultipleMessages(update) { - let messages = []; - let messageIds = []; - - this.state.messages.forEach(msg => { - if (msg.selected === true) { - messages.push(Object.assign({}, msg, update)); - messageIds.push(msg.id); - } else { - messages.push(msg); - } - }); - fetch(`http://localhost:8181/api/messages`, { - method: "PATCH", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - messageIds: messageIds, - command: "read", - read: update.read - }) - }).then(() => { - this.setState({ messages }); - }); - } - updateState(messageId, update) { this.setState(prevState => { let message = prevState.messages.find(msg => msg.id === messageId); @@ -230,22 +98,12 @@ class App extends Component {
{this.state.showCompose ? : null} - +
); diff --git a/src/actions/index.js b/src/actions/index.js new file mode 100644 index 0000000..82f9b90 --- /dev/null +++ b/src/actions/index.js @@ -0,0 +1,183 @@ +export const MESSAGES_RECEIVED = "MESSAGES_RECEIVED"; +export const TOGGLE_STARRED = "TOGGLE_STARRED"; +export const TOGGLE_SELECTED = "TOGGLE_SELECTED"; +export const UPDATE_READ = "UPDATE_READ"; +export const UPDATE_UNREAD = "UPDATE_UNREAD"; +export const UPDATE_ALL = "UPDATE_ALL"; +export const UPDATE_LABEL_STATE = "UPDATE_LABEL_STATE"; + +export function fetchMessages() { + return async (dispatch, getState, { Api }) => { + const messages = await Api.fetchMessages(); + messages.forEach(message => { + message.selected = false; + }); + console.log(messages); + return dispatch({ + type: MESSAGES_RECEIVED, + messages + }); + }; +} + +export function toggleStarred(id, initialValue) { + return async (dispatch, getState) => { + await fetch(`http://localhost:8181/api/messages`, { + method: "PATCH", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + messageIds: [id], + command: "star", + star: !initialValue + }) + }); + return dispatch({ + type: TOGGLE_STARRED, + id + }); + }; +} + +export function toggleSelected(id, initialValue) { + return (dispatch, getState) => { + return dispatch({ + type: TOGGLE_SELECTED, + id + }); + }; +} +export function updateReadMessages() { + return async (dispatch, getState) => { + let messageIds = []; + + getState().messages.forEach(msg => { + if (msg.selected === true) { + messageIds.push(msg.id); + } + }); + await fetch(`http://localhost:8181/api/messages`, { + method: "PATCH", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + messageIds: [messageIds], + command: "read", + read: true + }) + }); + return dispatch({ + type: UPDATE_READ + }); + }; +} + +export function updateUnreadMessages() { + return async (dispatch, getState) => { + let messageIds = []; + + getState().messages.forEach(msg => { + if (msg.selected === true) { + messageIds.push(msg.id); + } + }); + await fetch(`http://localhost:8181/api/messages`, { + method: "PATCH", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + messageIds: [messageIds], + command: "read", + read: false + }) + }); + return dispatch({ + type: UPDATE_UNREAD + }); + }; +} +export function updateAll(update) { + return (dispatch, getState) => { + return dispatch({ + type: UPDATE_ALL, + update + }); + }; +} + +function addNewLabel(msg, newLabel) { + if (!msg.labels.includes(newLabel)) { + msg.labels.push(newLabel); + } + return { labels: msg.labels }; +} + +function deleteSelectedLabel(msg, newLabel) { + let index = msg.labels.indexOf(newLabel); + msg.labels.splice(index, 1); + return { labels: msg.labels }; +} + +export function updateLabelState(newLabel, add) { + return async (dispatch, getState) => { + let messages = []; + let messageIds = []; + let msgLabels; + getState().messages.forEach(msg => { + if (msg.selected === true) { + if (add === "add") { + msgLabels = addNewLabel(msg, newLabel); + messageIds.push(msg.id); + } else { + msgLabels = deleteSelectedLabel(msg, newLabel); + messageIds.push(msg.id); + } + messages.push(Object.assign({}, msg, msgLabels)); + } else { + messages.push(msg); + } + }); + if (add === "add") { + await fetch(`http://localhost:8181/api/messages`, { + method: "PATCH", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + messageIds: messageIds, + command: "addLabel", + label: newLabel + }) + }); + return dispatch({ + type: UPDATE_LABEL_STATE, + newLabel, + add + }); + } else { + await fetch(`http://localhost:8181/api/messages`, { + method: "PATCH", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + messageIds: messageIds, + command: "removeLabel", + label: newLabel + }) + }); + return dispatch({ + type: UPDATE_LABEL_STATE, + newLabel + }); + } + }; +} diff --git a/src/components/Message.js b/src/components/Message.js index 1ddea7a..0fa6849 100755 --- a/src/components/Message.js +++ b/src/components/Message.js @@ -1,30 +1,10 @@ import React, { Component } from "react"; +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; +import { toggleStarred, toggleSelected } from "../actions"; import Label from "./Labels.js"; class Message extends Component { - toggleStarred() { - let isStarred = !this.props.starred; - fetch(`http://localhost:8181/api/messages`, { - method: "PATCH", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - messageIds: [this.props.id], - command: "star", - star: isStarred - }) - }).then(() => { - this.props.updateState(this.props.id, { starred: isStarred }); - }); - } - - toggleChecked() { - let isSelected = !this.props.selected; - this.props.updateState(this.props.id, { selected: isSelected }); - } - render() { const read = this.props.read ? "read" : "unread"; const selected = this.props.selected ? "selected" : ""; @@ -38,14 +18,15 @@ class Message extends Component { this.toggleChecked()} + onChange={() => + this.props.toggleSelected(this.props.id, this.props.selected)} />
{ - this.toggleStarred(); + this.props.toggleStarred(this.props.id, this.props.starred); }} />
@@ -63,5 +44,20 @@ class Message extends Component { ); } } +const mapStateToProps = state => { + const messages = state.messages; + return { + messages + }; +}; + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + toggleStarred, + toggleSelected + }, + dispatch + ); -export default Message; +export default connect(mapStateToProps, mapDispatchToProps)(Message); diff --git a/src/components/MessageList.js b/src/components/MessageList.js index afefb04..38639ea 100755 --- a/src/components/MessageList.js +++ b/src/components/MessageList.js @@ -1,26 +1,48 @@ import React, { Component } from "react"; +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; +import { fetchMessages } from "../actions"; import Message from "./Message.js"; class MessageList extends Component { + componentDidMount() { + this.props.fetchMessages(); + } render() { + const { messages } = this.props; + return (
- {this.props.apiData.map(email => + {messages.map(message => )}
); } } +const mapStateToProps = state => { + const messages = state.messages; + return { + messages + }; +}; + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + fetchMessages + }, + dispatch + ); -export default MessageList; +export default connect(mapStateToProps, mapDispatchToProps)(MessageList); diff --git a/src/components/Toolbar.js b/src/components/Toolbar.js index f8afd9a..085e0a7 100755 --- a/src/components/Toolbar.js +++ b/src/components/Toolbar.js @@ -1,26 +1,42 @@ import React, { Component } from "react"; +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; +import { + updateReadMessages, + updateUnreadMessages, + updateAll, + updateLabelState +} from "../actions"; class Toolbar extends Component { + calculateSelected() { + let selectAll = "minus-"; + let selectedMsgs = this.props.messages.filter(msg => msg.selected === true); + + if (!selectedMsgs.length) { + selectAll = ""; + } else if (selectedMsgs.length === this.props.messages.length) { + selectAll = "check-"; + } + + return selectAll; + } selectAllMessages() { - if (this.props.calculateSelected !== "check-") { + if (this.calculateSelected() !== "check-") { this.props.updateAll({ selected: true }); } else { this.props.updateAll({ selected: false }); } } selectedMessagesCount() { - let selectedMessagesCount = this.props.apiData.filter( + let selectedMessagesCount = this.props.messages.filter( message => message.selected ); return selectedMessagesCount.length; } - - markReadMessages() { - this.props.updateMultipleMessages({ read: true }); - } - - markUnreadMessages() { - this.props.updateMultipleMessages({ read: false }); + unreadMessageCount() { + let unreadMsgs = this.props.messages.filter(msg => msg.read === false); + return unreadMsgs.length; } addLabel(event) { @@ -49,7 +65,7 @@ class Toolbar extends Component {

- {this.props.unreadMessageCount} + {this.unreadMessageCount()} unread messages

this.props.compose()}> @@ -58,20 +74,20 @@ class Toolbar extends Component { @@ -107,4 +123,22 @@ class Toolbar extends Component { } } -export default Toolbar; +const mapStateToProps = state => { + const messages = state.messages; + return { + messages + }; +}; + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + updateReadMessages, + updateUnreadMessages, + updateAll, + updateLabelState + }, + dispatch + ); + +export default connect(mapStateToProps, mapDispatchToProps)(Toolbar); diff --git a/src/index.js b/src/index.js index 7ab8e8b..a02690c 100755 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,18 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; -import registerServiceWorker from './registerServiceWorker'; -import 'bootstrap/dist/css/bootstrap.css'; -import 'bootstrap/dist/css/bootstrap-theme.css'; -import 'font-awesome/css/font-awesome.css'; -import './index.css'; +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./App"; +import registerServiceWorker from "./registerServiceWorker"; +import "bootstrap/dist/css/bootstrap.css"; +import "bootstrap/dist/css/bootstrap-theme.css"; +import "font-awesome/css/font-awesome.css"; +import "./index.css"; +import store from "./store"; +import { Provider } from "react-redux"; -ReactDOM.render(, document.getElementById('root')); +ReactDOM.render( + + + , + document.getElementById("root") +); registerServiceWorker(); diff --git a/src/reducers/index.js b/src/reducers/index.js new file mode 100644 index 0000000..482f59a --- /dev/null +++ b/src/reducers/index.js @@ -0,0 +1,88 @@ +import { combineReducers } from "redux"; +import { + MESSAGES_RECEIVED, + TOGGLE_STARRED, + TOGGLE_SELECTED, + UPDATE_READ, + UPDATE_UNREAD, + UPDATE_ALL, + UPDATE_LABEL_STATE +} from "../actions"; + +function addNewLabel(msg, newLabel) { + if (!msg.labels.includes(newLabel)) { + msg.labels.push(newLabel); + } + return { labels: msg.labels }; +} + +function deleteSelectedLabel(msg, newLabel) { + let index = msg.labels.indexOf(newLabel); + msg.labels.splice(index, 1); + return { labels: msg.labels }; +} + +function messages(state = [], action) { + switch (action.type) { + case MESSAGES_RECEIVED: + //if action too old, do nothing + const { messages } = action; + return [...messages]; + + case TOGGLE_STARRED: + return state.map(message => { + if (message.id === action.id) { + message.starred = !message.starred; + } + return message; + }); + case TOGGLE_SELECTED: + return state.map(message => { + if (message.id === action.id) { + message.selected = !message.selected; + } + return message; + }); + case UPDATE_READ: + return state.map(message => { + if (message.selected === true) { + message.read = true; + } + return message; + }); + case UPDATE_UNREAD: + return state.map(message => { + if (message.selected === true) { + message.read = false; + } + return message; + }); + case UPDATE_ALL: + return state.map(msg => Object.assign(msg, action.update)); + + case UPDATE_LABEL_STATE: + let msgs = []; + let msgLabels; + return state.map(msg => { + if (msg.selected === true) { + console.log(action.add); + if (action.add === "add") { + msgLabels = addNewLabel(msg, action.newLabel); + } else { + msgLabels = deleteSelectedLabel(msg, action.newLabel); + } + msgs.push(Object.assign({}, msg, msgLabels)); + } else { + msgs.push(msg); + } + return Object.assign({}, ...msgs, msg); + }); + + default: + return state; + } +} + +export default combineReducers({ + messages +}); diff --git a/src/store.js b/src/store.js new file mode 100644 index 0000000..334d05f --- /dev/null +++ b/src/store.js @@ -0,0 +1,13 @@ +import thunkMiddleware from "redux-thunk"; +import { createStore, applyMiddleware } from "redux"; +import logger from "redux-logger"; +import rootReducer from "./reducers"; +import Api from "./Api"; + +const store = createStore( + rootReducer, + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), + applyMiddleware(thunkMiddleware.withExtraArgument({ Api }), logger) +); + +export default store; From 1ab4a1ecaf99afff600e64d7ea1fcdf0dd3067e9 Mon Sep 17 00:00:00 2001 From: Michael Murray Date: Wed, 19 Jul 2017 10:34:22 -0700 Subject: [PATCH 2/4] complete redux refactor --- src/App.js | 114 ++++++++-------------------------- src/actions/index.js | 69 ++++++++++++++++++-- src/components/Compose.js | 23 ++++++- src/components/Message.js | 4 +- src/components/MessageList.js | 2 - src/components/Toolbar.js | 16 +++-- src/reducers/index.js | 54 +++++++--------- src/store.js | 17 ++++- 8 files changed, 165 insertions(+), 134 deletions(-) diff --git a/src/App.js b/src/App.js index 2aca735..ddb6f91 100755 --- a/src/App.js +++ b/src/App.js @@ -2,92 +2,13 @@ import React, { Component } from "react"; import MessageList from "./components/MessageList.js"; import Toolbar from "./components/Toolbar.js"; import Compose from "./components/Compose.js"; +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; +import { renderCompose, submitForm } from "../src/actions"; import logo from "./logo.svg"; import "./App.css"; class App extends Component { - constructor(props) { - super(props); - - this.state = { - messages: [], - showCompose: false - }; - - this.updateRemovedMessages = this.updateRemovedMessages.bind(this); - this.updateState = this.updateState.bind(this); - this.renderCompose = this.renderCompose.bind(this); - this.submitForm = this.submitForm.bind(this); - } - - renderCompose() { - this.setState({ showCompose: !this.state.showCompose }); - } - - updateRemovedMessages() { - let messages = []; - let messageIds = []; - - this.state.messages.forEach(msg => { - if (msg.selected) { - messageIds.push(msg.id); - } else { - messages.push(msg); - } - }); - fetch(`http://localhost:8181/api/messages`, { - method: "PATCH", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - messageIds: messageIds, - command: "delete" - }) - }).then(() => { - this.setState({ messages }); - }); - } - - updateState(messageId, update) { - this.setState(prevState => { - let message = prevState.messages.find(msg => msg.id === messageId); - let index = prevState.messages.indexOf(message); - let msgKey = Object.keys(update)[0]; - return { - messages: [ - ...prevState.messages.slice(0, index), - { ...message, [msgKey]: update[msgKey] }, - ...prevState.messages.slice(index + 1) - ] - }; - }); - } - submitForm = form => { - fetch(`http://localhost:8181/api/messages`, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - subject: form.subjectValue, - body: form.bodyValue - }) - }) - .then(response => { - return response.json(); - }) - .then(data => { - console.log(data); - console.log(this.state.messages); - this.setState({ - messages: [...this.state.messages, data], - showCompose: false - }); - }); - }; render() { return (
@@ -96,12 +17,9 @@ class App extends Component {

Welcome to React Inbox

- - {this.state.showCompose - ? + + {this.props.compose + ? : null}
@@ -110,4 +28,22 @@ class App extends Component { } } -export default App; +const mapStateToProps = state => { + const compose = state.compose; + const messages = state.messages; + return { + messages, + compose + }; +}; + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + renderCompose, + submitForm + }, + dispatch + ); + +export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/src/actions/index.js b/src/actions/index.js index 82f9b90..4c8087c 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -5,6 +5,9 @@ export const UPDATE_READ = "UPDATE_READ"; export const UPDATE_UNREAD = "UPDATE_UNREAD"; export const UPDATE_ALL = "UPDATE_ALL"; export const UPDATE_LABEL_STATE = "UPDATE_LABEL_STATE"; +export const UPDATE_REMOVED_MESSAGES = "UPDATE_REMOVED_MESSAGES"; +export const RENDER_COMPOSE = "RENDER_COMPOSE"; +export const COMPOSE_MESSAGE = "COMPOSE_MESSAGE"; export function fetchMessages() { return async (dispatch, getState, { Api }) => { @@ -12,7 +15,6 @@ export function fetchMessages() { messages.forEach(message => { message.selected = false; }); - console.log(messages); return dispatch({ type: MESSAGES_RECEIVED, messages @@ -158,8 +160,7 @@ export function updateLabelState(newLabel, add) { }); return dispatch({ type: UPDATE_LABEL_STATE, - newLabel, - add + messages }); } else { await fetch(`http://localhost:8181/api/messages`, { @@ -176,8 +177,68 @@ export function updateLabelState(newLabel, add) { }); return dispatch({ type: UPDATE_LABEL_STATE, - newLabel + messages }); } }; } + +export function updateRemovedMessages() { + return async (dispatch, getState) => { + let messages = []; + let messageIds = []; + + getState().messages.forEach(msg => { + if (msg.selected) { + messageIds.push(msg.id); + } else { + messages.push(msg); + } + }); + fetch(`http://localhost:8181/api/messages`, { + method: "PATCH", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + messageIds: messageIds, + command: "delete" + }) + }); + return dispatch({ + type: UPDATE_REMOVED_MESSAGES, + messages + }); + }; +} +export function renderCompose() { + return (dispatch, getState) => { + const compose = getState().compose; + return dispatch({ + type: RENDER_COMPOSE, + compose + }); + }; +} + +export function submitForm(form) { + return async (dispatch, getState) => { + let response = await fetch(`http://localhost:8181/api/messages`, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + subject: form.subjectValue, + body: form.bodyValue + }) + }); + let data = await response.json(); + return dispatch({ + type: COMPOSE_MESSAGE, + data + }); + }; +} diff --git a/src/components/Compose.js b/src/components/Compose.js index e0b98fe..389304a 100644 --- a/src/components/Compose.js +++ b/src/components/Compose.js @@ -1,4 +1,7 @@ import React, { Component } from "react"; +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; +import { renderCompose, submitForm } from "../actions"; class Compose extends Component { constructor(props) { @@ -59,4 +62,22 @@ class Compose extends Component { } } -export default Compose; +const mapStateToProps = state => { + const compose = state.compose; + const messages = state.messages; + return { + messages, + compose + }; +}; + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + renderCompose, + submitForm + }, + dispatch + ); + +export default connect(mapStateToProps, mapDispatchToProps)(Compose); diff --git a/src/components/Message.js b/src/components/Message.js index 0fa6849..2ee3d98 100755 --- a/src/components/Message.js +++ b/src/components/Message.js @@ -33,8 +33,8 @@ class Message extends Component {
- {this.props.labels.map(label => -
diff --git a/src/components/Toolbar.js b/src/components/Toolbar.js index 085e0a7..8f94264 100755 --- a/src/components/Toolbar.js +++ b/src/components/Toolbar.js @@ -5,7 +5,9 @@ import { updateReadMessages, updateUnreadMessages, updateAll, - updateLabelState + updateLabelState, + updateRemovedMessages, + renderCompose } from "../actions"; class Toolbar extends Component { @@ -68,7 +70,9 @@ class Toolbar extends Component { {this.unreadMessageCount()} unread messages

-
this.props.compose()}> + this.props.renderCompose()}>