Difference between revisions of "Creating Admin Portal Project in React"
(→Installing & Using FontAwesome Icons) |
|||
| (6 intermediate revisions by the same user not shown) | |||
| Line 13: | Line 13: | ||
** '''Prettier''' by Esben Petersen | ** '''Prettier''' by Esben Petersen | ||
*** Set '''Format on Save''' | *** Set '''Format on Save''' | ||
| + | ** Auto Import by Sergey Korenuk | ||
in VS Code go to File / Preferences / Settings and add the line below in UserSettings section | in VS Code go to File / Preferences / Settings and add the line below in UserSettings section | ||
| Line 62: | Line 63: | ||
npx nodemon index.js | npx nodemon index.js | ||
== Coding == | == Coding == | ||
| − | === | + | === Routing === |
| + | install router | ||
| + | npm i react-router-dom | ||
| + | in '''index.js''' file add this line | ||
| + | import { BrowserRouter } from "react-router-dom"; | ||
| + | and wrap <App/> with <BrowserRouter> like below | ||
| + | <pre> | ||
| + | ReactDOM.render( | ||
| + | <BrowserRouter> | ||
| + | <App /> | ||
| + | </BrowserRouter>, | ||
| + | document.getElementById("root") | ||
| + | ); | ||
| + | </pre> | ||
| + | |||
| + | in App.js import '''Route''' & '''Switch''' | ||
| + | import { Route, Switch } from "react-router-dom"; | ||
| + | and add these lines to render() | ||
| + | <pre> | ||
| + | return ( | ||
| + | <div className="App"> | ||
| + | <TopBar /> | ||
| + | <SideBar /> | ||
| + | <header className="App-header"> | ||
| + | <div className="content"> | ||
| + | <Switch> | ||
| + | <Route path="/admin" component={Admin} /> | ||
| + | </Switch> | ||
| + | </div> | ||
| + | </header> | ||
| + | </div> | ||
| + | ); | ||
| + | </pre> | ||
| + | So in this scenario there is one top bar and a left side bar. They are static. The rest of the screen (div with className="content") will be different in every page. | ||
| + | |||
| + | Switch is for stopping rendering to go to next Route which matches the satisfying criteria. Like: | ||
| + | |||
| + | if the first route is path="/admin" and the second route is "/" without a switch here it will render both of them back to back. | ||
| + | === Links === | ||
| + | In sideBar component when I click a link it normalyy reload the whole page as it uses <a> tag to prevent this import Link | ||
| + | import {Link} from "react-router-dom"; | ||
| + | and replace '''<a''' with '''<Link''' and '''href''' with '''to'''. ie: | ||
| + | <Link to="/product"> | ||
| + | == Http Client - Preparation == | ||
| + | install Axios | ||
| + | npm i axios | ||
| + | install Toastify | ||
| + | npm i react-toastify | ||
| + | in app.js import Toastfy and css file and add a tag for container | ||
| + | import {ToastContainer} from "react-toastify"; | ||
| + | import "react-toastify/dist/ReactToastify.css"; | ||
| + | |||
| + | render() { | ||
| + | return ( | ||
| + | <div className="App"> | ||
| + | <ToastContainer/> | ||
| + | .... | ||
| + | === Creating httpService.js === | ||
| + | create a folder "services" and within this folder create a file names "httpService.js" | ||
| + | <pre class="brush:js;"> | ||
| + | import axios from "axios"; | ||
| + | import { toast } from "react-toastify"; | ||
| + | import log from "./logService.js"; | ||
| + | |||
| + | axios.interceptors.response.use(null, error => { | ||
| + | const expectedError = | ||
| + | error.response && | ||
| + | error.response.status >= 400 && | ||
| + | error.response.status < 500; | ||
| + | if (!expectedError) { | ||
| + | logger.log(error); | ||
| + | toast.error("An unexpected error occured."); | ||
| + | } | ||
| + | |||
| + | return Promise.reject(error); | ||
| + | }); | ||
| + | |||
| + | export default { | ||
| + | get: axios.get, | ||
| + | post: axios.post, | ||
| + | put: axios.put, | ||
| + | delete: axios.delete | ||
| + | }; | ||
| + | |||
| + | </pre> | ||
| + | here we use interceptor for responses and capture any unexpected errors. display them in a toaster and send a copy to logService which is basically doing nothing at the moment. | ||
| + | ==== logService ==== | ||
| + | <pre class="brush:js;"> | ||
| + | function init() {} | ||
| + | |||
| + | function log(error) { | ||
| + | console.log(error); | ||
| + | } | ||
| + | |||
| + | export default { | ||
| + | init, | ||
| + | log | ||
| + | }; | ||
| + | |||
| + | </pre> | ||
| + | It is not functional but keep it as a placeholder for future. | ||
| + | |||
| + | === Config.json === | ||
| + | create a config.json file in the root to keep config elements in it. ie : | ||
| + | <pre> | ||
| + | { | ||
| + | "loginApiEndpoint": "http://localhost:10795", | ||
| + | "apiEndpoint": "http://localhost:20795" | ||
| + | } | ||
| + | |||
| + | </pre> | ||
| + | |||
| + | |||
| + | == Authentication & Authorisation == | ||
| + | === Login Form === | ||
| + | ==== input.jsx ==== | ||
| + | Instead of repeating input fields and validations etc. we can create an input component and reuse it when we need it. | ||
| + | # create a folder '''common''' | ||
| + | # create a file '''input.jsx''' | ||
| + | The content of input.jsx is | ||
| + | <pre class="brush:js;"> | ||
| + | import React from "react"; | ||
| + | |||
| + | const Input = ({ name, label, value, error, onChange }) => { | ||
| + | return ( | ||
| + | <div className="form-group"> | ||
| + | <label htmlFor={name}>{label}</label> | ||
| + | <input | ||
| + | id={name}` | ||
| + | name={name} | ||
| + | type="text" | ||
| + | className="form-control" | ||
| + | autoFocus | ||
| + | onChange={onChange} | ||
| + | value={value} | ||
| + | /> | ||
| + | {error && <div className="alert alert-danger">{error}</div>} | ||
| + | </div> | ||
| + | ); | ||
| + | }; | ||
| + | |||
| + | export default Input; | ||
| + | |||
| + | </pre> | ||
| + | ==== loginForm.jsx ==== | ||
| + | <pre class="brush:js;"> | ||
| + | import React, { Component } from "react"; | ||
| + | import "./loginForm.css"; | ||
| + | import Input from "../common/input"; | ||
| + | |||
| + | |||
| + | </pre> | ||
| + | === Authentication Service === | ||
| + | Create "'''authService.js'''" in the '''services''' folder | ||
| + | <pre class="brush:js;"> | ||
| + | import http from "./httpService"; | ||
| + | import { loginApiEndpoint } from "../config.json" | ||
| + | |||
| + | const apiEndpoint = loginApiEndpoint + "/api/Login"; | ||
| + | |||
| + | export function login(email, password) { | ||
| + | return http.post(apiEndpoint, {Email:email, Password:password, Group:"Administrator"}) | ||
| + | } | ||
| + | </pre> | ||
Latest revision as of 14:47, 28 December 2018
Starting a project
Setting Up The Environment
- install node
- install create-react-app
npm i -g create-react-app
install VS Code
- install VS Code
- install VS Code snippets - Click Extensions icon on the left sidebar and search and install
- Simple React Snippets by Burke Holland
- Prettier by Esben Petersen
- Set Format on Save
- Auto Import by Sergey Korenuk
in VS Code go to File / Preferences / Settings and add the line below in UserSettings section
"editor.formatOnSave":true
Creating React App
Go to the project folder
npx create-react-app adminportal
cd adminportal
adminportal is lowercase because [Create React App] doesn't like capitals
Installing Bootstrap
npm i bootstrap@4.1.1
go to ./src/index.js and add this line
import 'bootstrap/dist/css/bootstrap.css';
Install reactstrap
npm install --save reactstrap
Check it's site for more details
Installing & Using FontAwesome Icons
npm i --save @fortawesome/fontawesome-svg-core npm i --save @fortawesome/free-solid-svg-icons npm i --save @fortawesome/react-fontawesome
Then in your app, import and add an icon to the Library:App.js
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faIgloo } from '@fortawesome/free-solid-svg-icons'
library.add(faIgloo)
Lastly, use the component and icon in your JSX:
import React from 'react'
import { faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export const Food = () => (
<div>
<FontAwesomeIcon icon={faSearch} />
</div>
)
https://scotch.io/tutorials/using-font-awesome-5-with-react
Install nodemon
it restarts the application when file contents changes.
npm install --save-dev nodemon
Usage
npx nodemon index.js
Coding
Routing
install router
npm i react-router-dom
in index.js file add this line
import { BrowserRouter } from "react-router-dom";
and wrap <App/> with <BrowserRouter> like below
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
in App.js import Route & Switch
import { Route, Switch } from "react-router-dom";
and add these lines to render()
return (
<div className="App">
<TopBar />
<SideBar />
<header className="App-header">
<div className="content">
<Switch>
<Route path="/admin" component={Admin} />
</Switch>
</div>
</header>
</div>
);
So in this scenario there is one top bar and a left side bar. They are static. The rest of the screen (div with className="content") will be different in every page.
Switch is for stopping rendering to go to next Route which matches the satisfying criteria. Like:
if the first route is path="/admin" and the second route is "/" without a switch here it will render both of them back to back.
Links
In sideBar component when I click a link it normalyy reload the whole page as it uses <a> tag to prevent this import Link
import {Link} from "react-router-dom";
and replace <a with <Link and href with to. ie:
<Link to="/product">
Http Client - Preparation
install Axios
npm i axios
install Toastify
npm i react-toastify
in app.js import Toastfy and css file and add a tag for container
import {ToastContainer} from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
render() {
return (
<ToastContainer/> ....
Creating httpService.js
create a folder "services" and within this folder create a file names "httpService.js"
import axios from "axios";
import { toast } from "react-toastify";
import log from "./logService.js";
axios.interceptors.response.use(null, error => {
const expectedError =
error.response &&
error.response.status >= 400 &&
error.response.status < 500;
if (!expectedError) {
logger.log(error);
toast.error("An unexpected error occured.");
}
return Promise.reject(error);
});
export default {
get: axios.get,
post: axios.post,
put: axios.put,
delete: axios.delete
};
here we use interceptor for responses and capture any unexpected errors. display them in a toaster and send a copy to logService which is basically doing nothing at the moment.
logService
function init() {}
function log(error) {
console.log(error);
}
export default {
init,
log
};
It is not functional but keep it as a placeholder for future.
Config.json
create a config.json file in the root to keep config elements in it. ie :
{
"loginApiEndpoint": "http://localhost:10795",
"apiEndpoint": "http://localhost:20795"
}
Authentication & Authorisation
Login Form
input.jsx
Instead of repeating input fields and validations etc. we can create an input component and reuse it when we need it.
- create a folder common
- create a file input.jsx
The content of input.jsx is
import React from "react";
const Input = ({ name, label, value, error, onChange }) => {
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
<input
id={name}`
name={name}
type="text"
className="form-control"
autoFocus
onChange={onChange}
value={value}
/>
{error && <div className="alert alert-danger">{error}</div>}
</div>
);
};
export default Input;
loginForm.jsx
import React, { Component } from "react";
import "./loginForm.css";
import Input from "../common/input";
Authentication Service
Create "authService.js" in the services folder
import http from "./httpService";
import { loginApiEndpoint } from "../config.json"
const apiEndpoint = loginApiEndpoint + "/api/Login";
export function login(email, password) {
return http.post(apiEndpoint, {Email:email, Password:password, Group:"Administrator"})
}