import React, { useState } from 'react';
import { BrowserRouter } from 'react-router-dom';
import './App.css';
function App() {
var [showChat, setShowChat ] = useState('hide');
var [showButton , setShowButton ] = useState('show');
function handleChatShow() {
setShowChat((showChat === "hide") ? "show" : "hide");
setShowButton((showChat === "hide") ? "hide" : "show");
}
return (
<>
<button className={"open-button "+showButton} id="openchat" onClick={handleChatShow}></button>
<div className={"chat_box form-container "+showChat} id="chatbubble">
<button type="button" className="btn cancel" onClick={handleChatShow}>—</button>
Text here
</div>
</>
);
}
export default App;
Basic React Login using External API
API
We will use a free Login API from MeCallAPI.com with the following detail:
- Method: POST
- URL: https://www.mecallapi.com/api/login
- Body (JSON):
{
"username": "karn.yong@mecallapi.com",
"password": "mecallapi"
}
- Response:
{
"status": "ok",
"message": "Logged in",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"fname": "Karn",
"lname": "Yong",
"username": "karn.yong@mecallapi.com",
"email": "karn.yong@mecallapi.com",
"avatar": "https://www.mecallapi.com/users/1.png"
}
}
For more APIs with populated fake data, such as user list and user profile authentication with JWT, these can be found at MeCallAPI.com.
Add packages
We will add the following packages to our application:
- Material UI
- react-router-dom
- Sweetalert
npm i @material-ui/core @material-ui/icons react-router-dom sweetalert
Create screen for Sign-in
Create Sigin.js (From line 41 is the use of login API from MeCallAPI.com)
import React, { useState } from 'react';
import swal from 'sweetalert';
import './Login.css';
async function loginUser(credentials) {
return fetch('https://www.mecallapi.com/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
})
.then(data => data.json())
}
const Login = props => {
const [username, setUserName] = useState();
const [password, setPassword] = useState();
const handleSubmit = async e => {
e.preventDefault();
const response = await loginUser({
username,
password
});
if ('accessToken' in response) {
swal("Success", response.message, "success", {
buttons: false,
timer: 2000,
})
.then((value) => {
localStorage.setItem('accessToken', response['accessToken']);
localStorage.setItem('user', JSON.stringify(response['user']));
window.location.href = "/admin";
});
} else {
swal("Failed", response.message, "error");
}
}
return (
<>
<div id="page-wrap" className="my-4 mx-4">
<h1>Login</h1>
<form noValidate onSubmit={handleSubmit}>
<div className="mb-3">
<label for="email" className="form-label">Email address</label>
<input
required
id="email"
name="email"
className="form-control"
aria-describedby="emailHelp"
onChange={e => setUserName(e.target.value)}
/>
</div>
<div className="mb-3">
<label for="password" className="form-label">Password</label>
<input
id="password"
name="password"
type="password"
className="form-control"
onChange={e => setPassword(e.target.value)}
/>
</div>
<div className="mb-3 form-check">
<input type="checkbox" className="form-check-input" id="exampleCheck1" />
<label className="form-check-label" for="exampleCheck1">Remember me</label>
</div>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</div>
</>
)
}
export default Login;
Now you can update your menu to show correct menu items for logged in/out states:
import React, { lazy } from 'react';
import { Link } from 'react-router-dom';
import './Sidebar.css';
const Menu = lazy(() => import('react-burger-menu/lib/menus/elastic'));
const Sidebar = props => {
const isLoggedIn = localStorage.getItem('accessToken');
const handleLogout = () => {
localStorage.removeItem("accessToken");
localStorage.removeItem("user");
window.location.href = "/";
};
let adminLink, loggedLink;
if (isLoggedIn) {
adminLink = <Link className="menu-item" to="/admin">Admin</Link>;
loggedLink = <div className="menu-item" onClick={handleLogout}>Logout</div>;
} else {
loggedLink = <Link className="menu-item" to="/login">Login</Link>;
}
return (
<Menu>
<Link className="menu-item" to="/">
Home
</Link>
<Link className="menu-item" to="/favourites">
Favourites
</Link>
{adminLink}
{loggedLink}
</Menu>
);
}
export default Sidebar;
Toggle elements inside list of items
Hold ID of each row inside an array that you set when clicked. Use if statement inside JSX to check whether array has the ID value and show selected icon if it does, empty icon if it doesn’t.
const List = props => {
var pagedList = props.pagedList;
const [isFave, setIsFave] = useState([]);
const selectFavourite = e => {
const { id } = e.target;
if(isFave.includes(id)){
setIsFave(isFave.filter(item => item !== id));
}else{
setIsFave([...isFave, id]);
}
}
console.log(isFave);
return (
<>
{pagedList.map((row,i) => {
return (
<div key={i} className="list-card row">
<div className="col-12 col-md g-0 list-content">
<div
className="favouriteButton"
id={row.id_pg}
onClick={selectFavourite}
>
{isFave.includes(row.id_pg) ? <FavoriteIcon /> : <FavoriteBorderIcon /> }
</div>
</div>
</div>
);
})}
</>
);
}
export default List
Check all checkboxes
We get the checkboxes from an array of results called list
. We use one of the array values to set the checked states and hold it in a variable called isCheck
.
All checkboxes in the list have an onChange
function called selectCheckbox
which sets the checkbox ID in the isCheck
array when checked and removes when unchecked.
IMPORTANT: To control the selecting all checkboxes, you need to add achecked
attribute onto each individual checkbox that checks if theisCheck
array includes the current array value.
In the main component below we have the select all checkbox. We have an onChange
function called selectAll
which toggles the true/false value for isCheckAll
variable. Then it sets all checkboxes or empties all checkboxes array of isCheck
depending on the true/false value of isCheckAll
.
import React, { lazy, useEffect, useState } from 'react';
import axios from 'axios';
const MyList = lazy(() => import('../../components/MyList/MyList'));
const YourComponent = props => {
const [isCheckAll, setIsCheckAll] = useState(false);
const [isCheck, setIsCheck] = useState([]);
var [list, setList] = useState([]);
useEffect(() => {
getRows()
}, []);
function getRows() {
let getUrl = 'https://domain.com/getrows.php';
axios({
method: "get",
url: getUrl,
}).then((response) => {
let data = response.data;
setList(data.listRows);
}, (error) => {
console.log(error)
})
}
const selectAll = e => {
setIsCheckAll(!isCheckAll);
setIsCheck(list.map(row => row.id_pg));
if (isCheckAll) {
setIsCheck([]);
}
};
return (
<>
<input
id="selectAllCheckbox"
name="selectAllCheckbox"
type="checkbox"
value=""
className="form-check-input"
onChange={selectAll}
/>
{list.length > 0 &&
<MyList
list={list}
isCheck={isCheck}
setIsCheck={setIsCheck.bind(this)}
/>
}
</>
)
}
export default YourComponent;
Next create the checkboxes list component:
import React from 'react'
import './List.css';
const MyList = props => {
var list= props.list;
const isCheck = props.isCheck;
const selectCheckbox = e => {
const { id, checked } = e.target;
props.setIsCheck([...isCheck, id]);
if (!checked) {
props.setIsCheck(isCheck.filter(item => item !== id));
}
};
console.log(isCheck);
return (
<>
{list.map((row,i) => {
return (
<div key={i} className="row">
<div className="col-1">
<input
id={row.id_pg}
name="select"
type="checkbox"
className="form-check-input"
onChange={selectCheckbox}
checked={isCheck.includes(row.id_pg)}
/>
</div>
<div className="col-11">
{row.name}
</div>
</div>
);
})}
</>
);
}
export default MyList
Rendering raw html with reactjs
dangerouslySetInnerHTML is React’s replacement for using innerHTML in the browser DOM. In general, setting HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) attack.
It is better/safer to sanitise your raw HTML (using e.g., DOMPurify) before injecting it into the DOM via dangerouslySetInnerHTML.
DOMPurify – a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. DOMPurify works with a secure default, but offers a lot of configurability and hooks.
import React from 'react'
import * as DOMPurify from 'dompurify';
const dropdownMenu = `
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-expanded="true">
Dropdown
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Action</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Another action</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Something else here</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Separated link</a></li>
</ul>
</div>
`
const YourComponent = props => {
<div>
{ <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(dropdownMenu) }} /> }
</div>
}
export default YourComponent;
If the HTML is coming from user input, make sure it is sanitized!
Adding Google Analytics 4 (GA4) Tracking to React App
Install the React GA4 library
npm i react-ga4
On the top level initialize the tracking ID:
import React, { Suspense } from 'react';
import { BrowserRouter } from 'react-router-dom';
import Routes from './Routes';
import ReactGA from 'react-ga4';
const TRACKING_ID = "G-XXXXXXXXXX"; // OUR_TRACKING_ID
ReactGA.initialize(TRACKING_ID);
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<BrowserRouter>
<Routes ReactGA={ReactGA} />
</BrowserRouter>
</Suspense>
);
}
export default App;
Track pages on Routes.js, then start the Google Event Tracker:
import React, { useEffect, lazy } from 'react';
import {Route, Routes as Switch, useLocation} from 'react-router-dom';
import GoogleAnalyticsEventTracker from './components/GoogleAnalyticsEventTracker/GoogleAnalyticsEventTracker';
const Home = lazy(() => import('./pages/Home/Home'));
const Page1 = lazy(() => import('./pages/Page1/Page1'));
const Page2 = lazy(() => import('./pages/Page2/Page2'));
const NotFound = lazy(() => import('./pages/NotFound/NotFound'));
const Routes = props => {
const url = useLocation();
useEffect(() => {
document.title = url.pathname;
props.ReactGA.pageview(url.pathname + url.search);
}, [url.pathname]);
const gaEventTracker = GoogleAnalyticsEventTracker('all_custom_events');
return (
<Switch>
<Route path='/' element={<Home gaEventTracker={gaEventTracker.bind(this)}/>} />
<Route path='/page1' element={<Page1 gaEventTracker={gaEventTracker.bind(this)}/>} />
<Route path='/page2/*' element={<Page2 gaEventTracker={gaEventTracker.bind(this)}/>} />
<Route element={<NotFound/>} />
</Switch>
);
}
export default Routes;
Inside your page you’ll add an event for any click events, functions that finish, etc.
import React from 'react'
const Page1 = props => {
const gaEventTracker = props.gaEventTracker;
return (
<button
className="button"
onClick={e => {
gaEventTracker('click_button');
}}
>
Click button
</button>
);
}
export default Page1
How to set state with dynamic key-value pair in React
With a dynamic key-value array you can save all form values without needing to create a separate variable for each form element.
import React, { useEffect, useState } from 'react'
const Form = props => {
const [state, setState] = React.useState({});
function handleFieldChange(event) {
setState({ ...state, [event.target.name]: event.target.value });
}
return (
<div className="form">
<div className="row g-3 mb-3">
<div className="col">
<input name="firstname" onChange={handleFieldChange} />
</div>
<div className="col">
<input name="email" onChange={handleFieldChange} />
</div>
</div>
<div className="row g-3 mb-3">
<div className="col">
<textarea name="message" onChange={handleFieldChange}></textarea>
</div>
</div>
</div>
);
}
export default Form
Update package.json
To update whatever you added or removed to package.json run this:
npm update
Create JSX Project
Create React folder in www with this code:
# Create a project folder
mkdir jsx-modules
cd jsx-modules
# Create an npm project without prompts (-y)
npm init -y
# Install babel
npm install @babel/cli @babel/core @babel/plugin-transform-react-jsx
Next we’ll need a .babelrc
which will tell babel how to process our jsx.
{
"plugins": [
["@babel/plugin-transform-react-jsx", { "pragma": "createElement", "pragmaFrag": "'fragment'" }]
],
"comments": false
}
And then add an index.jsx
file
// A jsx pragma method to create html dom elements
function createElement(tag, props, ...children) {
if (typeof tag === "function") return tag(props, children)
const element = document.createElement(tag)
Object.entries(props || {}).forEach(([name, value]) => {
if (name.startsWith('on') && name.toLowerCase() in window)
element.addEventListener(name.toLowerCase().substr(2), value)
else element.setAttribute(name, value.toString())
})
children.forEach((child) => {
appendChild(element, child)
})
return element
}
const appendChild = (parent, child) => {
if (Array.isArray(child))
child.forEach((nestedChild) => appendChild(parent, nestedChild))
else
parent.appendChild(
child.nodeType ? child : document.createTextNode(child)
)
}
const createFragment = (props, ...children) => {
return children
}
// Setup some data
const name = 'Geoff'
const friends = ['Sarah', 'James', 'Hercule']
// Create some dom elements
const app = (
<div className="app">
<h1 className="title"> Hello, world! </h1>
<p> Welcome back, {name} </p>
<p>
<strong>Your friends are:</strong>
</p>
<ul>
{friends.map(name => (
<li>{name}</li>
))}
</ul>
</div>
)
// Render our dom elements
window.document.getElementById('app').replaceWith(app)
Now you can run this command to generate your javascript.
npx babel index.jsx -d dist
Which will create a new dist/index.js
file with the transpilled code.
You can add
// eslint-disable-next-line no-unused-vars
to ignore annoying eslint errors
And to test our code, add an index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>jsx WITHOUT react</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/light.min.css"
/>
</head>
<body>
<div id="app"></div>
<script src="dist/index.js"></script>
</body>
</html>
I snuck in water.css to make raw html prettier.
Create React Project
Create React folder in www with this code:
npx create-react-app react-modules
Once all the necessary files are installed, change directory into react-modules
and start the application with:
npm start
Build the files ready to go to production:
npm build
We are creating buttons that open a modal window where the content of the modal window changes with each button.
Add this line to dependencies in package.json
"axios": "^0.24.0",
Update packages with this command: npm update
Open App.js
import React from "react";
import axios from "axios";
import "./styles.css";
class App extends React.Component {
state = {
show: false,
content: "",
title: "",
};
removeHTML(string){
const regex = /(<([^>]+)>)/ig;
const result = string.replace(regex, '');
return result;
}
showModal = getContent => {
if (typeof getContent === 'string' || getContent instanceof String){
axios({
method: "get",
url: "http://api.tvmaze.com/search/shows?q="+getContent,
}).then((response) => {
let movies = response.data;
let match = false;
{movies.map((movie,i) => {
if( movie.show.language === "English" && match === false ){
match = true;
this.setState({
title: movie.show.name,
content: this.removeHTML(movie.show.summary)
});
}
})}
}, (error) => {
console.log(error);
});
}else{
this.setState({
content: "",
title: "",
});
}
this.setState({
show: !this.state.show
});
};
render() {
return (
<div className="App">
<button
className="toggle-button centered-toggle-button"
onClick={e => {
this.showModal("The Golden Girls");
}}
>
Golden Girls
</button>
<button
className="toggle-button centered-toggle-button"
onClick={e => {
this.showModal("Jane The Virgin");
}}
>
Jane The Virgin
</button>
{this.state.show && (
<div className="modal" id="modal">
<h2>{this.state.title}</h2>
<div className="content">
{this.state.content}
</div>
<div className="actions">
<button className="toggle-button" onClick={this.showModal}>
close
</button>
</div>
</div>
)}
</div>
);
}
}
export default App;
Add modal css to styles.css
.App {
font-family: sans-serif;
text-align: center;
}
.modal {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: white;
border: 1px solid #ccc;
transition: 1.1s ease-out;
box-shadow: -2rem 2rem 2rem rgba(0, 0, 0, 0.2);
}
.modal h2 {
border-bottom: 1px solid #ccc;
padding: 1rem;
margin: 0;
}
.modal .content {
padding: 1rem;
}
.modal .actions {
border-top: 1px solid #ccc;
background: #eee;
padding: 0.5rem 1rem;
}
.modal .actions button {
border: 0;
background: #78f89f;
border-radius: 5px;
padding: 0.5rem 1rem;
font-size: 0.8rem;
line-height: 1;
}
button.centered-toggle-button {
margin: 0 10px;
}
button {
cursor: pointer;
}