# 10 - Connecting to the backend
## HOC (higher-order component)
A higher-order component is a function that takes a component and returns a new component.
**General concept**
HOC Component
```
// HOC.js
import React, {Component} from 'react';
export default function Hoc(HocComponent){
return class extends Component{
render(){
return (
<div>
<HocComponent>
</HocComponent>
</div>
);
}
}
}
```
App.js file
```
// App.js
import React, { Component } from 'react';
import Hoc from './HOC';
class App extends Component {
render() {
return (
<div>
Higher-Order Component Example
</div>
)
}
}
App = Hoc(App);
export default App;
```
How to use?
```
App = Hoc(App);
```
Now Latest App value will be the final component that returned from HOC function.
Example:
Let us say; we have one component, which just lists the products list. So, first, we are going to create one ProductList.js component.
```
// ProductList.js
import React, { Component } from 'react';
class ProductList extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{
id: 1,
name: 'TCS'
},
{
id: 2,
name: 'Infosys'
},
{
id: 3,
name: 'Reliance'
}
]
};
}
render() {
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell>ID</StyledTableCell>
<StyledTableCell align="left">Name</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{products.map((row) => (
<StyledTableRow key={row.name}>
<StyledTableCell component="th" scope="row">
{row.id}
</StyledTableCell>
<StyledTableCell align="left">{row.name}</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
}
export default ProductList;
```
Users List with same properties and style.
```
// UsersList.js
import React, { Component } from 'react';
class UsersList extends Component {
constructor(props) {
super(props);
this.state = {
users: [
{
id: 1,
name: 'Fadi'
},
{
id: 2,
name: 'Ahmed'
},
{
id: 3,
name: 'Nermen'
}
]
};
}
render() {
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell>ID</StyledTableCell>
<StyledTableCell align="left">Name</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((row) => (
<StyledTableRow key={row.name}>
<StyledTableCell component="th" scope="row">
{row.id}
</StyledTableCell>
<StyledTableCell align="left">{row.name}</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
}
export default UsersList;
```
Now, we need to include one of them as below into the App.js file.
```
// App.js
import React, { Component } from 'react';
import ProductList from './ProductList';
class App extends Component {
render() {
return (
<div>
<ProductList></ProductList>
</div>
)
}
}
export default App;
```
Now if you noticed, If we need to include UsersTable, we will just copy and paste everything!.
What about make our code easier, readable and more well-designed?
let's improve that using HOC.
1- Refactor Users list and products list as below:
UsersTable
Remove list and copy it to app.js, change *products* variable with *props.data*:
```
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell, { tableCellClasses } from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
'&:nth-of-type(odd)': {
backgroundColor: theme.palette.action.hover,
},
// hide last border
'&:last-child td, &:last-child th': {
border: 0,
},
}));
export default function ProductsTable(props) {
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell>ID</StyledTableCell>
<StyledTableCell align="left">Name</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{props.data.map((row) => (
<StyledTableRow key={row.name}>
<StyledTableCell component="th" scope="row">
{row.id}
</StyledTableCell>
<StyledTableCell align="left">{row.name}</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
```
The same for UsersList.
2- App.js will contains list for users and products
```
import logo from './logo.svg';
import './App.css';
import Hoc from './components/HOC'
import ProductsTable from './components/ProductsTable'
import UsersTable from './components/UsersTable'
const ProductsData = [
{
id: 1,
name: 'TCS'
},
{
id: 2,
name: 'Infosys'
},
{
id: 3,
name: 'Reliance'
}
];
const UsersData = [
{
id: 1,
name: 'Ali'
},
{
id: 2,
name: 'Zain'
},
{
id: 3,
name: 'Mohammed'
}
];
const Products = Hoc(
ProductsTable,
ProductsData
);
const Users = Hoc(
UsersTable,
UsersData
);
function App() {
return (
<div className="App">
<Products></Products>
</div>
);
}
export default App;
```
Repo:
https://github.com/ProgFadi/hoc-example.git
## Sending a Request From the Front-End
### Axios
Axios is a Javascript library used to make HTTP requests from node.js or XMLHttpRequests from the browser that also supports the ES6 Promise API.
**Why axios?**
* Make XMLHttpRequests from the browser that support ES6.
* Make http requests from node.js
* Supports the Promise API.
* Intercept request and response
* Transform request and response data
* Cancel requests
* Automatic transforms for JSON data
* Client side support for protecting against XSRF
Syntax:
```
// send a POST request
axios({
method: 'post',
url: 'endpoint',
data: {
firstName: 'Finn',
lastName: 'Williams'
}
});
```
Shorthand methods for Axios HTTP requests
* axios.request(config)
* axios.get(url[, config])
* axios.delete(url[, config])
* axios.head(url[, config])
* axios.options(url[, config])
* axios.post(url[, data[, config]])
* axios.put(url[, data[, config]])
* axios.patch(url[, data[, config]])
For example, we can use .post method instead as below:
```
axios.post('/login', {
firstName: 'Finn',
lastName: 'Williams'
});
```
Axios returns a promise that is either fulfilled or rejected, depending on the response from the backend service.
```
axios.post('/login', {
firstName: 'Finn',
lastName: 'Williams'
})
.then((response) => { // if success
console.log(response);
}, (error) => { // if failed
console.log(error);
});
```
Information that included in the response of axios request:
```
{
// `data` is the response that was provided by the server
data: {},
// `status` is the HTTP status code from the server response
status: 200,
// `statusText` is the HTTP status message from the server response
statusText: 'OK',
// `headers` the headers that the server responded with
// All header names are lower cased
headers: {},
// `config` is the config that was provided to `axios` for the request
config: {},
// `request` is the request that generated this response
// It is the last ClientRequest instance in node.js (in redirects)
// and an XMLHttpRequest instance the browser
request: {}
}
```
Example: Let's see response of a request
```
axios.get('https://api.github.com/users/mapbox')
.then((response) => {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
```
**Sending custom headers with Axios**
Sending custom headers with Axios is very straightforward. Simply pass an object containing the headers as the last argument. For example:
```
const options = {
headers: {'X-Custom-Header': 'value'}
};
axios.post('/save', { a: 10 }, options);
```
**POST JSON with Axios**
Axios automatically serializes JavaScript objects to JSON when passed to the axios.post function as the second parameter. This eliminates the need to serialize POST bodies to JSON.
Axios also sets the Content-Type header to application/json. This enables web frameworks to automatically parse the data.
If you want to send a preserialized JSON string to axios.post() as JSON, you’ll need to make sure the Content-Type header is set.
**Transforming requests and responses**
Although Axios automatically converts requests and responses to JSON by default, it also allows you to override the default behavior and define a different transformation mechanism. This is particularly useful when working with an API that accepts only a specific data format, such as XML or CSV.
To change request data before sending it to the server, set the transformRequest property in the config object. Note that this method only works for PUT, POST, and PATCH request methods.
```
const options = {
method: 'post',
url: '/login',
data: {
firstName: 'Finn',
lastName: 'Williams'
},
transformRequest: [(data, headers) => {
// transform the data
return data;
}]
};
// send the request
axios(options);
```
To modify the data before passing it to then() or catch(), you can set the transformResponse property:
```
const options = {
method: 'post',
url: '/login',
data: {
firstName: 'Finn',
lastName: 'Williams'
},
transformResponse: [(data) => {
// transform the response
return data;
}]
};
// send the request
axios(options);
```
**Intercepting requests and responses
**
HTTP interception is a popular feature of Axios. With this feature, you can examine and change HTTP requests from your program to the server and vice versa, which is very useful for a variety of implicit tasks, such as logging and authentication.
Intercepting looks like:
```
// declare a request interceptor
axios.interceptors.request.use(config => {
// perform a task before the request is sent
console.log('Request was sent');
return config;
}, error => {
// handle the error
return Promise.reject(error);
});
// sent a GET request
axios.get('https://api.github.com/users/mapbox')
.then(response => {
console.log(response.data.created_at);
});
```
Axios also provides a response interceptor, which allows you to transform the responses from a server on their way back to the application:
```
// declare a response interceptor
axios.interceptors.response.use((response) => {
// do something with the response data
console.log('Response was received');
return response;
}, error => {
// handle the response error
return Promise.reject(error);
});
// sent a GET request
axios.get('https://api.github.com/users/mapbox')
.then(response => {
console.log(response.data.created_at);
});
```
**Client-side support for protection against XSRF**
Cross-site request forgery (or XSRF for short) is a method of attacking a web-hosted app in which the attacker disguises himself as a legal and trusted user to influence the interaction between the app and the user’s browser. There are many ways to execute such an attack, including XMLHttpRequest.
Fortunately, Axios is designed to protect against XSRF by allowing you to embed additional authentication data when making requests. This enables the server to discover requests from unauthorized locations. Here’s how this can be done with Axios:
```
const options = {
method: 'post',
url: '/login',
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
};
// send the request
axios(options);
```
## Read the API documentation
### Logging In
**Axios-application tutorial**
**Step1**
create a fresh reach application:
```
npx create-react-app axios-app
```
**Step2**
Install necessary depenencies (packages)
- axios: to make HTTP request to the server.
- react-router-dom: or routing purposes.
- @mui/icons-material, @mui/material, @emotion/react, @emotion/styled
**Step3**
- Enable routing for our application.
- setup the directories, etc.
- Make Drawer as main component.
- Define routes
**Step4**
Make request to a login endpoint using axios
1. Create login page
2. Add login page to the routes
3. Design UI for login page
4. Implemenet simple POST Axios request for logging.
Repo:
https://github.com/ProgFadi/axios-app.git
## local storage API
The local storage API allows developers to interact with the web storage to persist data. In other words, we can save data to the client's computer, where it stays until the user clears the browser's storage.
For React developers—or Single-Page-Application devs in general—this means we can keep the user logged in, store data that has been fetched from an API, or save user settings.
We can use the API to store data in the form of key/value pairs. The functions it provides are the following 5:
```
// Get
localStorage.getItem('dark-mode');
// Set
localStorage.setItem('dark-mode', true);
// Remove
localStorage.removeItem('dark-mode');
// Remove all keys
localStorage.clear()
// Get n-th key name
localStorage.key(2);
```
Example, init default value of a state from local storage:
```
const [dark, setDark] = React.useState(
localStorage.getItem('dark-mode') === 'true'
);
```
Save state to local storage:
```
React.useEffect(() => {
localStorage.setItem('dark-mode', dark);
}, [dark]);
```
Full Example:
```
const App = () => {
const [dark, setDark] = React.useState(localStorage.getItem('dark-mode') === 'true');
React.useEffect(() => {
localStorage.setItem('dark-mode', dark);
}, [dark]);
const toggleDarkMode = () => {
setDark(!dark);
};
return (
<div className={`app-wrapper ${dark ? 'dark' : ''}`}>
<button className="dark-mode-toggle" onClick={toggleDarkMode}>
<img src="https://felixgerschau.com/logos/lightbulb.svg" height="100px" width="100px"/>
<h2>Click me!</h2>
</button>
</div>
);
};
```
**Storing objects in local storage**
Example:
```
localStorage.setItem('user', JSON.stringify({ name: 'Felix' }));
```
**Get object from local storage:**
```
const user = JSON.parse(localStorage.getItem('user'));
```
## Routes Protection
Our SPA may have several routes as following:
- Auth Routes like (Login, signup), these routes should be accessible via Guests and authorized users.
- Guests Routes like(Privacy policy, About, Plans)
- Protected Routes like(Profile, Managements, etc)
So we need to make some of them available for guests users, and some of them should be accessible by authorized users only.
First we need to re-design tree of components for our application to match our idea which is:
- Drawer Component will be the default component for all the routes ('/products','dashboard', 'categories') like following:
```
return <Routes>
<Route path="/dashboard" element={<Drawer {...props} data={data} />} />
<Route path="/categories" element={<Drawer {...props} />} />
<Route path="/products" element={<Drawer />} />
<Route path="/login" element={<Login />} />
<Route path="/" element={<Login />} />
</Routes>
```
- Add *useLocation* hook function at the start of Drawer function to get current pathname in each rendering.
```
const location = useLocation()
```
- Add *renderContent* function to Drawer component to be used for deciding which component (page) should be rendered in the main content of Drawer
Function Implementation
```
const renderContent = (routeName)=>{
console.log(routeName)
switch(routeName)
{
case '/login':
return <Login/>
case '/products':
return <Products/>
case '/dashboard':
return <Dashboard/>
case '/categories':
return <Categories/>
}
}
```
Function Usage
```
<Main open={open}>
<DrawerHeader />
{renderContent(location.pathname)}
</Main>
```
- Seperate Login component (page) alone outside application routes to make login page appear without appBar and sideNav.
Let's update on our previous example with a simple way and make the following routes(pages) protected (Dashboard, Categories, Products) and leave login route accessible for anyone except logged user.
Repo: https://github.com/ProgFadi/axios-app.git
We need to add the following checking logic code for each component that we eant to protect
```
const [isLogged, setIsLogged] = useState(true)
useEffect(() => {
let userData;
try {
console.log('read')
userData = JSON.parse(localStorage.getItem('token'))
if (userData)
setIsLogged(true)
else
setIsLogged(false)
}
catch (error) {
console.log('error')
setIsLogged(false)
}
})
if (!isLogged)
return <Navigate to="/login" />
```
### logout
To implement this functionality, we just have to remove token from local storage with following code:
```
localstorage.removeItem('token');
```
by doing this, we will throw responsibility for logging out on each component. After removing stored token, each component will check stored token and once it doesn't find it, then it will redirect the user to login page.
Let's create a logout component at Appbar component with logout functionality:
1. Create avatar component.
```
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Stack from '@mui/material/Stack';
export default function ImageAvatars() {
return (
<Stack direction="row" spacing={2}>
<Avatar alt="Remy Sharp" src="/static/images/avatar/1.jpg" />
</Stack>
);
}
```
https://mui.com/components/avatars/
**Note:** Don't forget to attach asset file (1.jpg) image into the project at */assets/images* directory
Now, let's add avatar component to the far right of App Bar. But you will notice that avatar will be stacked to the left of App Bar! simply we can use Box component of MUI to help us justify it's position.
https://mui.com/components/box/
**Adding menu**
We need to create a menu that shows/hids according to click on avatar, thie menu simply have a *logout* button icon to be used by the user to logout from the application.
https://mui.com/components/app-bar/#app-bar-with-responsive-menu
Workaround with menu code and move it to your AppBar. in addition add a state value to handle opening and closing Menu relatively to avatar.
```
const [anchorElUser, setAnchorElUser] = React.useState(null);
```
Complete code of drawer component:
```
import * as React from 'react';
import { useEffect } from 'react'
import { styled, useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Drawer from '@mui/material/Drawer';
import CssBaseline from '@mui/material/CssBaseline';
import MuiAppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import List from '@mui/material/List';
import Typography from '@mui/material/Typography';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import ListItem from '@mui/material/ListItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import InboxIcon from '@mui/icons-material/MoveToInbox';
import MailIcon from '@mui/icons-material/Mail';
import { Routes, Link, Route, useLocation, useNavigate } from 'react-router-dom'
import Dashboard from '../pages/Dashboard'
import Categories from '../pages/Categories'
import Products from '../pages/Products'
import LogoutIcon from '@mui/icons-material/Logout';
import GridViewIcon from '@mui/icons-material/GridView';
import CategoryIcon from '@mui/icons-material/Category';
import ProductionQuantityLimitsIcon from '@mui/icons-material/ProductionQuantityLimits';
import Login from '../pages/Login'
import Avatar from './Avatar'
import MenuItem from '@mui/material/MenuItem';
import Menu from '@mui/material/Menu';
import Tooltip from '@mui/material/Tooltip';
const drawerWidth = 240;
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(
({ theme, open }) => ({
flexGrow: 1,
padding: theme.spacing(3),
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
marginLeft: `-${drawerWidth}px`,
...(open && {
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
marginLeft: 0,
}),
}),
);
const AppBar = styled(MuiAppBar, {
shouldForwardProp: (prop) => prop !== 'open',
})(({ theme, open }) => ({
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
...(open && {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: `${drawerWidth}px`,
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
}),
}));
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: 'flex-end',
}));
function PersistentDrawerLeft(props) {
const location = useLocation()
const navigate = useNavigate()
const theme = useTheme();
const [open, setOpen] = React.useState(false);
const [anchorElUser, setAnchorElUser] = React.useState(null);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
const logout = () => {
localStorage.removeItem('token')
props.history('/login')
handleCloseUserMenu()
}
useEffect(() => {
}, [])
const renderContent = (routeName) => {
console.log(routeName)
switch (routeName) {
case '/login':
return <Login />
case '/products':
return <Products />
case '/dashboard':
return <Dashboard />
case '/categories':
return <Categories />
}
}
const handleOpenUserMenu = (event) => {
setAnchorElUser(event.currentTarget);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
};
return (
<Box sx={{ display: 'flex' }}>
<CssBaseline />
<AppBar position="fixed" open={open}>
<Toolbar >
<Box sx={{
flexGrow: 1,
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between'
}}>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
sx={{ mr: 2, ...(open && { display: 'none' }) }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
</Typography>
<Tooltip title="Logout">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar alt="Remy Sharp" src="/static/images/avatar/2.jpg" />
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
<MenuItem key='Logout' onClick={logout}>
<Typography textAlign="center">Logout</Typography>
</MenuItem>
</Menu>
</Box>
</Toolbar>
</AppBar>
<Drawer
sx={{
width: drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: drawerWidth,
boxSizing: 'border-box',
},
}}
variant="persistent"
anchor="left"
open={open}
>
<DrawerHeader>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'ltr' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
</IconButton>
</DrawerHeader>
<Divider />
<List>
<ListItem component={Link} to="/dashboard" key='dashboard'>
<ListItemIcon>
<GridViewIcon />
</ListItemIcon>
<ListItemText primary='Dashboard' />
</ListItem>
<ListItem component={Link} to="/categories" key='categories'>
<ListItemIcon>
<CategoryIcon />
</ListItemIcon>
<ListItemText primary='Categories' />
</ListItem>
<ListItem component={Link} to="/products" key='products'>
<ListItemIcon>
<ProductionQuantityLimitsIcon />
</ListItemIcon>
<ListItemText primary='Products' />
</ListItem>
</List>
<Divider />
<List>
<ListItem component={Link} to="/login" key='logout'>
<ListItemIcon>
<LogoutIcon />
</ListItemIcon>
<ListItemText primary='Logout' />
</ListItem>
</List>
</Drawer>
<Main open={open}>
<DrawerHeader />
{renderContent(location.pathname)}
</Main>
</Box>
);
}
export default PersistentDrawerLeft
```
## Constants
It is a good idea to sperate constants in a seperated file, by doing this we can ensure that vars cannot updated by mistake during development, in addition this will help us to write a well-design code, keep it clean and easy to maintenance.
1. Create a file named "Constants.js" that contains constants variable like BaseURL, endpoints, etc.
2. Export each constant variable with following keyword
`export const BASE_URL = '127.0.0.0:8000'`
3. Try to keep all constants names in capital letter.
Example:
```
export const THEMES = {
LIGHT: 'LIGHT',
ONE_DARK: 'ONE_DARK'
};
```
## MUI Customization
https://mui.com/customization/how-to-customize/