@@ -18,14 +18,14 @@ npm install
1818
1919- Let's update as well the name of the component.
2020
21- _ ./src/pages/loginPage .tsx_
21+ _ ./src/pages/login.component .tsx_
2222
2323``` diff
2424import * as React from "react";
2525import { Link } from "react-router-dom";
2626
2727- export const PageA = () => (
28- + export const LoginPage = () => (
28+ + export const LoginComponent: React.FC<PropsForm> = (props ) => {
2929 <div>
3030- <h2>Hello from page A</h2>
3131+ <h2> Hello from login Page</h2>
@@ -43,7 +43,7 @@ _./src/app.tsx_
4343import * as React from "react";
4444import { HashRouter, Switch, Route } from "react-router-dom";
4545- import { PageA } from "./pages/pageA";
46- + import { LoginPage } from "./pages/loginPage ";
46+ + import { LoginComponent } from "./pages/login.component ";
4747import { PageB } from "./pages/pageB";
4848
4949export const App = () => {
@@ -53,7 +53,7 @@ export const App = () => {
5353 <HashRouter>
5454 <Switch>
5555- <Route exact={true} path="/" component={PageA} />
56- + <Route exact={true} path="/" component={LoginPage } />
56+ + <Route exact={true} path="/" component={LoginComponent } />
5757 <Route path="/pageB" component={PageB} />
5858 </Switch>
5959 </HashRouter>
@@ -118,7 +118,7 @@ npm install @material-ui/core @material-ui/icons --save
118118
119119- Now we could create a login form it could look something like:
120120
121- _ ./src/pages/loginPage .tsx_
121+ _ ./src/pages/login.container .tsx_
122122
123123``` javascript
124124import * as React from " react" ;
@@ -143,7 +143,7 @@ const useStyles = makeStyles(theme =>
143143
144144interface Props extends RouteComponentProps {}
145145
146- const LoginPageInner = (props : Props ) => {
146+ export const LoginContainer : React . FC < Props > = (props ) => {
147147 const classes = useStyles ();
148148
149149 return (
@@ -167,8 +167,6 @@ const LoginPageInner = (props: Props) => {
167167 < / Card>
168168 );
169169};
170-
171- export const LoginPage = withRouter < Props > LoginPageInner;
172170```
173171
174172- This can be ok, but if we take a deeper look to this component, we could break down into two, one is the card itself the other the form dialog, so it should finally look like:
@@ -186,10 +184,10 @@ export const LoginPage = withRouter < Props > LoginPageInner;
186184
187185- Let's create the LoginForm component (append it to the loginPage file):
188186
189- _ ./src/pages/loginPage .tsx_
187+ _ ./src/pages/login.component .tsx_
190188
191189``` javascript
192- const LoginForm = props => {
190+ export const LoginComponent : React . FC < PropsForm > = ( props ) => {
193191 return (
194192 < div
195193 style= {{
@@ -198,26 +196,36 @@ const LoginForm = props => {
198196 justifyContent: " center"
199197 }}
200198 >
201- < TextField label= " Name" margin= " normal" / >
202- < TextField label= " Password" type= " password" margin= " normal" / >
203- < Button variant= " contained" color= " primary" >
199+ < TextField
200+ label= " Name"
201+ margin= " normal"
202+ / >
203+ < TextField
204+ label= " Password"
205+ type= " password"
206+ margin= " normal"
207+ / >
208+ < Button
209+ variant= " contained"
210+ color= " primary"
211+ >
204212 Login
205213 < / Button>
206214 < / div>
207215 );
208216};
209217```
210218
211- - And let's update the _ loginPage .tsx_
219+ - And let's update the _ login.container .tsx_
212220
213- _ ./src/pages/loginPage .tsx_
221+ _ ./src/pages/login.container .tsx_
214222
215223``` diff
216224 return (
217225 <Card className={classes.card}>
218226 <CardHeader title="Login" />
219227 <CardContent>
220- + <LoginForm />
228+ + <LoginComponent />
221229- <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
222230- <TextField
223231- label="Name"
@@ -247,7 +255,7 @@ npm start
247255
248256- First we will expose a method to do that in the loginPage.
249257
250- _ ./src/pages/login/loginPage .tsx_
258+ _ ./src/pages/login/login.container .tsx_
251259
252260``` diff
253261// ...
@@ -264,7 +272,7 @@ const useStyles = makeStyles(theme =>
264272
265273interface Props extends RouteComponentProps {}
266274
267- const LoginPageInner = (props) => {
275+ export const LoginContainer: React.FC<Props> = (props) => {
268276 const { classes } = props;
269277
270278+ const onLogin = () => {
@@ -282,14 +290,13 @@ const LoginPageInner = (props) => {
282290 )
283291}
284292
285- export const LoginPage = withRouter<Props>(LoginPageInner);
286293```
287294
288295- Let's add the navigation on button clicked (later on we will check for user and pwd) _ form.tsx_ .
289296 In order to do this we have used react-router 4 "withRouter" HoC (High order component), and pass it
290297 down to the LoginForm component.
291298
292- _ ./src/pages/loginPage .tsx_
299+ _ ./src/pages/login.component .tsx_
293300
294301``` diff
295302+ interface PropsForm {
@@ -351,86 +358,85 @@ _./src/api/login.ts_
351358import { LoginEntity } from " ../model/login" ;
352359
353360// Just a fake loginAPI
354- export const isValidLogin = (loginInfo: LoginEntity): boolean =>
355- loginInfo .login === " admin" && loginInfo .password === " test" ;
361+ export const isValidLogin = (loginInfo: LoginEntity): Promise < boolean> =>
362+ new Promise ((resolve ) => {
363+ setTimeout (() => {
364+ // mock call
365+ resolve (loginInfo .login === " admin" && loginInfo .password === " test" );
366+ }, 500 );
367+ });
356368```
357369
358370- Let's add the _ api_ integration, plus navigation on login succeeded:
359371
360372- First let's create a login state and add the api integration.
361373
362- _ ./src/pages/loginPage .tsx_
374+ _ ./src/pages/login.container .tsx_
363375
364376``` diff
365377+ import { LoginEntity, createEmptyLogin } from '../model/login';
366378+ import { isValidLogin } from '../api/login';
367379```
368380
369- _ ./src/pages/loginPage .tsx_
381+ _ ./src/pages/login.container .tsx_
370382
371383``` diff
372384const LoginPageInner = (props: Props) => {
373- + const [loginInfo, setLoginInfo] = React.useState<LoginEntity>(createEmptyLogin());
385+ + const [loginInfo, setLoginInfo] = React.useState<LoginEntity>(
386+ + createEmptyLogin()
387+ + );
374388 const { classes } = props;
375389
376- const onLogin = () => {
377- + if(isValidLogin(loginInfo)) {
378- props.history.push("/pageB");
379- + }
390+ const loginSucceeded = (isValid: boolean) => {
391+ if (isValid) {
392+ history.push("/pageB");
393+ } else {
394+ setShowAlert(true);
395+ }
396+ };
397+
398+ const handleLogin = (login: LoginEntity) => {
399+ isValidLogin(login).then(loginSucceeded);
380400 };
381401
382402```
383403
384404- Now let's read the data from the textfields components (user and password).
385405
386- _ ./src/pages/loginPage .tsx_
406+ _ ./src/pages/login.container .tsx_
387407
388408``` diff
389- const onLogin = () => {
390- if (isValidLogin(loginInfo)) {
391- props.history.push("/pageB");
392- }
393- };
394-
395- + const onUpdateLoginField = (name, value) => {
396- + setLoginInfo({
397- + ...loginInfo,
398- + [name]: value,
399- + })
400- + }
401-
402409 return (
403410 <Card className={classes.card}>
404411 <CardHeader title="Login" />
405412 <CardContent>
406- <LoginForm onLogin={onLogin}
407- + onUpdateField={onUpdateLoginField}
408- + loginInfo={loginInfo}
409- />
413+ + <LoginForm onLogin={handleLogin}/>
410414 </CardContent>
411415 </Card>
412416 );
413417```
414418
415419- And update _ LoginForm_ props and textField onChange.
416420
417- _ ./src/pages/loginPage .tsx_
421+ _ ./src/pages/login.component .tsx_
418422
419423``` diff
420424interface PropsForm {
421- onLogin: () => void;
422- + onUpdateField: (name: string, value: any) => void;
423- + loginInfo : LoginEntity;
425+ + onLogin: (login: LoginEntity) => void;
424426}
425427
426- const LoginForm = (props: PropsForm) => {
427- - const { onLogin } = props;
428- + const { onLogin, onUpdateField, loginInfo } = props;
429-
430- + // TODO: Enhacement move this outside the stateless component discuss why is a good idea
428+ export const LoginComponent: React.FC<PropsForm> = (props) => {
429+ + const [loginInfo, setLoginInfo] = React.useState<LoginEntity>(
430+ + const { onLogin } = props;
431+ + createEmptyLogin()
432+ + );
433+ + const classes = useFormStyles();
431434+ const onTexFieldChange = (fieldId) => (e) => {
432- + onUpdateField(fieldId, e.target.value);
433- + }
435+ + setLoginInfo({
436+ + ...loginInfo,
437+ + [fieldId]: e.target.value,
438+ + });
439+ + };
434440
435441 return (
436442 <div
@@ -440,15 +446,24 @@ const LoginForm = (props: PropsForm) => {
440446 justifyContent: "center"
441447 }}
442448 >
443- <TextField label="Name" margin="normal"
449+ <TextField
450+ label="Name"
451+ margin="normal"
444452+ value={loginInfo.login}
445- + onChange={onTexFieldChange(' login' )}
453+ + onChange={onTexFieldChange(" login" )}
446454 />
447- <TextField label="Password" type="password" margin="normal"
455+ <TextField
456+ label="Password"
457+ type="password"
458+ margin="normal"
448459+ value={loginInfo.password}
449- + onChange={onTexFieldChange(' password' )}
460+ + onChange={onTexFieldChange(" password" )}
450461 />
451- <Button variant="contained" color="primary" onClick={onLogin}>
462+ <Button
463+ variant="contained"
464+ color="primary"
465+ onClick={() => onLogin(loginInfo)}
466+ >
452467 Login
453468 </Button>
454469 </div>
@@ -458,13 +473,11 @@ const LoginForm = (props: PropsForm) => {
458473
459474- We will add material-ui classes to LoginForm component.
460475
461- _ ./src/pages/loginPage .tsx_
476+ _ ./src/pages/login.container .tsx_
462477
463478``` diff
464479interface PropsForm {
465- onLogin: () => void;
466- onUpdateField: (name: string, value: any) => void;
467- loginInfo : LoginEntity;
480+ onLogin: (login: LoginEntity) => void;
468481}
469482
470483+ // https://material-ui.com/styles/api/#makestyles-styles-options-hook
@@ -478,14 +491,8 @@ interface PropsForm {
478491+ })
479492+ );
480493
481- const LoginForm = (props: PropsForm ) => {
494+ export const LoginComponent: React.FC<PropsForm> = (props) => {
482495+ const classes = useFormStyles();
483- const { onLogin, onUpdateField, loginInfo } = props;
484-
485- // TODO: Enhacement move this outside the stateless component discuss why is a good idea
486- const onTexFieldChange = (fieldId) => (e) => {
487- onUpdateField(fieldId, e.target.value);
488- }
489496
490497 return (
491498- <div
@@ -583,7 +590,7 @@ export * from "./notification";
583590
584591- Now let's instantiate this in our _ loginPage_
585592
586- _ ./src/pages/loginPage .tsx_
593+ _ ./src/pages/login.container .tsx_
587594
588595``` diff
589596+ import { NotificationComponent } from "../common";
@@ -594,15 +601,15 @@ const LoginPageInner = (props: Props) => {
594601 const [loginInfo, setLoginInfo] = React.useState<LoginEntity>(
595602 createEmptyLogin()
596603 );
597- + const [showLoginFailedMsg, setShowLoginFailedMsg ] = React.useState(false);
604+ + const [isShowAlert, setShowAlert ] = React.useState(false);
598605 const classes = useStyles();
599606
600607 const onLogin = () => {
601608 if (isValidLogin(loginInfo)) {
602609 props.history.push("/pageB");
603610- }
604611+ } else {
605- + setShowLoginFailedMsg (true);
612+ + setShowAlert (true);
606613+ }
607614 }
608615
@@ -625,11 +632,11 @@ const LoginPageInner = (props: Props) => {
625632 />
626633 </CardContent>
627634 </Card>
628- + <NotificationComponent
629- + message="Invalid login or password, please type again"
630- + show={showLoginFailedMsg }
631- + onClose={() => setShowLoginFailedMsg (false)}
632- + />
635+ + <NotificationComponent
636+ + message="Invalid login or password, please type again"
637+ + show={isShowAlert }
638+ + onClose={() => setShowAlert (false)}
639+ + />
633640+ </>
634641 );
635642};
0 commit comments