diff --git a/06 SimpleApp_Navigation/package.json b/06 SimpleApp_Navigation/package.json
index 37d9845..a9d8fe9 100644
--- a/06 SimpleApp_Navigation/package.json
+++ b/06 SimpleApp_Navigation/package.json
@@ -10,36 +10,34 @@
"author": "",
"license": "ISC",
"devDependencies": {
- "@types/react": "^15.0.21",
- "@types/react-dom": "^0.14.23",
- "@types/react-redux": "^4.4.38",
- "@types/react-router": "^2.0.38",
- "@types/react-router-redux": "^4.0.34",
- "@types/redux-thunk": "^2.1.0",
- "awesome-typescript-loader": "^3.1.2",
- "babel-core": "^6.24.0",
- "babel-preset-env": "^1.3.2",
- "css-loader": "^0.27.3",
- "extract-text-webpack-plugin": "^2.1.0",
- "file-loader": "^0.10.1",
- "html-webpack-plugin": "^2.24.0",
- "style-loader": "^0.16.0",
- "ts-loader": "^2.0.3",
- "typescript": "^2.0.6",
- "url-loader": "^0.5.7",
- "webpack": "^2.3.2",
- "webpack-dev-server": "^2.4.2"
+ "@types/react": "^16.0.22",
+ "@types/react-dom": "^16.0.3",
+ "@types/react-redux": "^5.0.12",
+ "@types/react-router-dom": "^4.2.1",
+ "awesome-typescript-loader": "^3.3.0",
+ "babel-core": "^6.26.0",
+ "babel-preset-env": "^1.6.1",
+ "css-loader": "^0.28.7",
+ "extract-text-webpack-plugin": "^3.0.2",
+ "file-loader": "^1.1.5",
+ "html-webpack-plugin": "^2.30.1",
+ "node-sass": "^4.6.0",
+ "sass-loader": "^6.0.6",
+ "style-loader": "^0.19.0",
+ "ts-loader": "^3.1.1",
+ "typescript": "^2.6.1",
+ "url-loader": "^0.6.2",
+ "webpack": "^3.8.1",
+ "webpack-dev-server": "^2.9.4"
},
"dependencies": {
- "@types/core-js": "^0.9.41",
+ "babel-polyfill": "^6.26.0",
"bootstrap": "^3.3.7",
- "core-js": "^2.4.1",
- "react": "^15.4.2",
- "react-dom": "^15.4.2",
- "react-redux": "^5.0.3",
- "react-router": "^3.0.0",
- "react-router-redux": "^4.0.8",
- "redux": "^3.6.0",
+ "react": "^16.0.0",
+ "react-dom": "^16.0.0",
+ "react-redux": "^5.0.6",
+ "react-router-dom": "^4.2.2",
+ "redux": "^3.7.2",
"redux-thunk": "^2.2.0"
}
}
diff --git a/06 SimpleApp_Navigation/readme.md b/06 SimpleApp_Navigation/readme.md
index 7b9e510..0b38017 100644
--- a/06 SimpleApp_Navigation/readme.md
+++ b/06 SimpleApp_Navigation/readme.md
@@ -39,60 +39,232 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not alrea
- Let's make some cleanup:
- Remove the _./src/helloworld.tsx_ and _./src/helloworldContainer.tsx_.
- Remove the _./src/nameEdit.tsx_ and _./src/nameEditContainer.tsx_.
- - Remove the _./actions/updateUserProfileName.tsx plus _./actions_ folder.
+ - Remove the _./src/actions/updateUserProfileName.tsx plus _./actions_ folder.
+ - Remove _./src/reducers/userProfile.ts_
-- It's time to install routing libraries:
+- And update `reducers` file:
-> At the time of writing this tutorial, there react-router-redux was not uptodate
-with react-route (alfa and dev tool noy fully integrating, under development), we will stick to version
-3.0 of react-router. More info: https://github.com/ReactTraining/react-router/tree/master/packages/react-router-redux
+### ./src/reducers/index.ts
-```cmd
-npm install react-router@3.0.0 react-router-redux@4.0.8 --save
-```
+```diff
+import { combineReducers } from 'redux';
+- import { userProfileReducer } from './userProfile';
+export const reducers = combineReducers({
+- userProfileReducer
+});
-```
-npm install @types/react-router@3 @types/react-router-redux@4.0.34 --save-dev
```
-- Let's install support for promises:
+- It's time to install routing libraries:
-```
-npm install core-js --save
+```bash
+npm install react-router-dom --save
```
-```
-npm install @types/core-js --save
+```bash
+npm install @types/react-router-dom --save-dev
```
-- Let's install Redux-Thunk to handle async actions
+- Let's install support for promises:
+```bash
+npm install babel-polyfill --save
```
+
+- Let's install Redux-Thunk to handle async actions, it has own typings:
+
+```bash
npm install redux-thunk --save
```
+- Install `sass`:
+
+```bash
+npm install sass-loader node-sass --save-dev
```
-npm install @types/redux-thunk --save-dev
-```
-- Let's configure redux-thunk in _main.tsx_
+- Last step to configure libs is update `webpack.config`:
+
+### ./webpack.config.js
+
+```diff
+var path = require('path');
+var webpack = require('webpack');
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+var ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+var basePath = __dirname;
+
+module.exports = {
+ context: path.join(basePath, "src"),
+ resolve: {
+ extensions: ['.js', '.ts', '.tsx']
+ },
+
+- entry: [
+- './main.tsx',
+- '../node_modules/bootstrap/dist/css/bootstrap.css'
+- ],
++ entry: {
++ app: './main.tsx',
++ vendor: [
++ 'babel-polyfill',
++ 'react',
++ 'react-dom',
++ 'react-redux',
++ 'react-router-dom',
++ 'redux',
++ 'redux-thunk',
++ ],
++ vendorStyles: [
++ '../node_modules/bootstrap/dist/css/bootstrap.css'
++ ],
++ },
+ output: {
+ path: path.join(basePath, 'dist'),
+- filename: 'bundle.js'
++ filename: '[name].js',
+ },
+
+ devtool: 'source-map',
+
+ devServer: {
+ contentBase: './dist', // Content base
+ inline: true, // Enable watch and live reload
+ host: 'localhost',
+ port: 8080,
+ stats: 'errors-only'
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.(ts|tsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'awesome-typescript-loader',
+ options: {
+ useBabel: true,
+ },
+ },
+ },
++ {
++ test: /\.scss$/,
++ exclude: /node_modules/,
++ loader: ExtractTextPlugin.extract({
++ fallback: 'style-loader',
++ use: [
++ {
++ loader: 'css-loader',
++ options: {
++ modules: true,
++ localIdentName: '[name]__[local]___[hash:base64:5]',
++ camelCase: true,
++ },
++ },
++ { loader: 'sass-loader', },
++ ],
++ }),
++ },
+ {
+ test: /\.css$/,
+ include: /node_modules/,
+ loader: ExtractTextPlugin.extract({
+ fallback: 'style-loader',
+ use: {
+ loader: 'css-loader',
+ },
+ }),
+ },
+ // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack
+ // Using here url-loader and file-loader
+ {
+ test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url-loader?limit=10000&mimetype=application/font-woff'
+ },
+ {
+ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url-loader?limit=10000&mimetype=application/octet-stream'
+ },
+ {
+ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url-loader?limit=10000&mimetype=image/svg+xml'
+ },
+ {
+ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'file-loader'
+ },
+ ]
+ },
+ plugins: [
+ // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin
+ new HtmlWebpackPlugin({
+ filename: 'index.html', // Name of file in ./dist/
+ template: 'index.html', // Name of template in ./src
+ hash: true
+ }),
+ new ExtractTextPlugin({
+- filename: '[chunkhash].[name].css',
++ filename: '[name].css',
+ disable: false,
+ allChunks: true,
+ }),
++ new webpack.optimize.CommonsChunkPlugin({
++ names: ['vendor', 'manifest'],
++ }),
+ ]
+}
+
+```
+
+- Let's configure `store` with redux-thunk:
+
+### ./src/store.ts
```javascript
+import { createStore, applyMiddleware, compose } from 'redux';
+import reduxThunk from 'redux-thunk';
+import { reducers } from './reducers';
+
+const middlewares = [
+ reduxThunk,
+];
-- + import { createStore } from 'redux';
-+ import { createStore, applyMiddleware } from 'redux';
-+ import reduxThunk from 'redux-thunk';
+const composeEnhancers = (process.env.NODE_ENV !== 'production' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ?
+ (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ :
+ compose;
+export const store = createStore(
+ reducers,
+ composeEnhancers(
+ applyMiddleware(...middlewares),
+ ),
+);
+
+```
+
+- Let's use the `store` in `main.tsx`:
+
+### ./src/main.tsx
+
+```diff
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+- import { createStore } from 'redux';
+import { Provider } from 'react-redux';
+- import {reducers} from './reducers'
++ import { store } from './store';
+import {App} from './app';
- let store = createStore(reducers);
-+let store = createStore(
-+ reducers,
-+ compose(
-+ applyMiddleware(reduxThunk),
-+ window['devToolsExtension'] ? window['devToolsExtension']() : f => f
-+ )
-+);
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root'));
+
```
- Let's start working with the pages structure, create the following folder _./src/pages_
@@ -101,6 +273,8 @@ npm install @types/redux-thunk --save-dev
- Let's create under _./src/pages/login/login.tsx_
+### ./src/pages/login/login.tsx
+
```javascript
import * as React from 'react';
@@ -113,40 +287,40 @@ export const LoginComponent = () => {
- Let's create under _./src/pages/login/loginContainer.tsx_
+### ./src/pages/login/loginContainer.tsx
+
```javascript
import { connect } from 'react-redux';
import { LoginComponent } from './login';
-const mapStateToProps = (state) => {
- return {
- }
-}
+const mapStateToProps = (state) => ({
-const mapDispatchToProps = (dispatch) => {
- return {
- }
-}
+});
+
+const mapDispatchToProps = (dispatch) => ({
+
+});
export const LoginContainer = connect(
- mapStateToProps
- ,mapDispatchToProps
- )(LoginComponent);
+ mapStateToProps,
+ mapDispatchToProps,
+)(LoginComponent);
+
```
-- Let's create under _./src/pages/login/index.tsx_
+- Let's create under _./src/pages/login/index.ts_
+
+### ./src/pages/login/index.ts
```javascript
-import { LoginContainer } from './loginContainer'
+export { LoginContainer } from './loginContainer';
-export {
- LoginContainer
-}
```
- Let's follow the same steps to create under _./src/pages/student-list
the folowing files:
-_studentList.tsx_
+### ./src/pages/student-list/studentList.tsx
```javascript
import * as React from 'react';
@@ -156,45 +330,41 @@ export const StudentListComponent = () => {
I'm the StudentList page
)
}
+
```
-_studentListContainer.tsx_
+### ./src/pages/student-list/studentListContainer.tsx
```javascript
import { connect } from 'react-redux';
import { StudentListComponent } from './studentList';
-const mapStateToProps = (state) => {
- return {
- }
-}
+const mapStateToProps = (state) => ({
-const mapDispatchToProps = (dispatch) => {
- return {
- }
-}
+});
+
+const mapDispatchToProps = (dispatch) => ({
+
+});
export const StudentListContainer = connect(
- mapStateToProps
- ,mapDispatchToProps
- )(StudentListComponent);
-```
+ mapStateToProps,
+ mapDispatchToProps,
+)(StudentListComponent);
+```
-_index.ts_
+### ./src/pages/student-list/index.ts
```javascript
-import {StudentListContainer} from './studentListContainer';
+export { StudentListContainer } from './studentListContainer';
-export {
- StudentListContainer
-}
```
- Let's follow the same steps to create under _./src/pages/student-detail
the folowing files:
-_studentDetail.tsx_
+### ./src/pages/student-detail/studentDetail.tsx
```javascript
import * as React from 'react';
@@ -204,537 +374,593 @@ export const StudentDetailComponent = () => {
I'm the StudentDetail page
)
}
+
```
-_studentDetailContainer.tsx_
+### ./src/pages/student-detail/studentDetailContainer.tsx
```javascript
import { connect } from 'react-redux';
import { StudentDetailComponent } from './studentDetail';
-const mapStateToProps = (state) => {
- return {
- }
-}
+const mapStateToProps = (state) => ({
-const mapDispatchToProps = (dispatch) => {
- return {
- }
-}
+});
+
+const mapDispatchToProps = (dispatch) => ({
+
+});
export const StudentDetailContainer = connect(
- mapStateToProps
- ,mapDispatchToProps
- )(StudentDetailComponent);
+ mapStateToProps,
+ mapDispatchToProps,
+)(StudentDetailComponent);
+
```
+### ./src/pages/student-detail/index.ts
-_index.tsx_
+```javascript
+export { StudentDetailContainer } from './studentDetailContainer';
+
+```
+
+- We could create now a file where we are going to declare all `appRoutes`.
+
+### ./src/appRoutes.tsx
```javascript
-import {StudentDetailContainer} from './studentDetailContainer';
+import * as React from 'react';
+import { Switch, Route } from 'react-router-dom';
+import { App } from './app';
+import { LoginContainer } from './pages/login';
+import { StudentListContainer } from './pages/student-list';
+import { StudentDetailContainer } from './pages/student-detail';
+
+export const AppRoutes: React.StatelessComponent = (props) => (
+
+
+
+
+
+
+
+);
-export {
- StudentDetailContainer
-}
```
-- Is time to wire up the navigation, let's start by adding _routerReducre_
+- Time to update _app.tsx_ to place the page container.
-_./src/reducers/index.ts_
+### ./src/app.tsx
```diff
-import { combineReducers } from 'redux';
-import { userProfileReducer } from './userProfile';
-+ import { routerReducer } from 'react-router-redux'
+import * as React from 'react';
+- import {HelloWorldContainer} from './helloWorldContainer';
+- import {NameEditContainer} from './nameEditContainer';
+
+- export const App = () => {
++ export const App: React.StatelessComponent = (props) => {
+ return (
+
+-
+-
+-
++ {props.children}
+
+ );
+}
+
+```
+
+- Finally, we have to use `appRoutes` in `main.tsx`. We have 2 ways of create `Router`, first one is using `HashRouter` and second one is using `Router` and create new instance of `history`. We are going to use the second one because we need to `navigate` programatically from `actions`:
+
+### ./src/history.ts
+
+```javascript
+import createHistory from 'history/createHashHistory';
+
+export const history = createHistory();
-export const reducers = combineReducers({
- userProfileReducer,
-+ routing: routerReducer
-});
```
-- Let's move to _./src/main.tsx_ and add the routing support (pending to separate
- this routing in a separate file).
+
+### ./src/main.tsx
```diff
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-+ import { Router, Route, IndexRoute, hashHistory } from 'react-router';
-+ import { syncHistoryWithStore} from 'react-router-redux'
-import { createStore, applyMiddleware, compose } from 'redux';
-import reduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
-import {reducers} from './reducers'
-import {App} from './app';
-+ import {LoginContainer} from './pages/login';
-+ import {StudentListContainer} from './pages/student-list';
-+ import {StudentDetailContainer} from './pages/student-detail';
-
-let store = createStore(
- reducers,
- compose(
- applyMiddleware(reduxThunk),
- window['devToolsExtension'] ? window['devToolsExtension']() : f => f
- )
-);
-
-+ const history = syncHistoryWithStore(hashHistory, store);
+import { store } from './store';
+- import { App } from './app';
++ import { Router } from 'react-router-dom';
++ import { history } from './history';
++ import { AppRoutes } from './appRoutes';
ReactDOM.render(
--
-+
-+
-+
-+
-+
-+
-+
-+
-+
-+
+-
++
++
++
,
- document.getElementById('root')
-);
+ document.getElementById('root'));
+
```
+- Let's create a header with `Links`:
+### ./src/appStyles.scss
-- Time to update _app.tsx_ to place the page container.
+```scss
+.header {
+ display: flex;
+ & > * {
+ margin-right: 10px;
+ }
+}
-```javascript
-import * as React from 'react'
-import { Link } from 'react-router'
+```
-export const App = (props: { children? }) => {
+### ./src/app.tsx
+
+```diff
+import * as React from 'react';
++ import { Link } from 'react-router-dom';
++ const styles = require('./appStyles.scss');
+
+export const App: React.StatelessComponent = (props) => {
return (
-
- Links:
- {' '}
- Login
- {' '}
- Student List
- {' '}
- Student Detail
-
-
{props.children}
-
- )
+- {props.children}
++
++ Login
++ Student List
++ Student Detail
++
++
++ {props.children}
++
+
+ );
}
+
```
- Let's create a loginEntity, under _./src/model_
-_./src/model/login.ts_
+### ./src/model/login.ts
```javascript
-export class LoginEntity {
- login : string;
- password : string;
-
- public constructor() {
- this.login = '';
- this.password = '';
- }
+export interface LoginEntity {
+ login: string;
+ password: string;
}
+
+export const createEmptyLoginEntity = (): LoginEntity => ({
+ login: '',
+ password: '',
+});
+
```
- And a userProfile entity.
-_./src/model/userProfile.ts_
+### ./src/model/userProfile.ts
```javascript
-export class UserProfile {
- fullname : string;
- role : string;
+export interface UserProfile {
+ fullname: string;
+ role: string;
}
+
+export const createEmptyUserProfile = (): UserProfile => ({
+ fullname: '',
+ role: '',
+});
+
```
- A loginResponse:
-_./src/model/loginResponse.ts_
+### ./src/model/loginResponse.ts
```javascript
-import {UserProfile} from './userProfile';
+import { UserProfile, createEmptyUserProfile } from './userProfile';
-export class LoginResponse {
- succeeded : boolean;
- userProfile : UserProfile;
+export interface LoginResponse {
+ succeeded: boolean;
+ userProfile: UserProfile;
}
+
+export const createEmptyLoginResponse = (): LoginResponse => ({
+ succeeded: false,
+ userProfile: createEmptyUserProfile(),
+});
+
```
-- Let's add a fake API to simulate a login (_./src/rest-api/loginApi.ts_).
+- And the `index` file:
+
+```javascript
+export * from './login';
+export * from './loginResponse';
+export * from './userProfile';
-_./src/rest-api/loginApi.ts_
+```
+
+- Let's add a fake API to simulate a login:
+
+# ./src/rest-api/loginApi.ts
```javascript
-import {LoginEntity} from '../model/login';
-import {UserProfile} from '../model/userProfile';
-import {LoginResponse} from '../model/loginResponse';
-import {} from 'core-js'
-
-class LoginApi {
- login(loginInfo : LoginEntity) : Promise {
- let loginResponse = new LoginResponse();
-
- if(loginInfo.login === 'admin' && loginInfo.password === 'test') {
- loginResponse.succeeded = true;
- loginResponse.userProfile = {fullname: "John Doe", role: 'admin' };
- } else {
- loginResponse.succeeded = false;
- loginResponse = null;
- }
-
- return Promise.resolve(loginResponse);
+import { LoginEntity, LoginResponse, createEmptyLoginResponse } from "../model";
+
+export const login = (loginEntity: LoginEntity): Promise => {
+ const loginResponse = createEmptyLoginResponse();
+
+ if (loginEntity.login === 'admin' && loginEntity.password === 'test') {
+ loginResponse.succeeded = true;
+ loginResponse.userProfile = {
+ fullname: 'John Doe',
+ role: 'admin',
+ };
}
+
+ return Promise.resolve(loginResponse);
}
+
```
-- Let's get started implementing our login functionallity, first we will define
-a perform login action (_./src/common/actions_):
+- Let's get started implementing our login functionallity, first we will define a perform login action:
-_./src/common/actionsEnums_
+### ./src/common/actionsEnums.ts
```diff
export const actionsEnums = {
-- UPDATE_USERPROFILE_NAME : 'UPDATE_USERPROFILE_NAME '
-+ USERPROFILE_PERFORM_LOGIN : 'USERPROFILE_PERFORM_LOGIN'
+- UPDATE_USERPROFILE_NAME: 'UPDATE_USERPROFILE_NAME '
++ USERPROFILE_PERFORM_LOGIN: 'USERPROFILE_PERFORM_LOGIN'
}
+
```
-- Login action will be asynchronous (we need to break it into two actions and use
- redux-thunk), we will create two actions _loginRequestStarted_ and _loginRequestCompleted_.
+- Login action will be asynchronous (we need to break it into two actions and use redux-thunk), we will create two actions _loginRequestStarted_ and _loginRequestCompleted_.
-- Let's go for the completed _./src/pages/login/actions/loginRequestCompleted.ts_
+- Let's go for the completed
-```javascript
-import {actionsEnums} from '../../../common/actionsEnums';
-import {LoginResponse} from '../../../model/loginResponse';
-
-export const loginRequestCompletedAction = (loginResponse : LoginResponse) => {
- return {
- type: actionsEnums.USERPROFILE_PERFORM_LOGIN,
- payload: loginResponse
- }
-}
-```
+###./src/pages/login/actions/loginRequest.ts
+```javascript
+import { actionsEnums } from '../../../common/actionsEnums';
+import { LoginResponse } from '../../../model';
-- Since this action is something we will fire only on the
-login window we will keep this under the following path _./src/pages/login/actions/loginRequestStarted.ts_
+const loginRequestCompletedAction = (loginResponse: LoginResponse) => ({
+ type: actionsEnums.USERPROFILE_PERFORM_LOGIN,
+ payload: loginResponse,
+});
-```javascript
-import {actionsEnums} from '../../../common/actionsEnums';
-import {LoginEntity} from '../../../model/login';
-import {loginApi} from '../../../rest-api/loginApi';
-import {loginRequestCompletedAction} from './loginRequestCompleted';
-import { hashHistory } from 'react-router';
+```
-export const loginRequestStartedAction = (login : LoginEntity) => {
- return function(dispatcher) {
- const promise = loginApi.login(login);
+- Next step is create the `loginRequestStartedAction`:
- promise.then(
- data => {
- dispatcher(loginRequestCompletedAction(data));
+###./src/pages/login/actions/loginRequest.ts
- // This is not ideal to have it here, maybe move it to middleware?
- if(data.succeeded == true) {
- hashHistory.push('/student-list');
- }
- }
+```diff
+import { actionsEnums } from '../../../common/actionsEnums';
+- import { LoginResponse } from '../../../model';
++ import { LoginResponse, LoginEntity } from '../../../model';
++ import { login } from '../../../rest-api/loginApi';
++ import { history } from '../../../history';
- );
++ export const loginRequestStartedAction = (loginEntity: LoginEntity) => (dispatcher) => {
++ const promise = login(loginEntity);
- return promise;
- }
-}
++ promise.then((loginResponse) => {
++ dispatcher(loginRequestCompletedAction(loginResponse));
-export const loginApi = new LoginApi();
-```
++ if (loginResponse.succeeded) {
++ history.push('/student-list');
++ }
++ });
-- Now the completed _./src/pages/login/actions/loginRequestCompleted.ts_
++ return promise;
++ }
-```javascript
-import {actionsEnums} from '../../../common/actionsEnums';
-import {LoginResponse} from '../../../model/loginResponse';
+const loginRequestCompletedAction = (loginResponse: LoginResponse) => ({
+ type: actionsEnums.USERPROFILE_PERFORM_LOGIN,
+ payload: loginResponse,
+});
-export const loginRequestCompleted = (loginResponse : LoginResponse) => {
- return {
- type: actionsEnums.USERPROFILE_PERFORM_LOGIN,
- payload: loginResponse
- }
-}
```
-- On the reducers side, let's remove the _./src/reducers/userProfile.ts_ reducer
-and add a new reducer that we will call _./src/reducers/session.ts_
+- On the reducers side, we are going to create `session` reducer to handle this `action`:
-_./src/reducers/session.ts_
+### ./src/reducers/session.ts
```javascript
-import {actionsEnums} from '../common/actionsEnums';
-import {UserProfile} from '../model/userProfile';
-import {LoginResponse} from '../model/loginResponse';
-import {LoginEntity} from '../model/login';
-
-class SessionState {
- isUserLoggedIn : boolean;
- userProfile : UserProfile;
- editingLogin : LoginEntity;
-
- public constructor()
- {
- this.isUserLoggedIn = false;
- this.userProfile = new UserProfile();
+import { actionsEnums } from '../common/actionsEnums';
+import {
+ UserProfile,
+ createEmptyUserProfile,
+ LoginEntity,
+ createEmptyLoginEntity,
+ LoginResponse,
+} from '../model';
+
+export interface SessionState {
+ isUserLoggedIn: boolean;
+ userProfile: UserProfile;
+ loginEntity: LoginEntity;
+}
+
+const createEmptyState = (): SessionState => ({
+ isUserLoggedIn: false,
+ userProfile: createEmptyUserProfile(),
+ loginEntity: createEmptyLoginEntity(),
+})
+
+export const sessionReducer = (state = createEmptyState(), action) => {
+ switch (action.type) {
+ case actionsEnums.USERPROFILE_PERFORM_LOGIN:
+ return userProfilePerformLoginHandler(state, action.payload);
}
-}
-
-export const sessionReducer = (state : SessionState = new SessionState(), action) => {
- switch (action.type) {
- case actionsEnums.USERPROFILE_PERFORM_LOGIN:
- return handlePerformLogin(state, action.payload);
-
-
- }
- return state;
-};
+ return state;
+}
+const userProfilePerformLoginHandler = (state: SessionState, payload: LoginResponse) => ({
+ ...state,
+ isUserLoggedIn: payload.succeeded,
+ userProfile: payload.userProfile,
+});
-const handlePerformLogin = (state : SessionState, payload : LoginResponse) => {
- return {...state,
- isUserLoggedIn: payload.succeeded,
- userProfile: payload.userProfile
- };
-}
```
-- Let's register this reducer _./src/reducers/index.ts_:
+- Let's register this reducer in _./src/reducers/index.ts_:
-_./src/reducers/index.ts_
+### ./src/reducers/index.ts
```diff
import { combineReducers } from 'redux';
-- import { userProfileReducer } from './userProfile';
-+ import { sessionReducer } from './session';
-import { routerReducer } from 'react-router-redux'
++ import { sessionReducer, SessionState } from './session';
-export const reducers = combineReducers({
-- userProfileReducer,
-+ sessionReducer,
- routing: routerReducer
++ export interface State {
++ session: SessionState;
++ }
+
+export const reducers = combineReducers({
++ session: sessionReducer,
});
+
```
-- It's time to jump into the ui part, we will use the login layout created in
-a previous sample, from repo [React By Sample: login form](https://github.com/Lemoncode/react-by-sample/tree/master/15%20LoginForm)
+- It's time to jump into the ui part, we will use the login layout created in a previous sample, from repo [React By Sample: login form](https://github.com/Lemoncode/react-by-sample/tree/master/15%20LoginForm)
-_./src/login/components/header.tsx_
+### ./src/login/components/header.tsx
```javascript
import * as React from "react"
export const Header = () => {
- return (
-
-
Please sign in
-
+ return (
+
+
Please sign in
+
);
}
+
```
-_./src/login/components/form.tsx_
+### ./src/login/components/form.tsx
```javascript
import * as React from "react"
-import {hashHistory} from 'react-router'
-import {LoginEntity} from '../../../model/login';
+import { LoginEntity } from '../../../model/login';
interface Props {
- loginInfo : LoginEntity;
- updateLoginInfo : (loginInfo : LoginEntity) => void;
- performLogin : () => void;
+ loginEntity: LoginEntity;
+ updateLoginEntity: (loginEntity: LoginEntity) => void;
+ performLogin: () => void;
}
-export const Form = (props: Props) => {
+export const Form: React.StatelessComponent = (props) => {
return (
- props.updateLoginInfo({login: props.loginInfo.login, password: e.target.value })}
+
- {props.performLogin()}}
- />
+
+ Login
+
);
-}
-```
-
-- Before continuing with the UI we have realized that this form components emits an event with the current
-editing login information (by doing this we can easily initialize it), we have to add to the session state
-a new property to hold this information, and an action to set it.
+};
-_./src/common/actionEnums.ts_
+const onChange = (props: Props) => (e) => {
+ const fieldName = e.target.name;
+ const value = e.target.value;
-```diff
-export const actionsEnums = {
-+ USERPROFILE_UPDATE_EDITING_LOGIN: 'USERPROFILE_UPDATE_EDITING_LOGIN',
- USERPROFILE_PERFORM_LOGIN : 'USERPROFILE_PERFORM_LOGIN'
+ props.updateLoginEntity({
+ ...props.loginEntity,
+ [fieldName]: value,
+ });
}
-```
-
-_./src/pages/login/actions/updateEditingLogin.ts
-
-```javascript
-import {actionsEnums} from '../../../common/actionsEnums';
-import {LoginEntity} from '../../../model/login';
-export const updateEditingLogin = (loginInfo : LoginEntity) => {
- return {
- type: actionsEnums.USERPROFILE_UPDATE_EDITING_LOGIN,
- payload: loginInfo
- }
+const onSubmit = (props: Props) => (e) => {
+ e.preventDefault();
+ props.performLogin();
}
+
```
-_./src/reducers/session.ts_
+- Before continuing with the UI we have realized that this form components emits an event with the current editing login information (by doing this we can easily initialize it), we have to add to the session state a new property to hold this information, and an action to set it.
+
+### ./src/common/actionEnums.ts
```diff
-import {actionsEnums} from '../common/actionsEnums';
-import {UserProfile} from '../model/userProfile';
-import {LoginResponse} from '../model/loginResponse';
-import {LoginEntity} from '../model/login';
-
-class SessionState {
- isUserLoggedIn : boolean;
- userProfile : UserProfile;
-+ editingLogin : LoginEntity;
-
- public constructor()
- {
- this.isUserLoggedIn = false;
- this.userProfile = new UserProfile();
-+ this.editingLogin = new LoginEntity();
- }
+export const actionsEnums = {
+ USERPROFILE_PERFORM_LOGIN: 'USERPROFILE_PERFORM_LOGIN',
++ USERPROFILE_UPDATE_EDITING_LOGIN: 'USERPROFILE_UPDATE_EDITING_LOGIN',
}
-export const sessionReducer = (state : SessionState = new SessionState(), action) => {
- switch (action.type) {
- case actionsEnums.USERPROFILE_PERFORM_LOGIN:
- return handlePerformLogin(state, action.payload);
-
-+ case actionsEnums.USERPROFILE_UPDATE_EDITING_LOGIN:
-+ return handleUpdateEditingLogin(state, action.payload);
- }
-
- return state;
-};
+```
+### ./src/pages/login/actions/updateEditingLogin.ts
-const handlePerformLogin = (state : SessionState, payload : LoginResponse) => {
- return {...state,
- isUserLoggedIn: payload.succeeded,
- userProfile: payload.userProfile
- };
-}
+```javascript
+import { actionsEnums } from '../../../common/actionsEnums';
+import { LoginEntity } from '../../../model';
+export const updateEditingLogin = (loginInfo: LoginEntity) => ({
+ type: actionsEnums.USERPROFILE_UPDATE_EDITING_LOGIN,
+ payload: loginInfo,
+});
-+const handleUpdateEditingLogin = (state: SessionState, payload : LoginEntity) => {
-+ return {
-+ ...state,
-+ editingLogin: payload
-+ };
-}
```
-- It's time to build the layout of the login Page _./src/pages/login/login.tsx_
+#### ./src/reducers/session.ts
-```javascript
-import * as React from 'react';
-import {Header} from './components/header';
-import {Form} from './components/form';
-import {LoginEntity} from '../../model/login';
+```diff
+import { actionsEnums } from '../common/actionsEnums';
+import {
+ UserProfile,
+ createEmptyUserProfile,
+ LoginEntity,
+ createEmptyLoginEntity,
+ LoginResponse,
+} from '../model';
+
+export interface SessionState {
+ isUserLoggedIn: boolean;
+ userProfile: UserProfile;
+ loginEntity: LoginEntity;
+}
+
+const createEmptyState = (): SessionState => ({
+ isUserLoggedIn: false,
+ userProfile: createEmptyUserProfile(),
+ loginEntity: createEmptyLoginEntity(),
+})
+
+export const sessionReducer = (state = createEmptyState(), action) => {
+ switch (action.type) {
+ case actionsEnums.USERPROFILE_PERFORM_LOGIN:
+ return userProfilePerformLoginHandler(state, action.payload);
+
++ case actionsEnums.USERPROFILE_UPDATE_EDITING_LOGIN:
++ return userProfileUpdateEditingLoginHandler(state, action.payload);
++ }
+
+ return state;
+}
+
+const userProfilePerformLoginHandler = (state: SessionState, payload: LoginResponse) => ({
+ ...state,
+ isUserLoggedIn: payload.succeeded,
+ userProfile: payload.userProfile,
+});
-interface Props {
- loginInfo : LoginEntity;
- updateLoginInfo : (loginInfo : LoginEntity) => void;
- performLogin : (loginInfo : LoginEntity) => void;
-}
++ const userProfileUpdateEditingLoginHandler = (state: SessionState, payload: LoginEntity) => ({
++ ...state,
++ loginEntity: payload,
++ });
-export const LoginComponent = (props : Props) => {
- return (
-
- )
-}
```
-- No we can wire up the loginContainer component with all the reducers info and actions
+- It's time to build the layout of the login Page:
-_./src/pages/login/loginContainer.tsx_
+### ./src/pages/login/login.tsx
+
+```diff
+import * as React from 'react';
++ import { Header } from './components/header';
++ import { Form } from './components/form';
++ import { LoginEntity } from '../../model';
+
++ interface Props {
++ loginEntity: LoginEntity;
++ updateLoginEntity: (loginEntity: LoginEntity) => void;
++ performLogin: (loginEntity: LoginEntity) => void;
++ }
+
+
+- export const LoginComponent = () => {
+- return (
+- Im the login page
+- )
+- }
+
++ export const LoginComponent: React.StatelessComponent = (props) => (
++
++ );
+
++ const performLogin = (props: Props) => () => {
++ props.performLogin(props.loginEntity);
++ };
+
+```
+
+- No we can wire up the loginContainer component with all the reducers info and actions:
+
+### ./src/pages/login/loginContainer.tsx
```diff
import { connect } from 'react-redux';
import { LoginComponent } from './login';
-+ import { LoginEntity } from '../../model/login';
++ import { State } from '../../reducers';
++ import { LoginEntity } from '../../model';
+ import { updateEditingLogin } from './actions/updateEditingLogin';
-+ import { loginRequestStartedAction} from './actions/loginRequestStarted';
-
++ import { loginRequestStartedAction } from './actions/loginRequest';
-const mapStateToProps = (state) => {
- return {
-+ loginInfo: state.sessionReducer.editingLogin
- }
-}
+- const mapStateToProps = (state) => ({
++ const mapStateToProps = (state: State) => ({
++ loginEntity: state.session.loginEntity,
+});
-const mapDispatchToProps = (dispatch) => {
- return {
-+ updateLoginInfo: (loginInfo : LoginEntity) => dispatch(updateEditingLogin(loginInfo)),
-+ performLogin: (loginInfo : LoginEntity) => dispatch(loginRequestStartedAction(loginInfo))
- }
-}
+const mapDispatchToProps = (dispatch) => ({
++ updateLoginEntity: (loginEntity: LoginEntity) => dispatch(updateEditingLogin(loginEntity)),
++ performLogin: (loginEntity: LoginEntity) => dispatch(loginRequestStartedAction(loginEntity)),
+});
export const LoginContainer = connect(
- mapStateToProps
- ,mapDispatchToProps
- )(LoginComponent);
+ mapStateToProps,
+ mapDispatchToProps,
+)(LoginComponent);
```
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/06 SimpleApp_Navigation/src/app.tsx b/06 SimpleApp_Navigation/src/app.tsx
index 2251ee4..b260f57 100644
--- a/06 SimpleApp_Navigation/src/app.tsx
+++ b/06 SimpleApp_Navigation/src/app.tsx
@@ -1,19 +1,18 @@
-import * as React from 'react'
-import { Link } from 'react-router'
+import * as React from 'react';
+import { Link } from 'react-router-dom';
+const styles = require('./appStyles.scss');
-export const App = (props: { children? }) => {
+export const App: React.StatelessComponent = (props) => {
return (
-
- Links:
- {' '}
+
Login
- {' '}
Student List
- {' '}
Student Detail
- {props.children}
+
+ {props.children}
+
- )
+ );
}
diff --git a/06 SimpleApp_Navigation/src/appRoutes.tsx b/06 SimpleApp_Navigation/src/appRoutes.tsx
new file mode 100644
index 0000000..01027cb
--- /dev/null
+++ b/06 SimpleApp_Navigation/src/appRoutes.tsx
@@ -0,0 +1,16 @@
+import * as React from 'react';
+import { Switch, Route } from 'react-router-dom';
+import { App } from './app';
+import { LoginContainer } from './pages/login';
+import { StudentListContainer } from './pages/student-list';
+import { StudentDetailContainer } from './pages/student-detail';
+
+export const AppRoutes: React.StatelessComponent = (props) => (
+
+
+
+
+
+
+
+);
diff --git a/06 SimpleApp_Navigation/src/appStyles.scss b/06 SimpleApp_Navigation/src/appStyles.scss
new file mode 100644
index 0000000..a41127a
--- /dev/null
+++ b/06 SimpleApp_Navigation/src/appStyles.scss
@@ -0,0 +1,6 @@
+.header {
+ display: flex;
+ & > * {
+ margin-right: 10px;
+ }
+}
diff --git a/06 SimpleApp_Navigation/src/common/actionsEnums.ts b/06 SimpleApp_Navigation/src/common/actionsEnums.ts
index f4ef099..47fea55 100644
--- a/06 SimpleApp_Navigation/src/common/actionsEnums.ts
+++ b/06 SimpleApp_Navigation/src/common/actionsEnums.ts
@@ -1,4 +1,4 @@
export const actionsEnums = {
+ USERPROFILE_PERFORM_LOGIN: 'USERPROFILE_PERFORM_LOGIN',
USERPROFILE_UPDATE_EDITING_LOGIN: 'USERPROFILE_UPDATE_EDITING_LOGIN',
- USERPROFILE_PERFORM_LOGIN : 'USERPROFILE_PERFORM_LOGIN'
}
diff --git a/06 SimpleApp_Navigation/src/history.ts b/06 SimpleApp_Navigation/src/history.ts
new file mode 100644
index 0000000..f36018b
--- /dev/null
+++ b/06 SimpleApp_Navigation/src/history.ts
@@ -0,0 +1,3 @@
+import createHistory from 'history/createHashHistory';
+
+export const history = createHistory();
diff --git a/06 SimpleApp_Navigation/src/main.tsx b/06 SimpleApp_Navigation/src/main.tsx
index 8fd08f3..b17d469 100644
--- a/06 SimpleApp_Navigation/src/main.tsx
+++ b/06 SimpleApp_Navigation/src/main.tsx
@@ -1,40 +1,15 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import { createStore, applyMiddleware, compose } from 'redux';
-import { Router, Route, IndexRoute, hashHistory } from 'react-router';
-import { syncHistoryWithStore } from 'react-router-redux'
-import reduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
-import {reducers} from './reducers'
-import {App} from './app';
-import {LoginContainer} from './pages/login';
-import {StudentListContainer} from './pages/student-list';
-import {StudentDetailContainer} from './pages/student-detail';
-
-
-let store = createStore(
- reducers,
- compose(
- applyMiddleware(reduxThunk),
- window['devToolsExtension'] ? window['devToolsExtension']() : f => f
- )
-);
-
-const history = syncHistoryWithStore(hashHistory, store);
+import { store } from './store';
+import { Router } from 'react-router-dom';
+import { history } from './history';
+import { AppRoutes } from './appRoutes';
ReactDOM.render(
-
-
-
-
-
-
-
-
-
-
+
+
+
,
- document.getElementById('root')
-);
-
+ document.getElementById('root'));
diff --git a/06 SimpleApp_Navigation/src/model/index.ts b/06 SimpleApp_Navigation/src/model/index.ts
new file mode 100644
index 0000000..ded7d16
--- /dev/null
+++ b/06 SimpleApp_Navigation/src/model/index.ts
@@ -0,0 +1,3 @@
+export * from './login';
+export * from './loginResponse';
+export * from './userProfile';
diff --git a/06 SimpleApp_Navigation/src/model/login.ts b/06 SimpleApp_Navigation/src/model/login.ts
index 0fbac16..b9b2d23 100644
--- a/06 SimpleApp_Navigation/src/model/login.ts
+++ b/06 SimpleApp_Navigation/src/model/login.ts
@@ -1,9 +1,9 @@
-export class LoginEntity {
- login : string;
- password : string;
-
- public constructor() {
- this.login = '';
- this.password = '';
- }
+export interface LoginEntity {
+ login: string;
+ password: string;
}
+
+export const createEmptyLoginEntity = (): LoginEntity => ({
+ login: '',
+ password: '',
+});
diff --git a/06 SimpleApp_Navigation/src/model/loginResponse.ts b/06 SimpleApp_Navigation/src/model/loginResponse.ts
index 82b796c..889793a 100644
--- a/06 SimpleApp_Navigation/src/model/loginResponse.ts
+++ b/06 SimpleApp_Navigation/src/model/loginResponse.ts
@@ -1,6 +1,11 @@
-import {UserProfile} from './userProfile';
+import { UserProfile, createEmptyUserProfile } from './userProfile';
-export class LoginResponse {
- succeeded : boolean;
- userProfile : UserProfile;
-}
\ No newline at end of file
+export interface LoginResponse {
+ succeeded: boolean;
+ userProfile: UserProfile;
+}
+
+export const createEmptyLoginResponse = (): LoginResponse => ({
+ succeeded: false,
+ userProfile: createEmptyUserProfile(),
+});
diff --git a/06 SimpleApp_Navigation/src/model/userProfile.ts b/06 SimpleApp_Navigation/src/model/userProfile.ts
index 447ff4f..8464c8f 100644
--- a/06 SimpleApp_Navigation/src/model/userProfile.ts
+++ b/06 SimpleApp_Navigation/src/model/userProfile.ts
@@ -1,4 +1,9 @@
-export class UserProfile {
- fullname : string;
- role : string;
+export interface UserProfile {
+ fullname: string;
+ role: string;
}
+
+export const createEmptyUserProfile = (): UserProfile => ({
+ fullname: '',
+ role: '',
+});
diff --git a/06 SimpleApp_Navigation/src/pages/login/actions/loginRequest.ts b/06 SimpleApp_Navigation/src/pages/login/actions/loginRequest.ts
new file mode 100644
index 0000000..2b5e7af
--- /dev/null
+++ b/06 SimpleApp_Navigation/src/pages/login/actions/loginRequest.ts
@@ -0,0 +1,24 @@
+import { actionsEnums } from '../../../common/actionsEnums';
+import { LoginResponse, LoginEntity } from '../../../model';
+import { login } from '../../../rest-api/loginApi';
+import { push } from 'react-router-redux';
+import { history } from '../../../history';
+
+export const loginRequestStartedAction = (loginEntity: LoginEntity) => (dispatcher) => {
+ const promise = login(loginEntity);
+
+ promise.then((loginResponse) => {
+ dispatcher(loginRequestCompletedAction(loginResponse));
+
+ if (loginResponse.succeeded) {
+ push('/student-list');
+ }
+ });
+
+ return promise;
+}
+
+const loginRequestCompletedAction = (loginResponse: LoginResponse) => ({
+ type: actionsEnums.USERPROFILE_PERFORM_LOGIN,
+ payload: loginResponse,
+});
diff --git a/06 SimpleApp_Navigation/src/pages/login/actions/loginRequestCompleted.ts b/06 SimpleApp_Navigation/src/pages/login/actions/loginRequestCompleted.ts
deleted file mode 100644
index 8ff26bd..0000000
--- a/06 SimpleApp_Navigation/src/pages/login/actions/loginRequestCompleted.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import {actionsEnums} from '../../../common/actionsEnums';
-import {LoginResponse} from '../../../model/loginResponse';
-
-export const loginRequestCompletedAction = (loginResponse : LoginResponse) => {
- return {
- type: actionsEnums.USERPROFILE_PERFORM_LOGIN,
- payload: loginResponse
- }
-}
diff --git a/06 SimpleApp_Navigation/src/pages/login/actions/loginRequestStarted.ts b/06 SimpleApp_Navigation/src/pages/login/actions/loginRequestStarted.ts
deleted file mode 100644
index bd67962..0000000
--- a/06 SimpleApp_Navigation/src/pages/login/actions/loginRequestStarted.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import {actionsEnums} from '../../../common/actionsEnums';
-import {LoginEntity} from '../../../model/login';
-import {loginApi} from '../../../rest-api/loginApi';
-import {loginRequestCompletedAction} from './loginRequestCompleted';
-import { hashHistory } from 'react-router';
-
-export const loginRequestStartedAction = (login : LoginEntity) => {
- return function(dispatcher) {
- const promise = loginApi.login(login);
-
- promise.then(
- data => {
- dispatcher(loginRequestCompletedAction(data));
-
- // This is not ideal to have it here, maybe move it to middleware?
- if(data.succeeded == true) {
- hashHistory.push('/student-list');
- }
- }
-
- );
-
- return promise;
- }
-}
diff --git a/06 SimpleApp_Navigation/src/pages/login/actions/updateEditingLogin.ts b/06 SimpleApp_Navigation/src/pages/login/actions/updateEditingLogin.ts
index 65bcc11..5a48808 100644
--- a/06 SimpleApp_Navigation/src/pages/login/actions/updateEditingLogin.ts
+++ b/06 SimpleApp_Navigation/src/pages/login/actions/updateEditingLogin.ts
@@ -1,9 +1,7 @@
-import {actionsEnums} from '../../../common/actionsEnums';
-import {LoginEntity} from '../../../model/login';
+import { actionsEnums } from '../../../common/actionsEnums';
+import { LoginEntity } from '../../../model';
-export const updateEditingLogin = (loginInfo : LoginEntity) => {
- return {
- type: actionsEnums.USERPROFILE_UPDATE_EDITING_LOGIN,
- payload: loginInfo
- }
-}
+export const updateEditingLogin = (loginInfo: LoginEntity) => ({
+ type: actionsEnums.USERPROFILE_UPDATE_EDITING_LOGIN,
+ payload: loginInfo,
+});
diff --git a/06 SimpleApp_Navigation/src/pages/login/components/form.tsx b/06 SimpleApp_Navigation/src/pages/login/components/form.tsx
index 69a7e76..821092a 100644
--- a/06 SimpleApp_Navigation/src/pages/login/components/form.tsx
+++ b/06 SimpleApp_Navigation/src/pages/login/components/form.tsx
@@ -1,35 +1,61 @@
import * as React from "react"
-import {hashHistory} from 'react-router'
-import {LoginEntity} from '../../../model/login';
+import { LoginEntity } from '../../../model/login';
interface Props {
- loginInfo : LoginEntity;
- updateLoginInfo : (loginInfo : LoginEntity) => void;
- performLogin : () => void;
+ loginEntity: LoginEntity;
+ updateLoginEntity: (loginEntity: LoginEntity) => void;
+ performLogin: () => void;
}
-export const Form = (props: Props) => {
+export const Form: React.StatelessComponent = (props) => {
return (
- props.updateLoginInfo({login: props.loginInfo.login, password: e.target.value })}
+
- {props.performLogin()}}
- />
+
+ Login
+
);
+};
+
+const onChange = (props: Props) => (e) => {
+ const fieldName = e.target.name;
+ const value = e.target.value;
+
+ props.updateLoginEntity({
+ ...props.loginEntity,
+ [fieldName]: value,
+ });
+}
+
+const onSubmit = (props: Props) => (e) => {
+ e.preventDefault();
+ props.performLogin();
}
diff --git a/06 SimpleApp_Navigation/src/pages/login/components/header.tsx b/06 SimpleApp_Navigation/src/pages/login/components/header.tsx
index f2ff3c8..7b0049e 100644
--- a/06 SimpleApp_Navigation/src/pages/login/components/header.tsx
+++ b/06 SimpleApp_Navigation/src/pages/login/components/header.tsx
@@ -1,9 +1,9 @@
import * as React from "react"
export const Header = () => {
- return (
-
-
Please sign in
-
+ return (
+
+
Please sign in
+
);
}
diff --git a/06 SimpleApp_Navigation/src/pages/login/index.ts b/06 SimpleApp_Navigation/src/pages/login/index.ts
index 099495b..2decf6f 100644
--- a/06 SimpleApp_Navigation/src/pages/login/index.ts
+++ b/06 SimpleApp_Navigation/src/pages/login/index.ts
@@ -1,5 +1 @@
-import { LoginContainer } from './loginContainer'
-
-export {
- LoginContainer
-}
\ No newline at end of file
+export { LoginContainer } from './loginContainer';
diff --git a/06 SimpleApp_Navigation/src/pages/login/login.tsx b/06 SimpleApp_Navigation/src/pages/login/login.tsx
index 8f68885..db51f4c 100644
--- a/06 SimpleApp_Navigation/src/pages/login/login.tsx
+++ b/06 SimpleApp_Navigation/src/pages/login/login.tsx
@@ -1,28 +1,31 @@
import * as React from 'react';
-import {Header} from './components/header';
-import {Form} from './components/form';
-import {LoginEntity} from '../../model/login';
+import { Header } from './components/header';
+import { Form } from './components/form';
+import { LoginEntity } from '../../model';
interface Props {
- loginInfo : LoginEntity;
- updateLoginInfo : (loginInfo : LoginEntity) => void;
- performLogin : (loginInfo : LoginEntity) => void;
+ loginEntity: LoginEntity;
+ updateLoginEntity: (loginEntity: LoginEntity) => void;
+ performLogin: (loginEntity: LoginEntity) => void;
}
-export const LoginComponent = (props : Props) => {
- return (
-
-
-
-
-
-
+export const LoginComponent: React.StatelessComponent
= (props) => (
+
+);
+
+const performLogin = (props: Props) => () => {
+ props.performLogin(props.loginEntity);
+};
diff --git a/06 SimpleApp_Navigation/src/pages/login/loginContainer.tsx b/06 SimpleApp_Navigation/src/pages/login/loginContainer.tsx
index fe5135e..429e485 100644
--- a/06 SimpleApp_Navigation/src/pages/login/loginContainer.tsx
+++ b/06 SimpleApp_Navigation/src/pages/login/loginContainer.tsx
@@ -1,23 +1,20 @@
import { connect } from 'react-redux';
import { LoginComponent } from './login';
-import { LoginEntity } from '../../model/login';
+import { State } from '../../reducers';
+import { LoginEntity } from '../../model';
import { updateEditingLogin } from './actions/updateEditingLogin';
-import { loginRequestStartedAction} from './actions/loginRequestStarted';
+import { loginRequestStartedAction } from './actions/loginRequest';
-const mapStateToProps = (state) => {
- return {
- loginInfo: state.sessionReducer.editingLogin
- }
-}
+const mapStateToProps = (state: State) => ({
+ loginEntity: state.session.loginEntity,
+});
-const mapDispatchToProps = (dispatch) => {
- return {
- updateLoginInfo: (loginInfo : LoginEntity) => dispatch(updateEditingLogin(loginInfo)),
- performLogin: (loginInfo : LoginEntity) => dispatch(loginRequestStartedAction(loginInfo))
- }
-}
+const mapDispatchToProps = (dispatch) => ({
+ updateLoginEntity: (loginEntity: LoginEntity) => dispatch(updateEditingLogin(loginEntity)),
+ performLogin: (loginEntity: LoginEntity) => dispatch(loginRequestStartedAction(loginEntity)),
+});
export const LoginContainer = connect(
- mapStateToProps
- ,mapDispatchToProps
- )(LoginComponent);
+ mapStateToProps,
+ mapDispatchToProps,
+)(LoginComponent);
diff --git a/06 SimpleApp_Navigation/src/pages/student-detail/index.ts b/06 SimpleApp_Navigation/src/pages/student-detail/index.ts
new file mode 100644
index 0000000..1dd8c52
--- /dev/null
+++ b/06 SimpleApp_Navigation/src/pages/student-detail/index.ts
@@ -0,0 +1 @@
+export { StudentDetailContainer } from './studentDetailContainer';
diff --git a/06 SimpleApp_Navigation/src/pages/student-detail/index.tsx b/06 SimpleApp_Navigation/src/pages/student-detail/index.tsx
deleted file mode 100644
index ba7f440..0000000
--- a/06 SimpleApp_Navigation/src/pages/student-detail/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import {StudentDetailContainer} from './studentDetailContainer';
-
-export {
- StudentDetailContainer
-}
diff --git a/06 SimpleApp_Navigation/src/pages/student-detail/studentDetailContainer.tsx b/06 SimpleApp_Navigation/src/pages/student-detail/studentDetailContainer.tsx
index de5e682..3287cf2 100644
--- a/06 SimpleApp_Navigation/src/pages/student-detail/studentDetailContainer.tsx
+++ b/06 SimpleApp_Navigation/src/pages/student-detail/studentDetailContainer.tsx
@@ -1,17 +1,15 @@
import { connect } from 'react-redux';
import { StudentDetailComponent } from './studentDetail';
-const mapStateToProps = (state) => {
- return {
- }
-}
+const mapStateToProps = (state) => ({
-const mapDispatchToProps = (dispatch) => {
- return {
- }
-}
+});
+
+const mapDispatchToProps = (dispatch) => ({
+
+});
export const StudentDetailContainer = connect(
- mapStateToProps
- ,mapDispatchToProps
- )(StudentDetailComponent);
+ mapStateToProps,
+ mapDispatchToProps,
+)(StudentDetailComponent);
diff --git a/06 SimpleApp_Navigation/src/pages/student-list/index.ts b/06 SimpleApp_Navigation/src/pages/student-list/index.ts
index 435be76..13cfacc 100644
--- a/06 SimpleApp_Navigation/src/pages/student-list/index.ts
+++ b/06 SimpleApp_Navigation/src/pages/student-list/index.ts
@@ -1,5 +1 @@
-import {StudentListContainer} from './studentListContainer';
-
-export {
- StudentListContainer
-}
+export { StudentListContainer } from './studentListContainer';
diff --git a/06 SimpleApp_Navigation/src/pages/student-list/studentListContainer.tsx b/06 SimpleApp_Navigation/src/pages/student-list/studentListContainer.tsx
index bcb8b5d..cce7e61 100644
--- a/06 SimpleApp_Navigation/src/pages/student-list/studentListContainer.tsx
+++ b/06 SimpleApp_Navigation/src/pages/student-list/studentListContainer.tsx
@@ -1,17 +1,15 @@
import { connect } from 'react-redux';
import { StudentListComponent } from './studentList';
-const mapStateToProps = (state) => {
- return {
- }
-}
+const mapStateToProps = (state) => ({
-const mapDispatchToProps = (dispatch) => {
- return {
- }
-}
+});
+
+const mapDispatchToProps = (dispatch) => ({
+
+});
export const StudentListContainer = connect(
- mapStateToProps
- ,mapDispatchToProps
- )(StudentListComponent);
+ mapStateToProps,
+ mapDispatchToProps,
+)(StudentListComponent);
diff --git a/06 SimpleApp_Navigation/src/reducers/index.ts b/06 SimpleApp_Navigation/src/reducers/index.ts
index f7dc905..8771f0e 100644
--- a/06 SimpleApp_Navigation/src/reducers/index.ts
+++ b/06 SimpleApp_Navigation/src/reducers/index.ts
@@ -1,8 +1,10 @@
import { combineReducers } from 'redux';
-import { sessionReducer } from './session';
-import { routerReducer } from 'react-router-redux'
+import { sessionReducer, SessionState } from './session';
-export const reducers = combineReducers({
- sessionReducer,
- routing: routerReducer
+export interface State {
+ session: SessionState;
+}
+
+export const reducers = combineReducers({
+ session: sessionReducer,
});
diff --git a/06 SimpleApp_Navigation/src/reducers/session.ts b/06 SimpleApp_Navigation/src/reducers/session.ts
index dbd2b9c..6824ae5 100644
--- a/06 SimpleApp_Navigation/src/reducers/session.ts
+++ b/06 SimpleApp_Navigation/src/reducers/session.ts
@@ -1,48 +1,43 @@
-import {actionsEnums} from '../common/actionsEnums';
-import {UserProfile} from '../model/userProfile';
-import {LoginResponse} from '../model/loginResponse';
-import {LoginEntity} from '../model/login';
-
-class SessionState {
- isUserLoggedIn : boolean;
- userProfile : UserProfile;
- editingLogin : LoginEntity;
-
- public constructor()
- {
- this.isUserLoggedIn = false;
- this.userProfile = new UserProfile();
- this.editingLogin = new LoginEntity();
- }
+import { actionsEnums } from '../common/actionsEnums';
+import {
+ UserProfile,
+ createEmptyUserProfile,
+ LoginEntity,
+ createEmptyLoginEntity,
+ LoginResponse,
+} from '../model';
+
+export interface SessionState {
+ isUserLoggedIn: boolean;
+ userProfile: UserProfile;
+ loginEntity: LoginEntity;
}
-export const sessionReducer = (state : SessionState = new SessionState(), action) => {
- switch (action.type) {
- case actionsEnums.USERPROFILE_PERFORM_LOGIN:
- return handlePerformLogin(state, action.payload);
- case actionsEnums.USERPROFILE_UPDATE_EDITING_LOGIN:
- return handleUpdateEditingLogin(state, action.payload);
-
- }
+const createEmptyState = (): SessionState => ({
+ isUserLoggedIn: false,
+ userProfile: createEmptyUserProfile(),
+ loginEntity: createEmptyLoginEntity(),
+})
- return state;
-};
+export const sessionReducer = (state = createEmptyState(), action) => {
+ switch (action.type) {
+ case actionsEnums.USERPROFILE_PERFORM_LOGIN:
+ return userProfilePerformLoginHandler(state, action.payload);
+ case actionsEnums.USERPROFILE_UPDATE_EDITING_LOGIN:
+ return userProfileUpdateEditingLoginHandler(state, action.payload);
+ }
-const handlePerformLogin = (state : SessionState, payload : LoginResponse) => {
- return {...state,
- isUserLoggedIn: payload.succeeded,
- userProfile: payload.userProfile
- };
+ return state;
}
+const userProfilePerformLoginHandler = (state: SessionState, payload: LoginResponse) => ({
+ ...state,
+ isUserLoggedIn: payload.succeeded,
+ userProfile: payload.userProfile,
+});
-const handleUpdateEditingLogin = (state: SessionState, payload : LoginEntity) => {
- const newState = {
- ...state,
- editingLogin: payload
- };
-
- return newState;
-}
-
+const userProfileUpdateEditingLoginHandler = (state: SessionState, payload: LoginEntity) => ({
+ ...state,
+ loginEntity: payload,
+});
diff --git a/06 SimpleApp_Navigation/src/rest-api/loginApi.ts b/06 SimpleApp_Navigation/src/rest-api/loginApi.ts
index 1c72393..571ffdd 100644
--- a/06 SimpleApp_Navigation/src/rest-api/loginApi.ts
+++ b/06 SimpleApp_Navigation/src/rest-api/loginApi.ts
@@ -1,22 +1,15 @@
-import {LoginEntity} from '../model/login';
-import {UserProfile} from '../model/userProfile';
-import {LoginResponse} from '../model/loginResponse';
-import {} from 'core-js'
+import { LoginEntity, LoginResponse, createEmptyLoginResponse } from "../model";
-class LoginApi {
- login(loginInfo : LoginEntity) : Promise {
- let loginResponse = new LoginResponse();
+export const login = (loginEntity: LoginEntity): Promise => {
+ const loginResponse = createEmptyLoginResponse();
- if(loginInfo.login === 'admin' && loginInfo.password === 'test') {
- loginResponse.succeeded = true;
- loginResponse.userProfile = {fullname: "John Doe", role: 'admin' };
- } else {
- loginResponse.succeeded = false;
- loginResponse = null;
- }
-
- return Promise.resolve(loginResponse);
+ if (loginEntity.login === 'admin' && loginEntity.password === 'test') {
+ loginResponse.succeeded = true;
+ loginResponse.userProfile = {
+ fullname: 'John Doe',
+ role: 'admin',
+ };
}
-}
-export const loginApi = new LoginApi();
+ return Promise.resolve(loginResponse);
+}
diff --git a/06 SimpleApp_Navigation/src/store.ts b/06 SimpleApp_Navigation/src/store.ts
new file mode 100644
index 0000000..cae7697
--- /dev/null
+++ b/06 SimpleApp_Navigation/src/store.ts
@@ -0,0 +1,18 @@
+import { createStore, applyMiddleware, compose } from 'redux';
+import reduxThunk from 'redux-thunk';
+import { reducers } from './reducers';
+
+const middlewares = [
+ reduxThunk,
+];
+
+const composeEnhancers = (process.env.NODE_ENV !== 'production' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ?
+ (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ :
+ compose;
+
+export const store = createStore(
+ reducers,
+ composeEnhancers(
+ applyMiddleware(...middlewares),
+ ),
+);
diff --git a/06 SimpleApp_Navigation/webpack.config.js b/06 SimpleApp_Navigation/webpack.config.js
index 744b3f2..63ca4d8 100644
--- a/06 SimpleApp_Navigation/webpack.config.js
+++ b/06 SimpleApp_Navigation/webpack.config.js
@@ -11,13 +11,24 @@ module.exports = {
extensions: ['.js', '.ts', '.tsx']
},
- entry: [
- './main.tsx',
- '../node_modules/bootstrap/dist/css/bootstrap.css'
- ],
+ entry: {
+ app: './main.tsx',
+ vendor: [
+ 'babel-polyfill',
+ 'react',
+ 'react-dom',
+ 'react-redux',
+ 'react-router-dom',
+ 'redux',
+ 'redux-thunk',
+ ],
+ vendorStyles: [
+ '../node_modules/bootstrap/dist/css/bootstrap.css'
+ ],
+ },
output: {
path: path.join(basePath, 'dist'),
- filename: 'bundle.js'
+ filename: '[name].js',
},
devtool: 'source-map',
@@ -42,6 +53,24 @@ module.exports = {
},
},
},
+ {
+ test: /\.scss$/,
+ exclude: /node_modules/,
+ loader: ExtractTextPlugin.extract({
+ fallback: 'style-loader',
+ use: [
+ {
+ loader: 'css-loader',
+ options: {
+ modules: true,
+ localIdentName: '[name]__[local]___[hash:base64:5]',
+ camelCase: true,
+ },
+ },
+ { loader: 'sass-loader', },
+ ],
+ }),
+ },
{
test: /\.css$/,
include: /node_modules/,
@@ -80,9 +109,12 @@ module.exports = {
hash: true
}),
new ExtractTextPlugin({
- filename: '[chunkhash].[name].css',
+ filename: '[name].css',
disable: false,
allChunks: true,
}),
+ new webpack.optimize.CommonsChunkPlugin({
+ names: ['vendor', 'manifest'],
+ }),
]
}