4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
26 | React App
27 |
28 |
29 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | @keyframes App-logo-spin {
26 | from { transform: rotate(0deg); }
27 | to { transform: rotate(360deg); }
28 | }
29 |
30 |
31 | .option-buttons{
32 | flex: 1;
33 | }
34 |
35 |
36 | .updating{
37 | background: rgb(0, 172, 172);
38 | }
39 |
40 | .done{
41 | background: rgb(69, 226, 69);
42 | }
43 | .deleting{
44 | background: rgb(235, 122, 73);
45 | }
46 |
47 | tr{
48 | transition: 800ms cubic-bezier(0, 0, 0, 1.43);
49 | }
50 |
51 | td.options{
52 | display: flex;
53 | flex-direction: row;
54 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import logo from './logo.svg';
3 |
4 | //Import the modified App.css
5 | import './App.css';
6 |
7 | // Import the Routes component, which contains our Route setup
8 |
9 | import { Routes } from './Routes'
10 |
11 |
12 | // Provider component is a react-redux component for setting up the Redux Store
13 |
14 | import { Provider } from 'react-redux'
15 |
16 | // Import the ConfigureStore that holds the initial Configuration
17 |
18 | import { configureStore } from './store/configureStore'
19 |
20 | import * as TodoActions from './todos/actions/todoActions'
21 |
22 | import AppBar from 'material-ui/AppBar';
23 |
24 |
25 | // Create a Store from the Configuration, we can pass a Initial State here
26 |
27 | const store = configureStore()
28 |
29 | // At first dispatch a Get Todos Actions, So we'll recieve the Todos
30 | // fetched from the server at the start of the app
31 |
32 | store.dispatch(TodoActions.GetTodos())
33 |
34 | const App = (props) => {
35 | return (
36 |
37 | //Provider needs to contain all the Containers/Components it will give access to the Store
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default App;
46 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/src/Routes.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { BrowserRouter, Switch, Route, Link } from 'react-router-dom'
3 |
4 | // The Todo Container Component
5 |
6 | import TodoContainer from './todos/containers/todoContainer'
7 |
8 |
9 | // The Routing Component providing all the routing Configuration
10 |
11 | const Routes = (props) => {
12 | return (
13 |
14 |
15 |
16 | {/* It's setup at the default index route */}
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | export { Routes }
--------------------------------------------------------------------------------
/src/api/httpClient.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 |
4 | //Create a Http Client using Axios. Further modifications in this layer can be done later like Authorization.
5 |
6 | const post = (url, data, config = {}) => {
7 | return axios.post(url, data, config)
8 | }
9 |
10 | const get = (url) => {
11 | return axios(url)
12 | }
13 |
14 | const put = (url, data, config = {}) => {
15 | return axios.put(url, data, config)
16 | }
17 |
18 | //Cannot contain a delete method - Cause delete is a keyword.
19 |
20 | const del = (url, config = {}) => {
21 | return axios.delete(url, config)
22 | }
23 |
24 | //Encapsulating in a JSON object
25 |
26 | const HttpClient = {
27 | post,
28 | get,
29 | put,
30 | delete: del
31 | }
32 |
33 | export {HttpClient}
--------------------------------------------------------------------------------
/src/api/todoApi.js:
--------------------------------------------------------------------------------
1 | import {HttpClient} from './httpClient'
2 |
3 | // This is the API. The backend root URL can be set from here.
4 |
5 | const API = 'http://localhost:3000/api'
6 |
7 | //Setting the todos URI
8 |
9 | const TODO_API = `${API}/todos`
10 |
11 | // The CRUD Operations of the Todo Resource.
12 |
13 |
14 | //Create
15 | const createTodo = todo => {
16 | return HttpClient.post(TODO_API, todo)
17 | }
18 |
19 | //Read
20 | const getTodo = () => {
21 | return HttpClient.get(TODO_API)
22 | }
23 |
24 | //Update
25 | const updateTodo = todo => {
26 | return HttpClient.put(TODO_API, todo)
27 | }
28 |
29 | //Delete
30 | const removeTodo = todo => {
31 | return HttpClient.delete(`${TODO_API}/${todo._id}`)
32 | }
33 |
34 |
35 | //Encapsulating in a JSON object
36 |
37 | const TodoApi = {createTodo, getTodo, updateTodo, removeTodo}
38 |
39 | export {TodoApi}
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | import 'semantic-ui-css/semantic.min.css';
8 | import 'font-awesome/css/font-awesome.css'
9 |
10 | ReactDOM.render(, document.getElementById('root'));
11 |
12 | registerServiceWorker();
13 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/reducers/rootReducer.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from 'redux'
2 | import {TodoListReducer} from '../todos/reducers/todoReducer'
3 |
4 |
5 | //One root reducer for the whole app. This is done so that the app will have one single reducer to manage lots of other resources.
6 | // And also communication between the reducers will be easier to maintain.
7 |
8 | const rootReducer = combineReducers({
9 | todos: TodoListReducer
10 | })
11 |
12 | export default rootReducer
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (!isLocalhost) {
36 | // Is not local host. Just register service worker
37 | registerValidSW(swUrl);
38 | } else {
39 | // This is running on localhost. Lets check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux'
2 |
3 | //Redux Thunk need to be added as a middleware
4 |
5 | import thunkMiddleware from 'redux-thunk'
6 |
7 | // Redux logging middleware
8 | import { createLogger } from 'redux-logger'
9 |
10 | // Import the root reducer
11 | import rootReducer from '../reducers/rootReducer'
12 |
13 |
14 | // Create the redux logging middleware
15 | const loggerMiddleware = createLogger()
16 |
17 |
18 | // Configuring the Store. PreloadState is the initial State.
19 | export function configureStore(preloadedState) {
20 |
21 | return createStore(
22 | rootReducer,
23 | preloadedState,
24 |
25 | //Apply the middleware usign the Redux's provided applymiddleware function
26 |
27 | applyMiddleware(
28 | thunkMiddleware,
29 | loggerMiddleware
30 | )
31 | )
32 | }
--------------------------------------------------------------------------------
/src/todos/actions/todoActions.js:
--------------------------------------------------------------------------------
1 | //Import the Todo API
2 |
3 | import { TodoApi } from "../../api/todoApi";
4 |
5 |
6 | // These are the action type constants. Ordered by CRUD order.
7 | // There is a pattern of Action, Action_Success, Action_Error action types for the Async actions.
8 |
9 |
10 |
11 | //Create
12 | export const CREATE_TODO = '[Todo] CREATE_TODO'
13 | export const CREATE_TODO_SUCCESS = '[Todo] CREATE_TODO_SUCCESS'
14 | export const CREATE_TODO_ERROR = '[Todo] CREATE_TODO_ERROR'
15 |
16 |
17 | //Read
18 | export const GET_TODOS = '[Todo] GET_TODOS'
19 | export const GET_TODOS_SUCCESS = '[Todo] GET_TODOS_SUCCESS'
20 | export const GET_TODOS_ERROR = '[Todo] GET_TODOS_ERROR'
21 |
22 |
23 | //Update
24 | export const START_EDITING ='[Todo] START_EDITING'
25 | export const CANCEL_EDITING = '[Todo] CANCEL_EDITING'
26 |
27 | export const UPDATE_TODO = '[Todo] UPDATE_TODO'
28 | export const UPDATE_TODO_SUCCESS = '[Todo] UPDATE_TODO_SUCCESS'
29 | export const UPDATE_TODO_ERROR = '[Todo] UPDATE_TODO_ERROR'
30 |
31 | export const COMPLETE_TODO = 'COMPLETE_TODO'
32 |
33 |
34 | //Delete
35 | export const DELETE_TODO = '[Todo] DELETE_TODO'
36 | export const DELETE_TODO_SUCCESS = '[Todo] DELETE_TODO_SUCCESS'
37 | export const DELETE_TODO_ERROR = '[Todo] DELETE_TODO_ERROR'
38 |
39 |
40 |
41 |
42 | //These are the action types Also ordered in CRUD Order.
43 |
44 | //Create
45 |
46 | //The dispatch and getstate function is provided by the Redux-Thunk middleware, we can dispatch actions with it.
47 |
48 | export function CreateTodo(todo){
49 | return (dispatch, getState) => {
50 | return TodoApi.createTodo(todo).then(res => {
51 | dispatch(CreateTodoSuccess(res.data.data))
52 | })
53 | }
54 | }
55 |
56 | export function CreateTodoSuccess(todo){
57 | return {
58 | type:CREATE_TODO_SUCCESS,
59 | todo
60 | }
61 | }
62 |
63 |
64 | //Read
65 | export function GetTodos(){
66 | return (dispactch, getState) => {
67 | return TodoApi.getTodo().then(res => {
68 | dispactch(GetTodoSuccess(res))
69 | })
70 | }
71 | }
72 |
73 | export function GetTodoSuccess(todos){
74 | return {
75 | type:GET_TODOS_SUCCESS,
76 | todos
77 | }
78 | }
79 |
80 |
81 | //Update
82 | export function StartEditing(_id) {
83 | return {
84 | type: START_EDITING,
85 | _id
86 | }
87 | }
88 | export function CancelEditing(_id) {
89 | return {
90 | type: CANCEL_EDITING,
91 | _id
92 | }
93 | }
94 |
95 | export function UpdateTodo(todo) {
96 | return (dispatch, getState) => {
97 |
98 | //Multiple actions can be sent usign the Redux-Thunk middleware
99 |
100 | dispatch({
101 | type: UPDATE_TODO,
102 | todo
103 | })
104 | TodoApi.updateTodo(todo).then(res => {
105 | dispatch(UpdateTodoSuccess(res.data.data))
106 | })
107 | }
108 | }
109 | export function UpdateTodoSuccess(todo) {
110 | return {
111 | type: UPDATE_TODO_SUCCESS,
112 | todo,
113 | _id: todo._id
114 | }
115 | }
116 |
117 |
118 | //Delete
119 | export function DeleteTodo(todo) {
120 | return (dispatch, getState) => {
121 | dispatch({
122 | type: DELETE_TODO,
123 | todo
124 | })
125 | TodoApi.removeTodo(todo).then(res => {
126 | if (res.status == 204) {
127 | dispatch(DeleteTodoSuccess(todo))
128 | }
129 | })
130 | }
131 | }
132 | export function DeleteTodoSuccess(todo) {
133 | return {
134 | type: DELETE_TODO_SUCCESS,
135 | todo,
136 | _id: todo._id
137 | }
138 | }
--------------------------------------------------------------------------------
/src/todos/components/editTodo.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 |
3 | import {Button, Icon, Label, Menu, Table} from 'semantic-ui-react'
4 | import {Input} from 'semantic-ui-react'
5 |
6 | //Import moment library for React Datepicker
7 |
8 | import moment from 'moment';
9 |
10 | import DatePicker from 'react-datepicker';
11 | import 'react-datepicker/dist/react-datepicker.css';
12 |
13 | class EditTodo extends Component {
14 |
15 | constructor(props) {
16 | super(props);
17 | // If props.todo exists this component is used to Edit a Todo,
18 | // else this is a Create New Todo Component
19 |
20 | if (this.props.todo) {
21 | this.state = {
22 | ...this.props.todo
23 | }
24 | } else {
25 | this.state = {
26 | ...this.emptyTodo()
27 | }
28 | }
29 | }
30 |
31 | //Initializes a Empty Todo Object
32 |
33 | emptyTodo = () => {
34 | return {title: "", description: "", date: moment()}
35 | }
36 |
37 |
38 | // Input change handling methods
39 |
40 | changeNewTitle = (event) => {
41 | this.setState({title: event.target.value})
42 | }
43 |
44 | changeNewDescription = (event) => {
45 | this.setState({description: event.target.value})
46 | }
47 |
48 | changeNewDate = (event) => {
49 | this.setState({date: event})
50 | }
51 |
52 | // Form submission methods
53 |
54 | createTodo = (event) => {
55 | this.resetTodo()
56 | this.props.createTodo(this.state)
57 | }
58 | editTodo = (event) => {
59 | this.props.editTodo(this.state)
60 | }
61 |
62 |
63 | // Modifying the inputs indirectly methods
64 |
65 | resetTodo = () => {
66 | this.setState({title: "", description: "", date: moment()})
67 | }
68 | cancelEditing = () => {
69 | this.props.cancelEditing();
70 | }
71 |
72 | // Convert the date to moment object for the React DatePicker
73 |
74 | getDateForDatePicker() {
75 | return moment(this.state.date)
76 | }
77 |
78 | render() {
79 | return (
80 |
81 |
82 |
83 |
84 | {/* The Value flows the data from the state to the control */}
85 | {/* The onChange method pass the value from the Control to the State, It takes a method reference */}
86 | {/* In this way a controlled two way binding is established */}
87 |
88 |
92 |
93 |
94 |
95 |
99 |
100 |
101 |
102 |
103 | {/* React Datepicker gets the moment date from the class method */}
104 |
105 |
108 |
109 |
110 | {/* The options component takes the inputs and decide if It's an option for a Edit Todo or Add New Todo */}
111 |
112 |
119 |
120 |
121 | )
122 | }
123 | }
124 |
125 | export default EditTodo;
126 |
127 |
128 | // The option component decides the component usage
129 |
130 | const Options = (props) => {
131 | if (props.todo && props.todo.editing) {
132 | return EditOptions(props);
133 | } else {
134 | return AddOptions(props);
135 | }
136 | }
137 |
138 | // The two local components - EditOptions and AddOptions simply maps their events
139 | // to the state events of their parent compoent through the props
140 |
141 |
142 | const EditOptions = (props) => {
143 | return (
144 |
145 |
148 | < Button color='blue' onClick={props.cancelEdit}>
149 | Cancel
150 |
151 |
152 | );
153 | }
154 |
155 | const AddOptions = (props) => {
156 | return (
157 |
158 |
161 | < Button color='blue' onClick={props.resetTodo}>
162 | Reset
163 |
164 |
165 | );
166 | }
167 |
168 |
--------------------------------------------------------------------------------
/src/todos/components/todoRow.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 |
3 | import {Button, Table} from 'semantic-ui-react'
4 |
5 | // The Todo Row component is a simple stateless component, It simply takes the props
6 | // and maps the specific events to the methods of parent component
7 |
8 | const TodoRow = (props) => {
9 | return (
10 |
11 | // getClass Name assigns the class names of this element
12 |
13 |
14 | {props.todo.title}
15 | {props.todo.description}
16 | {props.todo.date}
17 |
18 | {props.todo.status != 'done' && }
21 |
24 |
27 |
28 |
29 | );
30 | }
31 |
32 | // Right now Updating, done and deleting these three states are represented with different Class Name
33 |
34 | const getClassName = (props) => {
35 | return `
36 |
37 | ${props.todo.updating
38 | ? "updating"
39 | : ""}
40 | ${props.todo.status == 'done'
41 | ? "done"
42 | : ""}
43 | ${props.todo.deleting
44 | ? "deleting"
45 | : ""}
46 | `
47 | }
48 |
49 | export default TodoRow;
--------------------------------------------------------------------------------
/src/todos/components/todoTable.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 |
3 | import {Button, Icon, Label, Menu, Table} from 'semantic-ui-react'
4 | import TodoRow from './todoRow'
5 | import EditTodo from './editTodo'
6 |
7 |
8 | // TodoTable is a Stateless component
9 |
10 | const TodoTable = (props) => {
11 | return (
12 |
13 |
14 |
15 |
16 | Title
17 | Description
18 | Date
19 | Options
20 |
21 |
22 |
23 |
24 |
25 | {/* This maps the todos recieved as a prop */}
26 |
27 | {props
28 | .todos
29 | .map(t => {
30 |
31 | // If the todo is being edited, EditTodo Component is rendered here
32 |
33 | if (t.editing) {
34 | return props.cancelEditing(t._id)}
37 | key={t._id}
38 | todo={t}/>
39 | } else {
40 |
41 | // Is the todo is not being edited the TodoRow stateless component is returned
42 |
43 | return props.completeTodo(t)}
47 | startEditing={e => props.startEditing(t._id)}
48 | deleteTodo={e=> props.deleteTodo(t)}
49 | />
50 | }
51 | })}
52 |
53 | {/* This EditTodo component is used as a Create new Todo Component */}
54 | {/* Thus by using the same component for both use, we can reuse a lot of the codes */}
55 |
56 |
57 |
58 |
59 |
60 | )
61 | }
62 |
63 | export default TodoTable;
--------------------------------------------------------------------------------
/src/todos/containers/todoContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import * as TodoActions from '../actions/todoActions'
3 | import { bindActionCreators } from 'redux';
4 | import { connect } from 'react-redux'
5 | import { PropTypes } from 'prop-types'
6 | import TodoTable from '../components/todoTable';
7 |
8 |
9 |
10 | export class TodoContainer extends Component {
11 | constructor(props) {
12 | super(props)
13 | }
14 |
15 | // Todo Container methods dispatch the actions to the reducer functions. Ordered by CRUD Order
16 |
17 | //Create
18 | createTodo = (todo) => {
19 | this.props.actions.CreateTodo(todo)
20 | }
21 |
22 |
23 | // No methods for reading, the first loading of data is done in App.js where the
24 | // getTodo Action is dispatched
25 |
26 | //Update
27 | startEditing = (id) => {
28 | this.props.actions.StartEditing(id)
29 | }
30 | cancelEditing = (id) => {
31 | this.props.actions.CancelEditing(id)
32 | }
33 | editTodo = (todo) => {
34 | this.props.actions.UpdateTodo(todo)
35 | }
36 | completeTodo = (todo) => {
37 | this.props.actions.UpdateTodo({...todo, status: 'done'})
38 | }
39 |
40 | //Delete
41 | deleteTodo = (todo) => {
42 | this.props.actions.DeleteTodo(todo)
43 | }
44 |
45 | render() {
46 | return (
47 |
48 |
57 |
58 | );
59 | }
60 | }
61 |
62 | // Define the property types of this Container Component
63 |
64 | TodoContainer.propTypes = {
65 | actions: PropTypes.object.isRequired,
66 | todos: PropTypes.array.isRequired
67 | }
68 |
69 | // This maps the state to the property of the component
70 |
71 | function mapStateToProps(state, ownProps) {
72 | return {
73 | todos: state.todos
74 | }
75 | }
76 |
77 | // This maps the dispatch to the property of the component
78 |
79 | function mapDispatchToProps(dispatch) {
80 | return {
81 | actions: bindActionCreators(TodoActions, dispatch)
82 | }
83 | }
84 |
85 | // The connect function connects the Redux Dispatch and state to the Todo Container Component.
86 | // Without this the Component wont be functional.
87 |
88 | export default connect(mapStateToProps, mapDispatchToProps)(TodoContainer);
--------------------------------------------------------------------------------
/src/todos/reducers/todoReducer.js:
--------------------------------------------------------------------------------
1 | // Import the TodoAction Creators and TodoActionTypes
2 |
3 | import * as TodoActions from '../actions/todoActions'
4 |
5 |
6 |
7 | // We are dividing the reducers using a technique called Reducer composition.
8 | // By doing this we are seperating the reducer for the Collection and the Individual Item
9 |
10 |
11 | //The collection Reducer, It handles only the collection
12 |
13 | export function TodoListReducer(state = [], action) {
14 | switch (action.type) {
15 |
16 | // The cases ordered in CRUD order.
17 |
18 | // Create
19 | case TodoActions.CREATE_TODO_SUCCESS: {
20 | return [
21 | ...state,
22 | action.todo
23 | ];
24 | }
25 |
26 | //Read
27 | case TodoActions.GET_TODOS_SUCCESS: {
28 |
29 | return action.todos.data.data.docs;
30 |
31 | }
32 |
33 | // The following Cases handle the data by mapping it. Mostly because they are related with the modification of a single Data
34 |
35 | //Update
36 | case TodoActions.START_EDITING: {
37 |
38 | return state.map(s => todo(s, action))
39 |
40 | }
41 | case TodoActions.CANCEL_EDITING: {
42 |
43 | return state.map(s => todo(s, action))
44 |
45 | }
46 | case TodoActions.UPDATE_TODO: {
47 |
48 | return state.map(s => todo(s, action))
49 |
50 | }
51 | case TodoActions.UPDATE_TODO_SUCCESS: {
52 |
53 | return state.map(s => todo(s, action))
54 |
55 | }
56 |
57 | //Delete
58 | case TodoActions.DELETE_TODO: {
59 |
60 | return state.map(s => todo(s, action))
61 |
62 | }
63 | case TodoActions.DELETE_TODO_SUCCESS: {
64 |
65 | return state.filter(s => todo(s, action))
66 |
67 | }
68 |
69 | default:
70 | return state
71 | }
72 | }
73 |
74 |
75 | //The individual Reducer. It handles only one Todo Object.
76 |
77 |
78 | const todo = (state, action) => {
79 |
80 | // If the mapped todo of the previous state matches with the new ID of the action,
81 | // Only then proceed to the Reducer Switch case
82 |
83 | if (state._id != (action._id || action.todo._id)) {
84 | return state;
85 | }
86 |
87 | switch (action.type) {
88 |
89 | // Edit/modifies the individual Todos using ES6 spread operator. The cases are self explanatory.
90 |
91 | case TodoActions.START_EDITING:
92 | {
93 | return {
94 | ...state,
95 | editing: true
96 | }
97 | }
98 |
99 | case TodoActions.CANCEL_EDITING:
100 | {
101 | return {
102 | ...state,
103 | editing: false
104 | }
105 | }
106 |
107 | case TodoActions.UPDATE_TODO:
108 | {
109 | return {
110 | ...state,
111 | editing: false,
112 | updating: true
113 | }
114 | }
115 |
116 | case TodoActions.UPDATE_TODO_SUCCESS:
117 | {
118 | return {
119 | ...state,
120 | ...action.todo,
121 | updating: false
122 | }
123 | }
124 |
125 | case TodoActions.DELETE_TODO:
126 | {
127 | return {
128 | ...state,
129 | deleting: true
130 | }
131 | }
132 |
133 | case TodoActions.DELETE_TODO_SUCCESS:
134 | {
135 | return false
136 | }
137 |
138 | default:
139 | {
140 | return state;
141 | }
142 | }
143 | }
--------------------------------------------------------------------------------