diff --git a/package.json b/package.json index 4488cb3..4faff3b 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,11 @@ "dependencies": { "react": "^16.9.0", "react-dom": "^16.9.0", + "react-redux": "^7.1.1", "react-router": "^5.0.1", "react-router-dom": "^5.0.1", - "react-scripts": "3.1.1" + "react-scripts": "3.1.1", + "redux": "^4.0.4" }, "scripts": { "start": "react-scripts start", diff --git a/redux-basics.js b/redux-basics.js new file mode 100644 index 0000000..bbb1bc3 --- /dev/null +++ b/redux-basics.js @@ -0,0 +1,23 @@ +const redux = require('redux'); +const createStore = redux.createStore; + +const initialState = { number: 0 }; +const reducer = (state = initialState, action) => { + if (action.type == 'ADD') { + return ({ ...state, number: state.number + 1 }); + } else if (action.type == 'ADD_VALUE') { + return ({ ...state, number: state.number + action.value }); + } else { + return state; + } +} + +const store = createStore(reducer); +console.log(store.getState()); + +store.subscribe(() => { + console.log('[Subscription]', store.getState()); +}); + +store.dispatch({ type: 'ADD' }); +store.dispatch({ type: 'ADD_VALUE', value: 5 }); \ No newline at end of file diff --git a/src/components/Todo/Todo.js b/src/components/Todo/Todo.js index 9c80927..d1adba6 100644 --- a/src/components/Todo/Todo.js +++ b/src/components/Todo/Todo.js @@ -8,10 +8,12 @@ const Todo = (props) => {
+ onClick={props.clickDetail}> {props.title}
{props.done &&
} + +
); }; diff --git a/src/containers/TodoList/NewTodo/NewTodo.js b/src/containers/TodoList/NewTodo/NewTodo.js index 59e7930..16f151f 100644 --- a/src/containers/TodoList/NewTodo/NewTodo.js +++ b/src/containers/TodoList/NewTodo/NewTodo.js @@ -4,27 +4,21 @@ import { Redirect } from 'react-router-dom'; import './NewTodo.css'; +import { connect } from 'react-redux'; +import * as actionTypes from '../../../store/actions/actionTypes'; + class NewTodo extends Component { state = { title: '', content: '', - submitted: false } postTodoHandler = () => { - const data = - { title: this.state.title, content: this.state.content } - alert('submitted' + data.title); - // this.props.history.push('/todos'); - this.props.history.goBack(); - this.setState({ submitted: true }); + this.props.onStoreTodo(this.state.title, this.state.content); + this.props.history.push('/todos'); } render() { - let redirect = null; - if (this.state.submitted) { - redirect = - } return (

Add a New Todo!

@@ -36,7 +30,7 @@ class NewTodo extends Component { > @@ -45,4 +39,11 @@ class NewTodo extends Component { } } -export default NewTodo; \ No newline at end of file +const mapDispatchToProps = dispatch => { + return { + onStoreTodo: (title, content) => + dispatch({ type: actionTypes.ADD_TODO, title: title, content: content }), + } +}; + +export default connect(null, mapDispatchToProps)(NewTodo); \ No newline at end of file diff --git a/src/containers/TodoList/RealDetail/RealDetail.js b/src/containers/TodoList/RealDetail/RealDetail.js index 89500a3..36d2c97 100644 --- a/src/containers/TodoList/RealDetail/RealDetail.js +++ b/src/containers/TodoList/RealDetail/RealDetail.js @@ -2,8 +2,22 @@ import React, { Component } from 'react'; import './RealDetail.css'; +import { connect } from 'react-redux'; +import * as actionTypes from '../../../store/actions/actionTypes'; + class RealDetail extends Component { + + componentDidMount() { + this.props.onGetTodo(this.props.match.params.id); + } + render() { + let title = ''; + let content = ''; + if (this.props.selectedTodo) { + title = this.props.selectedTodo.title; + content = this.props.selectedTodo.content; + } return (
@@ -11,6 +25,7 @@ class RealDetail extends Component { Name:
+ {title}
@@ -18,6 +33,7 @@ class RealDetail extends Component { Content:
+ {content}
@@ -25,4 +41,17 @@ class RealDetail extends Component { } }; -export default RealDetail; \ No newline at end of file +const mapStateToProps = state => { + return { + selectedTodo: state.td.selectedTodo, + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onGetTodo: id => + dispatch({ type: actionTypes.GET_TODO, targetID: id }), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(RealDetail); \ No newline at end of file diff --git a/src/containers/TodoList/TodoList.js b/src/containers/TodoList/TodoList.js index 6cb72c1..331335d 100644 --- a/src/containers/TodoList/TodoList.js +++ b/src/containers/TodoList/TodoList.js @@ -7,6 +7,10 @@ import { NavLink } from 'react-router-dom'; import './TodoList.css'; +import { connect } from 'react-redux'; +import { withRouter } from 'react-router'; +import * as actionTypes from '../../store/actions/actionTypes'; + class TodoList extends Component { state = { todos: [ @@ -18,21 +22,19 @@ class TodoList extends Component { } clickTodoHandler = (td) => { - if (this.state.selectedTodo === td) { - this.setState({ ...this.state, selectedTodo: null }); - } else { - this.setState({ ...this.state, selectedTodo: td }); - } + this.props.history.push(this.props.match.url + '/' + td.id); } render() { - const todos = this.state.todos.map(td => { + const todos = this.props.storedTodos.map(td => { return ( this.clickTodoHandler(td)} + clickDetail={() => this.clickTodoHandler(td)} + clickDone={() => this.props.onToggleTodo(td.id)} + clickDelete={() => this.props.onDeleteTodo(td.id)} /> ); }); @@ -59,4 +61,19 @@ class TodoList extends Component { } } -export default TodoList; \ No newline at end of file +const mapStateToProps = state => { + return { + storedTodos: state.td.todos, + }; +} + +const mapDispatchToProps = dispatch => { + return { + onToggleTodo: (id) => + dispatch({ type: actionTypes.TOGGLE_DONE, targetID: id }), + onDeleteTodo: (id) => + dispatch({ type: actionTypes.DELETE_TODO, targetID: id }), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(TodoList)); \ No newline at end of file diff --git a/src/index.js b/src/index.js index 87d1be5..271efaf 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,23 @@ import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; -ReactDOM.render(, document.getElementById('root')); +import { Provider } from 'react-redux'; + +import { createStore, combineReducers } from 'redux'; + +import todoReducer from './store/reducers/todo'; + +const rootReducer = combineReducers({ + td: todoReducer, +}); + +const store = createStore(rootReducer); + +ReactDOM.render( + + + , + document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. diff --git a/src/store/actions/actionTypes.js b/src/store/actions/actionTypes.js new file mode 100644 index 0000000..eef36b5 --- /dev/null +++ b/src/store/actions/actionTypes.js @@ -0,0 +1,5 @@ +export const GET_ALL = 'GET_ALL'; +export const ADD_TODO = 'ADD_TODO'; +export const GET_TODO = 'GET_TODO'; +export const TOGGLE_DONE = 'TOGGLE_DONE'; +export const DELETE_TODO = 'DELETE_TODO'; \ No newline at end of file diff --git a/src/store/reducers/todo.js b/src/store/reducers/todo.js new file mode 100644 index 0000000..25d772f --- /dev/null +++ b/src/store/reducers/todo.js @@ -0,0 +1,45 @@ +import * as actionTypes from '../actions/actionTypes'; + +const initialState = { + todos: [ + { id: 1, title: 'SWPP', content: 'take swpp class', done: true }, + { id: 2, title: 'Movie', content: 'watch movie', done: false }, + { id: 3, title: 'Dinner', content: 'eat dinner', done: false } + ], + selectedTodo: null, +}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case actionTypes.ADD_TODO: + const newTodo = { + id: state.todos.length + 1, + title: action.title, + content: action.content, + done: false + }; + return { ...state, todos: state.todos.concat(newTodo) }; + case actionTypes.DELETE_TODO: + const deletedTodos = state.todos.filter((todo) => { + return todo.id !== action.targetID; + }); + return { ...state, todos: deletedTodos }; + case actionTypes.TOGGLE_DONE: + const modified = state.todos.map((todo) => { + if (todo.id === action.targetID) { + return { ...todo, done: !todo.done }; + } else { + return { ...todo }; + } + }); + return { ...state, todos: modified }; + case actionTypes.GET_TODO: + const target = { ...state.todos[action.targetID - 1] }; + return { ...state, selectedTodo: target }; + default: + break; + } + return state; +}; + +export default reducer; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 54cccc1..d988d7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -770,6 +770,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.5.5": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.2.tgz#c3d6e41b304ef10dcf13777a33e7694ec4a9a6dd" + integrity sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -4422,7 +4429,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0: +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== @@ -7939,11 +7946,23 @@ react-error-overlay@^6.0.1: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.1.tgz#b8d3cf9bb991c02883225c48044cb3ee20413e0f" integrity sha512-V9yoTr6MeZXPPd4nV/05eCBvGH9cGzc52FN8fs0O0TVQ3HYYf1n7EgZVtHbldRq5xU9zEzoXIITjYNIfxDDdUw== -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0: version "16.9.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb" integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw== +react-redux@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.1.tgz#ce6eee1b734a7a76e0788b3309bf78ff6b34fa0a" + integrity sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + invariant "^2.2.4" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + react-router-dom@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.1.tgz#ee66f4a5d18b6089c361958e443489d6bab714be" @@ -8122,6 +8141,14 @@ recursive-readdir@2.2.2: dependencies: minimatch "3.0.4" +redux@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" + integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" @@ -9115,6 +9142,11 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"