Show/Hide by toggling class

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:

{
    "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 a checked attribute onto each individual checkbox that checks if the isCheck 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

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;
}