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 JavaScript Accordion

JavaScript:

const accordionSections = document.querySelectorAll(".accordion");
accordionSections.forEach((accordion) => {
	accordion.onclick = function () {
		this.classList.toggle("is-open");

		let content = this.getElementsByClassName("accordion-content")[0];;
		//console.log(content);
		if (content.style.maxHeight) {
		  //this is if the accordion is open
		  content.style.maxHeight = null;
		} else {
			//if the accordion is currently closed
			content.style.maxHeight = content.scrollHeight + "px";
			//console.log(content.style.maxHeight);
		}
	};
});

HTML

<link rel='stylesheet'  href='https://fonts.googleapis.com/icon?family=Material+Icons' media='all' />
<div class="accordion">
	<div class="summary">Title 1</div>							
	<div class="accordion-content">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque sed eros ac leo ultricies interdum sit amet et nunc.</div>											</div>
<div class="accordion">
	<div class="summary">Title 2</div>							
	<div class="accordion-content">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque sed eros ac leo ultricies interdum sit amet et nunc.</div>											</div>
<div class="accordion">
	<div class="summary">Title 3</div>							
	<div class="accordion-content">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque sed eros ac leo ultricies interdum sit amet et nunc.</div>											</div>

CSS:

.accordion {
    cursor: pointer;
    position: relative;
}
.accordion:before {
    font-family: "Material Icons";
    content: "\e5e1";
    display: inline-block;
    position: absolute;
    top: 22px;
}
.accordion .summary {
    padding-left: 24px;
}
.accordion.is-open:before {
    transform: rotate(90deg);
}
.accordion:hover,
.accordion.is-open {
}
.accordion-content {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.2s ease-in-out;
}

Animate elements if visible in viewport (page scroll)

Using IntersectionObserver API

The IntersectionObserver API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.

Here’s an example that triggers a function when an Element is in viewport:

const inViewport = (entries, observer) => {
	entries.forEach(entry => {
		if(entry.isIntersecting && entry.target.id=="php"){
			php.setValueAnimated(9, 3);
		}
	});
};

const Obs = new IntersectionObserver(inViewport);
const obsOptions = {}; //See: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options

// Attach observer to every [data-inviewport] element:
const ELs_inViewport = document.querySelectorAll('.gauge-container');
ELs_inViewport.forEach(EL => {
	Obs.observe(EL, obsOptions);
});

Here’s an example that triggers a classList toggle when an Element is in viewport:

const inViewport = (entries, observer) => {
  entries.forEach(entry => {
    entry.target.classList.toggle("is-inViewport", entry.isIntersecting);
  });
};

const Obs = new IntersectionObserver(inViewport);
const obsOptions = {}; //See: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options

// Attach observer to every [data-inviewport] element:
const ELs_inViewport = document.querySelectorAll('[data-inviewport]');
ELs_inViewport.forEach(EL => {
  Obs.observe(EL, obsOptions);
});
[data-inviewport] { /* THIS DEMO ONLY */
  width:100px; height:100px; background:#0bf; margin: 150vh 0; 
}

/* inViewport */

[data-inviewport="scale-in"] { 
  transition: 2s;
  transform: scale(0.1);
}
[data-inviewport="scale-in"].is-inViewport { 
  transform: scale(1);
}

[data-inviewport="fade-rotate"] { 
  transition: 2s;
  opacity: 0;
}
[data-inviewport="fade-rotate"].is-inViewport { 
  transform: rotate(180deg);
  opacity: 1;
}
Scroll down...
<div data-inviewport="scale-in"></div>
<div data-inviewport="fade-rotate"></div>