mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
got add, remove, and edit actions working
This commit is contained in:
757
package-lock.json
generated
757
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -4,19 +4,27 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --mode development --progress",
|
"start:client": "webpack-dev-server --mode development --progress",
|
||||||
"build": "webpack --mode production",
|
"build": "webpack --mode production",
|
||||||
"test": "jest --watch"
|
"test": "jest --watch",
|
||||||
|
"start:server": "nodemon server/index.js",
|
||||||
|
"start": "run-p start:server start:client"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.16.1",
|
||||||
|
"@types/body-parser": "^1.17.0",
|
||||||
|
"@types/node": "~10.12.21",
|
||||||
"@types/jest": "^23.3.13",
|
"@types/jest": "^23.3.13",
|
||||||
"@types/react": "^16.7.20",
|
"@types/react": "^16.7.20",
|
||||||
"@types/react-dom": "^16.0.11",
|
"@types/react-dom": "^16.0.11",
|
||||||
"@types/react-redux": "^7.0.0",
|
"@types/react-redux": "^7.0.0",
|
||||||
"@types/redux": "^3.6.0",
|
"@types/redux": "^3.6.0",
|
||||||
|
"@types/cors": "^2.8.4",
|
||||||
|
"body-parser": "^1.18.3",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"jest": "^23.6.0",
|
"jest": "^23.6.0",
|
||||||
"ts-jest": "^23.10.5",
|
"ts-jest": "^23.10.5",
|
||||||
@@ -24,15 +32,19 @@
|
|||||||
"typescript": "^3.2.4",
|
"typescript": "^3.2.4",
|
||||||
"webpack": "^4.28.4",
|
"webpack": "^4.28.4",
|
||||||
"webpack-cli": "^3.2.1",
|
"webpack-cli": "^3.2.1",
|
||||||
"webpack-dev-server": "^3.1.14"
|
"webpack-dev-server": "^3.1.14",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"nodemon": "^1.18.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"office-ui-fabric-react": "^6.128.0",
|
|
||||||
"@uifabric/experiments": "^6.51.1",
|
"@uifabric/experiments": "^6.51.1",
|
||||||
|
"express": "^4.16.4",
|
||||||
|
"immer": "^1.12.1",
|
||||||
|
"office-ui-fabric-react": "^6.128.0",
|
||||||
"react": "^16.7.0",
|
"react": "^16.7.0",
|
||||||
"react-dom": "^16.7.0",
|
"react-dom": "^16.7.0",
|
||||||
"redux": "^4.0.1",
|
|
||||||
"react-redux": "^6.0.0",
|
"react-redux": "^6.0.0",
|
||||||
"immer": "^1.12.1"
|
"redux-thunk": "^2.3.0",
|
||||||
|
"redux": "^4.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { action, GenericActionTypes, GenericAction, GenericActionLookup } from '../redux-utils/action';
|
import { action, GenericActionTypes, GenericAction, GenericActionLookup } from '../redux-utils/action';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import * as todosService from '../service/todosService';
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
add: (label: string) => action('add', { label }),
|
add: (label: string) => action('add', { id: String(counter++), label }),
|
||||||
remove: (id: string) => action('remove', { id }),
|
remove: (id: string) => action('remove', { id }),
|
||||||
edit: (id: string, label: string) => action('edit', { id, label }),
|
edit: (id: string, label: string) => action('edit', { id, label }),
|
||||||
complete: (id: string) => action('complete', { id }),
|
complete: (id: string) => action('complete', { id }),
|
||||||
@@ -9,6 +14,46 @@ export const actions = {
|
|||||||
filter: (filterTypes: string) => action('filter', { filter: filterTypes })
|
filter: (filterTypes: string) => action('filter', { filter: filterTypes })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const actionsWithService = {
|
||||||
|
add: (label: string) => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
const addAction = actions.add(label);
|
||||||
|
const id = addAction.id;
|
||||||
|
dispatch(addAction);
|
||||||
|
await todosService.add(id, getState().todos[id]);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
edit: (id: string, label: string) => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
dispatch(actions.edit(id, label));
|
||||||
|
await todosService.edit(id, getState().todos[id]);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: (id: string) => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
dispatch(actions.remove(id));
|
||||||
|
await todosService.remove(id);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: (id: string) => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
dispatch(actions.complete(id));
|
||||||
|
await todosService.edit(id, getState().todos[id]);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: () => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
dispatch(actions.clear());
|
||||||
|
await todosService.editBulk(getState().todos);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export type ActionTypes = GenericActionTypes<typeof actions>;
|
export type ActionTypes = GenericActionTypes<typeof actions>;
|
||||||
export type TodoAction = GenericAction<typeof actions>;
|
export type TodoAction = GenericAction<typeof actions>;
|
||||||
|
export type TodoActionWithService = GenericAction<typeof actionsWithService>;
|
||||||
export type TodoActionLookup = GenericActionLookup<typeof actions>;
|
export type TodoActionLookup = GenericActionLookup<typeof actions>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { actions, TodoAction } from '../actions';
|
import { actions, actionsWithService } from '../actions';
|
||||||
import { Store, FilterTypes } from '../store';
|
import { Store, FilterTypes } from '../store';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
@@ -11,12 +11,12 @@ export function mapStateToProps({ todos, filter }: Store) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapDispatchToProps(dispatch: Dispatch<TodoAction>) {
|
export function mapDispatchToProps(dispatch: any) {
|
||||||
return {
|
return {
|
||||||
add: (label: string) => dispatch(actions.add(label)),
|
add: (label: string) => dispatch(actionsWithService.add(label)),
|
||||||
remove: (id: string) => dispatch(actions.remove(id)),
|
remove: (id: string) => dispatch(actionsWithService.remove(id)),
|
||||||
complete: (id: string) => dispatch(actions.complete(id)),
|
complete: (id: string) => dispatch(actionsWithService.complete(id)),
|
||||||
edit: (id: string, label: string) => dispatch(actions.edit(id, label)),
|
edit: (id: string, label: string) => dispatch(actionsWithService.edit(id, label)),
|
||||||
clear: () => dispatch(actions.clear()),
|
clear: () => dispatch(actions.clear()),
|
||||||
setFilter: (filter: FilterTypes) => dispatch(actions.filter(filter))
|
setFilter: (filter: FilterTypes) => dispatch(actions.filter(filter))
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,36 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { createStore } from 'redux';
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { reducer } from './reducers';
|
import { reducer } from './reducers';
|
||||||
import { TodoAppContainer } from './components/TodoAppContainer';
|
import { TodoAppContainer } from './components/TodoAppContainer';
|
||||||
import { initializeIcons } from '@uifabric/icons';
|
import { initializeIcons } from '@uifabric/icons';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import * as todosService from './service/todosService';
|
||||||
|
import { FilterTypes } from './store';
|
||||||
|
|
||||||
declare var window: any;
|
declare var window: any;
|
||||||
|
|
||||||
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
||||||
|
// For preloading store
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
ReactDOM.render(
|
|
||||||
<Provider store={store}>
|
(async () => {
|
||||||
<TodoAppContainer />
|
const preloadStore = {
|
||||||
</Provider>,
|
todos: await todosService.getAll(),
|
||||||
document.getElementById('app')
|
filter: 'all' as FilterTypes
|
||||||
);
|
};
|
||||||
|
|
||||||
|
const store = createStore(reducer, preloadStore, composeEnhancers(applyMiddleware(thunk)));
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<TodoAppContainer />
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('app')
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
// For Synchronous Case
|
||||||
|
// const store = createStore(reducer, { todos: {}, filter: 'all' }, composeEnhancers(applyMiddleware(thunk)));
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ import { createReducer } from './createReducer';
|
|||||||
import { Store, FilterTypes } from '../store';
|
import { Store, FilterTypes } from '../store';
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
export const reducer = combineReducers<Store>({
|
export const reducer = combineReducers<Store>({
|
||||||
todos: createReducer<Store['todos']>(
|
todos: createReducer<Store['todos']>(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
add(draft, action) {
|
add(draft, action) {
|
||||||
const id = String(counter++);
|
draft[action.id] = { label: action.label, completed: false };
|
||||||
draft[id] = { label: action.label, completed: false };
|
|
||||||
return draft;
|
return draft;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
48
playground/src/service/todosService.ts
Normal file
48
playground/src/service/todosService.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { TodoItem, Store } from '../store';
|
||||||
|
const HOST = 'http://localhost:3000';
|
||||||
|
|
||||||
|
export async function add(id: string, todo: TodoItem) {
|
||||||
|
const response = await fetch(`${HOST}/todos/${id}`, {
|
||||||
|
method: 'post',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(todo)
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function edit(id: string, todo: TodoItem) {
|
||||||
|
const response = await fetch(`${HOST}/todos/${id}`, {
|
||||||
|
method: 'put',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(todo)
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(id: string) {
|
||||||
|
const response = await fetch(`${HOST}/todos/${id}`, {
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAll() {
|
||||||
|
const response = await fetch(`${HOST}/todos`, {
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editBulk(todos: Store['todos']) {
|
||||||
|
const response = await fetch(`${HOST}/todos`, {
|
||||||
|
method: 'post',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(todos)
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
38
server/index.js
Normal file
38
server/index.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// @ts-check
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const cors = require('cors');
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const store = {
|
||||||
|
/** @type {any} */
|
||||||
|
todos: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
app.get('/todos', (req, res) => {
|
||||||
|
res.json(store.todos);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put('/todos/:id', (req, res) => {
|
||||||
|
store.todos[req.params.id] = req.body;
|
||||||
|
res.json('ok');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/todos/:id', (req, res) => {
|
||||||
|
store.todos[req.params.id] = req.body;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/todos/:id', (req, res) => {
|
||||||
|
delete store.todos[req.body.id];
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/todos', (req, res) => {
|
||||||
|
store.todos = req.body;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000, () => {
|
||||||
|
console.log('Listening at http://localhost:3000');
|
||||||
|
});
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"allowJs": true
|
"allowJs": true,
|
||||||
|
"lib": ["es2015", "dom"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user