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 =>
-
+ {this.props.labels.map((label, index) =>
+
)}
{this.props.subject}
diff --git a/src/components/MessageList.js b/src/components/MessageList.js
index 38639ea..4bb0226 100755
--- a/src/components/MessageList.js
+++ b/src/components/MessageList.js
@@ -22,8 +22,6 @@ class MessageList extends Component {
starred={message.starred}
read={message.read}
subject={message.subject}
- // selectAll={this.props.selectAll}
- // updateState={this.props.updateState}
/>
)}
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()}>