Table of contents
- Resources
- axios sent two times request in backend in Effects
- Implement automatic login after a period of inactivity in a React app
- How to download file by hit download button
- How to create a custome context
- format HTML text
- How to write logEdit in your project
- JS array delete duplicate element
- JavaScript async function
- Subcomponent cannot show up content correctly
- Connect to database directly without hitting a button
- development and test port share the same ones
Resources
axios sent two times request in backend in Effects
If you use
Implement automatic login after a period of inactivity in a React app
To implement automatic login after a period of inactivity in a React app, you can use a combination of local storage or cookies, a timer, and an authentication mechanism. Here’s a step-by-step guide:
- Authentication Setup: Authentication Mechanism: Choose an authentication method like JWT (JSON Web Tokens), OAuth, or a custom backend solution. Login/Logout: Implement login and logout functionality. Store the authentication token (e.g., JWT) in local storage or a secure cookie upon successful login.
- Implementing the Inactivity Timer
import React, { useEffect, useState } from 'react';
function App() {
const [lastActivity, setLastActivity] = useState(Date.now());
useEffect(() => {
const handleActivity = () => {
setLastActivity(Date.now());
};
window.addEventListener('mousemove', handleActivity);
window.addEventListener('keydown', handleActivity);
return () => {
window.removeEventListener('mousemove', handleActivity);
window.removeEventListener('keydown', handleActivity);
};
}, []);
useEffect(() => {
const inactivityTimeout = setTimeout(() => {
// Perform logout or redirect to login
}, 300000); // 5 minutes in milliseconds
return () => clearTimeout(inactivityTimeout);
}, [lastActivity]);
// ... rest of your component logic
}
- Handling Auto-Login Check Token: On app load, check if a valid authentication token exists in local storage or cookie. Automatic Login: If a valid token is found, automatically log the user in. You may want to display a notification or a “Welcome back” message.
- Security Considerations Secure Storage: Ensure that you store the authentication token securely. Use HttpOnly and Secure flags for cookies. Token Expiration: Set an appropriate expiration time for the authentication token. Session Timeout: Implement a session timeout on the backend to invalidate tokens after a certain period of inactivity.
import React, { useEffect } from 'react';
import jwtDecode from 'jwt-decode';
function App() {
useEffect(() => {
const token = localStorage.getItem('authToken');
if (token) {
try {
const decodedToken = jwtDecode(token);
if (decodedToken.exp * 1000 > Date.now()) {
// User is logged in, perform necessary actions
} else {
// Token has expired, redirect to login
}
} catch (error) {
// Invalid token, redirect to login
}
}
}, []);
// ... rest of your component logic
}
Important Considerations User Experience: Clearly communicate to the user that they will be automatically logged in after a period of inactivity. Security: Prioritize security when implementing automatic login functionality. Consider using HTTPS and implementing appropriate security measures.
How to download file by hit download button
When you’d like to hit a button and then show up a screen to download a file. Here’s a example
Backend
import tmpfile
from flask import send_file,make_response
@bp.route('/download/<reportID>', methods=['POST'])
def download(reportID):
pathFolder= tempfile.mkdtemp(prefix='generateReport_' ,dir=pathTemp)
fd, pathFile = tempfile.mkstemp(prefix=f'{caseNo}_',suffix='.docx',dir=pathFolder)
pathFolder = pathlib.Path(pathFolder) # TODO: fix
# list_files = list(pathFolder.glob('*.xlsx'))
# stream = BytesIO()
# with ZipFile(stream, 'w') as zf:
# for file in list_files:
# zf.write(file, file.name)
# stream.seek(0)
# TODO: save the data to MongoDB database
response = make_response( send_file(
# stream,
pathFile,
as_attachment=True,
# download_name=f'archive_{utils.get_timeStamp()}.zip'
)
)
response.headers['pathFolder'] = str(pathFolder.name)
response.headers['filename'] = f'{caseNo}_{utils.get_timeStampForName()}.docx'
return response
Frontend
const handleClickGenerateReport = () =>{
axios(
{
url: `/api/report/download/${reportID}`, //your url
method: 'POST',
data: formData, // this can be deleted
responseType: 'blob', // important
})
.then((response) => {
console.log('response data is :', response)
const pathFolder = response.headers.pathFolder;
const filename = response.headers.filename;
const href = URL.createObjectURL(response.data);
// create "a" HTML element with href to file & click
const link = document.createElement('a');
link.href = href;
link.setAttribute('download',filename); //or any other extension
// link.setAttribute('download', 'archieve.zip'); //or any other extension
document.body.appendChild(link);
link.click();
// clean up "a" element & remove ObjectURL
document.body.removeChild(link);
URL.revokeObjectURL(href);
//POST /report/generateReport/667450b95d141978d7bcc016
axios.delete('/api/report/deleteTemp', {
params: { folderName:pathFolder },
})
.then(response => {
console.log(response.data);
})
.catch(error => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
})
.catch(err => console.warn('Error: ',err));
}
How to create a custome context
import * as React from "react";
import { useLocation, Navigate } from "react-router-dom";
import axios from "axios";
const authContext = React.createContext();
function useProviderAuth() {
//Do something and get some information here and return the information in a dictionary style.
return {
authed,
user,
login,
logout,
};
}
// make the context be available to the children, thus in the main.js we could just embrace the component with this Authprovider <AuthProvider> </ComponentName> </AuthProvider>
export function AuthProvider({ children }) {
const auth = useProviderAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}
// in the children components, you can import the useAuth to get the context. Here it's actually finishe the work React.useContext(authContext) here, and you don't need to do it in the componet.
export default function useAuth() {
return React.useContext(authContext);
}
Here’s an example about authenication of account
import * as React from "react";
import { useLocation, Navigate } from "react-router-dom";
import axios from "axios";
const authContext = React.createContext();
function useProviderAuth() {
const [authed, setAuthed] = React.useState(false);
const [user,setUser] = React.useState(undefined);
// confirm at backend side and and change the state in the login function
const login = async (email, password) => {
console.log('email is: ',email, 'password is: ',password)
const formData = new FormData();
formData.append('email', email);
formData.append('password', password);
try {
const res = await axios.post('/api/auth/login',formData);
console.log(res)
localStorage.setItem('token',res.data['access_token']);
setAuthed(true);
setUser(res.data['user']); // user is a dictionary that contains : username, email, role
console.log('res.data is : ',res.data, 'authed is : ',authed, 'user is: ',user);
} catch (error){
console.log('Error: ',error);
alert(error.response.data['msg'])
}
}
// change the state in the logout function
const logout = async() => {
try{
const res = await axios.post("/api/auth/logout");
localStorage.removeItem("token");
setAuthed(false);
setUser(null)
} catch (error){
console.log('Error: ',error);
}
}
// the context that you need about authenication
return {
authed,
user,
login,
logout,
};
}
export function AuthProvider({ children }) {
const auth = useProviderAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}
export default function useAuth() {
return React.useContext(authContext);
}
export function RequireAuth({ children }) {
const { authed } = useAuth();
const location = useLocation();
return authed === true ? (
children
) : (
<Navigate to="/auth/login" replace state= />
);
}
export function RequireAdmin({ children }) {
const { user,authed } = useAuth();
const location = useLocation();
if (authed){
if (user.role === 'admin'){
return children;
}
else{
return (
<>
<p>You do not have permission for this function</p>
</>
)
}
}
else {
<Navigate to="/auth/login" replace state= />
}
// return authed === true ? (
// children
// ) : (
// <Navigate to="/auth/login" replace state= />
// );
}
format HTML text
How to write HTML text using the original mark.
How to write logEdit in your project
We need to write the following fileds in the logEdit to cover the essential information:
- Who
- When
- What
e.g.
from datetime import datetime
logEdit = [
{
'editor':editor,
'date':datetime.now(),
'act':'init',
'category':'',
'before':'',
'after':'',
}
]
JS array delete duplicate element
Using reducer to sort js array:
onst listTag = listJob.reduce((accumulator,item) => {
const listTag = item['listTag'];
listTag.forEach( item => {
if(!accumulator.includes(item)){
accumulator.push(item);
}
})
return accumulator;
},[])
please note that: []
is the initial value of the accumulator
. Thus in this case, the accumulator is a empty list. So inside the function. You handle the accumulaotr.
JavaScript async function
Sometimes we’d like to fetch data from database using an API. We need to use async functions
or Promise functions
async function and Promise function
function resolveAfter2Seconds() {
return new Promise((resolve) => {
setTimeout(() => {
const res = 3+4;
resolve(['resolvedddd',res]);
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// Expected output: "resolved"
return 'Get the result'
}
In the above code, if you use Promise inside the function, then you shouldn’t add async
keyword before the function
Subcomponent cannot show up content correctly
That’s because there’s no key in the subcomponent, you should provide the key in the subcomponent to make sure the content is correct. React use key to check whether this component should be rendered. If you use MongoDB, then the _id is a good choice to be the key.
Connect to database directly without hitting a button
Sometimes we’d like show some database content directly when we go into the web page not after hitting the button. we can use useEffect
hook to do that. The logic is to use async function. Here you should also use setLoading
state to let the front know when your data is ready.
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const res = await axios.get('/path/get');
// console.log('resonpose is :',res.data)
setList(res.data);
} catch (error) {
console.error(error.message);
}
setLoading(false);
}
fetchData();
}, []);
Besides that, you can customize this function into a state.
development and test port share the same ones
Here’s a situation I ecountered:
I used Vite to generate a react app,which will use Tailwind css and Bootstrap to decorate the elements. After I created the frontend and backend, I used docker to contain the app. And use docker-copose.yml file to launch the app. I found that if use the follwoing docker-compose.yml file
version : '3'
services:
report_backend:
container_name : report_backend
restart: always
build: ./backend
tty: true
ports:
- "1111:1111"
command: python run.py
volumes:
- /path/from:/path/to
report_frontend:
container_name: report_frontend
restart: always
build: ./frontend
ports:
- "2222:2222"
command: npm run dev
depends_on:
- report_backend
# report_mongodb:
# container_name: report_mongodb
# restart: always
# build: ./mongodb_data
# ports:
# - "3333:3333"
# volumes:
# - docker-data:/data/db
If I use docker-compose up --build -d
to launch this docker compose file, then when I test the web locally, I need to change the ports to avoid the ports that have been used by the docker.
Here’s a important note: because I have used the MongoDB
data database in another app, I can use it directly instead of create a new one container for MongoDB database.
In order to test and launch docker without changing ports, I changed the docker-compose.yml file and vite configure file as below.
# docker-compose.yml file
version : '3'
services:
report_backend:
container_name : report_backend
restart: always
build: ./backend
tty: true
ports:
- "1011:1111"
command: python run.py
volumes:
- /path/from:/path/to
report_frontend:
container_name: report_frontend
restart: always
build: ./frontend
ports:
- "2022:2222"
command: npm run dev
depends_on:
- report_backend
# report_mongodb:
# container_name: report_mongodb
# restart: always
# build: ./mongodb_data
# ports:
# - "3333:3333"
# volumes:
# - docker-data:/data/db
# vite.config.js file
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// const path = require('path');
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: true,
port : 2222,
proxy: {
'/api': {
target: 'ipAddress:1111',
changeOrigin: true,
secure: false,
ws: true,
rewrite: (path) => path.replace(/^\/api/, ""), # all the path from backend will add api,
}
}
},
server2: {
host: true,
port : 2022,
proxy: {
'/api': {
target: 'ipAddress:1011',
changeOrigin: true,
secure: false,
ws: true,
rewrite: (path) => path.replace(/^\/api/, ""),
}
}
},
})
Actually, the main idea is that, I created two servers in the vite config file. Server1 for the local test that use the ports 2222 for fronted, 1111 for backend. Server2 that uses ports 2022 for frontend, 1011 for backend.
And in the docker-compose.yml file, I also changed the maping of machine_port:container_port from 2222:2222 to 2022:2222, Thus the container use its namespace 2222, but the machine use 2022. thus you can use 1pAddress:2022 to show the frontend.
the backend ports mapping is changed from 1111:1111 to 1011:1111. the container use 1111 in its namespace, while the machine use 1011. That’s the reason why in the vite config file we set the server2
server2: {
host: true,
port : 2022,
proxy: {
'/api': {
target: 'ipAddress:1011', # after mapping, this is the machine ports use
changeOrigin: true,
secure: false,
ws: true,
rewrite: (path) => path.replace(/^\/api/, ""),
}
}
},