diff --git a/redux-basic/redux-basics.js b/redux-basic/redux-basics.js new file mode 100644 index 0000000..6f8a16f --- /dev/null +++ b/redux-basic/redux-basics.js @@ -0,0 +1,25 @@ +const redux = require('redux'); // load module in Node.js +const createStore = redux.createStore; +const initialState = { number: 0 }; // default state + +// create identity reducer +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 + }); + } + return state; +} + +const store = createStore(reducer); +store.subscribe(() => { + console.log('[Subscription]', store.getState()); +}); + +store.dispatch({ type: 'ADD' }); +console.log(store.getState()); +store.dispatch({ type: 'ADD_VALUE', value: 5 }); +console.log(store.getState()); \ No newline at end of file diff --git a/src/components/Todo/Todo.js b/src/components/Todo/Todo.js index 9c80927..a9625cf 100644 --- a/src/components/Todo/Todo.js +++ b/src/components/Todo/Todo.js @@ -7,11 +7,12 @@ const Todo = (props) => { return (
+ className={`text ${props.done && 'done'}`} 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..3da3299 100644 --- a/src/containers/TodoList/NewTodo/NewTodo.js +++ b/src/containers/TodoList/NewTodo/NewTodo.js @@ -1,5 +1,7 @@ import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import * as actionTypes from '../../../store/actions/actionTypes'; import { Redirect } from 'react-router-dom'; import './NewTodo.css'; @@ -14,6 +16,8 @@ class NewTodo extends Component { postTodoHandler = () => { const data = { title: this.state.title, content: this.state.content } + this.props.onStoreTodo(this.state.title, this.state.content); + this.setState({ submitted: true }); alert('submitted' + data.title); // this.props.history.push('/todos'); this.props.history.goBack(); @@ -36,7 +40,7 @@ class NewTodo extends Component { > @@ -45,4 +49,10 @@ 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..c6e7159 100644 --- a/src/containers/TodoList/RealDetail/RealDetail.js +++ b/src/containers/TodoList/RealDetail/RealDetail.js @@ -1,23 +1,34 @@ import React, { Component } from 'react'; - +import { connect } from 'react-redux'; import './RealDetail.css'; +import * as actionTypes from '../../../store/actions/actionTypes'; class RealDetail extends Component { + componentDidMount() { + this.props.onGetTodo(parseInt(this.props.match.params.id)); + } render() { + let content = '', title = ''; + if (this.props.selectedTodo) { + title = this.props.selectedTodo.title; + content = this.props.selectedTodo.content; + } return (
- Name: + Name:
+ {this.props.selectedTodo.title}
- Content: + Content:
+ {this.props.selectedTodo.content}
@@ -25,4 +36,19 @@ 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..af0322c 100644 --- a/src/containers/TodoList/TodoList.js +++ b/src/containers/TodoList/TodoList.js @@ -1,13 +1,21 @@ import React, { Component } from 'react'; - +import axios from 'axios'; import Todo from '../../components/Todo/Todo'; import TodoDetail from '../../components/TodoDetail/TodoDetail'; +import { withRouter } from 'react-router'; +import { connect } from 'react-redux'; import { NavLink } from 'react-router-dom'; +import * as actionTypes from '../../store/actions/actionTypes'; import './TodoList.css'; class TodoList extends Component { + componentDidMount() { + axios.get("/api/todo") + .then(result => console.log(result.data)) + .catch(error => console.error(`Oops! Error occurred!! ${error}`)) + } state = { todos: [ { id: 1, title: 'SWPP', content: 'take swpp class', done: true }, @@ -18,21 +26,20 @@ 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('/todos/' + 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 +66,18 @@ 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 }), + onGetAll: () => dispatch(actionCreators.getTodos()), + }; +}; // don’t forget import * as actionTypes from ‘../../store/actions/actionTypes’; + +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..05f475e 100644 --- a/src/index.js +++ b/src/index.js @@ -3,8 +3,22 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; +import thunk from 'redux-thunk'; +import { applyMiddleware } from 'redux'; -ReactDOM.render(, document.getElementById('root')); +// At index.js of project root +import { Provider } from 'react-redux'; +import { createStore, comebineReducers, applyMiddleware } from 'redux'; +import todoReducer from './store/reducers/todo'; + +const rootReducer = combineReducers({ + td: todoReducer, +}); +const store = createStore(rootReducer, applyMiddleware()); + + +const store = createStore((state = {}, action) => state); // TODO +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..b954b80 --- /dev/null +++ b/src/store/actions/actionTypes.js @@ -0,0 +1,5 @@ +export const GET_ALL = 'GET_ALL'; +export const GET_TODO = 'GET_TODO'; +export const TOGGLE_DONE = 'TOGGLE_DONE'; +export const DELETE_TODO = 'DELETE_TODO'; +export const ADD_TODO = 'ADD_TODO'; \ No newline at end of file diff --git a/src/store/actions/index.js b/src/store/actions/index.js new file mode 100644 index 0000000..3edbbea --- /dev/null +++ b/src/store/actions/index.js @@ -0,0 +1 @@ +export { getTodos } from './todo'; \ No newline at end of file diff --git a/src/store/actions/todo.js b/src/store/actions/todo.js new file mode 100644 index 0000000..ad7e286 --- /dev/null +++ b/src/store/actions/todo.js @@ -0,0 +1,14 @@ +import * as actionTypes from './actionTypes'; +import axios from 'axios'; + +export const getTodos_ = (todos) => { + return { type: actionTypes.GET_ALL, todos: todos }; +}; + +export const getTodos = () => { // Why no argument? We’ll see later. + return dispatch => { + return axios + .get('/api/todo') + .then(res => dispatch(getTodos_(res.data))); + } +} diff --git a/src/store/reducers/todo.js b/src/store/reducers/todo.js new file mode 100644 index 0000000..339be54 --- /dev/null +++ b/src/store/reducers/todo.js @@ -0,0 +1,45 @@ +import { ADD_TODO, DELETE_TODO, TOGGLE_DONE, GET_TODO } 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) { + // we will handle actions via switch statement + case ADD_TODO: + // as React, do not mutate state directly, make new object + const newTodo = { + id: state.todos.length + 1, // temporary + title: action.title, content: action.content, done:false + } + return {...state, todos: state.todos.concat(newTodo)}; + default: + return state; + case DELETE_TODO: + const deleted = state.todos.filter((todo) => { + return todo.id !== action.targetID; + }); + return { ...state, todos: deleted }; + case 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 GET_TODO: + const target = {...state.todos[action.targetID - 1]}; // temporary + return { ...state, selectedTodo: target }; + + return state; + case GET_ALL: + return {...state, todos: action.todos }; + } + } +export default reducer; \ No newline at end of file