How to add an interactive tutorial to a website with React

Clock
29.08.2024
Clock
15.04.2025
Clock
4 minutes
An eye
206
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
Tags: React Search result parser series Frontend

Intro, or for what reason you should bother creating interactive tutorials

Hello again. In this article, you will learn how to quickly and easily create an interactive tutorial for your React tool. And the first question I would ask is: is it even necessary?
And indeed, if the functionality of the tool is as simple and intuitive as possible, then there is no need to bother with a tutorial. Ideally, this is what you need to strive for, so that the user starts using your tool right away.
But as it happens in real life, not all tools are easy-to-understand, and many have large and complex functionality. Take the following web tools, for example:
  1. Topvisor
  2. Google Analytics
You can't just jump into them and use them. First, you have to learn how to use them. In my case, the tool turned out to be not very intuitive. Therefore, we will make an embedded tutorial on the site.

Creating new events for launching a tutorial

We already have a button to call up a chain of hints.
Now we need to set it up so that this chain works. We will do this by creating our own event.

Before

export default function Header() {
const header_buttons = document.getElementById('meta-header')
const btns = []
for ( const btn of header_buttons.children){
const ref = btn.firstElementChild.getAttribute('href')
if (btn.dataset.type == 'inner-link'){
btns.push(<Button color='primary'><a href={ref}>{btn.innerText}</a><LinkIcon className='mb-2' fontSize='small' /></Button>)
}
else if (btn.dataset.type == 'tutorial' ){
btns.push(<Button color='primary'><a href={ref}>{btn.innerText}</a><QuestionMarkIcon className='mb-2' fontSize='small'/></Button>)
}

After

export default function Header() {
const header_buttons = document.getElementById('meta-header')
const btns = []
for ( const btn of header_buttons.children){
const ref = btn.firstElementChild.getAttribute('href')
if (btn.dataset.type == 'inner-link'){
btns.push(<Button color='primary'><a href={ref}>{btn.innerText}</a><LinkIcon className='mb-2' fontSize='small' /></Button>)
}
else if (btn.dataset.type == 'tutorial' ){
btns.push(<Button id='onTutorial' onClick={(e)=>{
const onTutorialEvent = new Event('onTutorial')
e.currentTarget.dispatchEvent(onTutorialEvent)
}} color='primary'><a href={ref}>{btn.innerText}</a><QuestionMarkIcon className='mb-2' fontSize='small'/></Button>)
}

We added an ID to the button + our own click event.

Creating a new component, Tutorial.js

Now let's create a new component. In the src/components directory, create a file Tutorial.js. Then add the following code there:
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import Popover from '@mui/material/Popover';


export default function Hint({children, anchor}) {
const [anchorEl, setAnchorEl] = React.useState(null);
const [counter, setCounter] = React.useState(1);
const [message, setMessage] = React.useState('');


const clearAllHints = () => {
const hints = document.querySelectorAll('.tutorial_hint')
hints.forEach( (hint_target) => {
hint_target.style = ''
if (hint_target.classList.contains('to_be_hidden')){
hint_target.classList.add('hidden')
hint_target.classList.remove('to_be_hidden')
}
})
}


const displayNextHint = () => {
const hints = document.querySelectorAll('.tutorial_hint')
hints.forEach( (hint_target) => {
if (parseInt(hint_target.dataset.queue) == counter){
hint_target.style = 'background-color: orange'
if (hint_target.classList.contains('hidden')){
hint_target.classList.remove('hidden')
hint_target.classList.add('to_be_hidden')
}
var hint_msg = document.getElementById(`tutorial_hint_${hint_target.dataset.queue}`)
setMessage(hint_msg.innerText)
setAnchorEl(hint_target)
}
})
setCounter(counter + 1)
}


const handleClick = (event) => {
clearAllHints()
displayNextHint()
};


let tutorial_button = document.getElementById('onTutorial')
tutorial_button.addEventListener('onTutorial', handleClick, {once: true})


const handleClose = () => {
const hints = document.querySelectorAll('.tutorial_hint')
if (hints.length < counter){
setMessage('')
setCounter(1)
setAnchorEl(null);
clearAllHints()
}
else{
clearAllHints()
displayNextHint()
}
};


const open = Boolean(anchorEl);
const id = open ? 'simple-popover' : undefined;


return (
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
{message}
</Popover>
);
}


const popover = document.getElementById('popover-tutorial');
const root = createRoot(popover);
root.render(<Hint></Hint>);

The essence of this code boils down to searching for marked elements with the tutorial_hint class and rendering a hint next to this element. The function that is worth paying attention to is displayNextHint. Having found all the marked elements, it finds the element associated with it and takes the hint text from there. Then it selects the hint element and shows the hint itself.
In order for the component to be able to render, we will add another element to app.html, right before the app_settings element.
<div id="popover-tutorial"></div>
And we will connect the new component to index.js

Mark the required items for hints

There is only one thing left to do. Mark the required items for hints.

File AppActions.js

export default function AppActions(){
const [isModal, setModal] = React.useState(false);
const req = {'setModal': setModal}
return (
<Box className="flex gap-1">
<div id="tutorial_hint_4" className="hidden"> Optional: Then you can save all of your configurations</div>
<IconButton id='onSaveRequest' onClick={()=>{setModal(true); waitTillModalIsUp(SaveRequest, req)}} className='w-fit tutorial_hint' data-queue="4"><SaveIcon className=" border-2 rounded-md"/></IconButton>
<Modal
open={isModal}
onClose={()=>{setModal(false)}}
>
<Box id='utils-modal' className="absolute top-1/2 left-1/2 -translate-x-2/4 -translate-y-2/4 shadow-md p-4 bg-white">
</Box>
</Modal>
<div id="tutorial_hint_3" className="hidden">Step 3: And now you can gather all informatino from SERP results</div>
<IconButton id='onStartParsing' data-queue="3" onClick={()=>{StartParsingRequest(req)}} className='w-fit tutorial_hint'><NotStartedIcon className=" border-2 rounded-md"/></IconButton>
</Box>
)
}

File AppQueries.js

export default function AppQueries(){
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEngine}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<List sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
{engine_list}
</List>
</Popover>
<div id="tutorial_hint_2" className="hidden">Step 2: Choose an required engine to parse. And then type your query</div>
<IconButton onClick={(ev)=>handleClick(ev,engine_list)} className='w-fit tutorial_hint' data-queue="2"><AddBoxIcon/></IconButton>
</div>
)
}

File AppSettings.js

export default function AppSettings(){
...
return (
<div>
<Paper elevation={2} className="z-10">
<div id="tutorial_hint_1" className="hidden" >Step 1: Check at least one checkbox, to choose what to save</div>
<div id="settings_content" className='hidden'>
<SettingsContent />
</div>
</Paper>
{/* Icon button is gonna be changed */}
<IconButton onClick={ToggleSettings} className='w-fit tutorial_hint' data-queue="1">
{ isSettings ? <CloseIcon className=" border-2 rounded-md"/>
: <SettingsApplicationsIcon className=" border-2 rounded-md"/>}
</IconButton>
</div>
)
}

File AppUtils.js

export default function AppUtils(){
...
return (
<Box className="flex flex-col">
<Box className="flex gap-1">
<div id="tutorial_hint_5" className="hidden">Here you can find the most popular and ready-to-use presets</div>
<IconButton onClick={ToggleTrending(true)} className='w-fit border tutorial_hint' data-queue="5"><TrendingUpIcon className=" border-2 rounded-md"/></IconButton>
<div id="tutorial_hint_6" className="hidden">Here you will find your own presets</div>
<IconButton onClick={ToggleOwnSaves(true)} className='w-fit tutorial_hint' data-queue="6"><SavedSearchIcon className=" border-2 rounded-md"/></IconButton>
<div id="tutorial_hint_7" className="hidden">Here you will see the actual proccess of gathering information</div>
<div id="console-button" className='hidden tutorial_hint' data-queue="7"><IconButton onClick={ToggleConsole(true)} className='w-fit'><FeaturedPlayListIcon className=" border-2 rounded-md"/></IconButton></div>
<div id="tutorial_hint_8" className="hidden">It is a link to a results of parsing proccess</div>
<div id="results-button" className='hidden tutorial_hint' data-queue="8"><IconButton id="results-button-ref" href='#' onClick={()=>{}} className='w-fit'><DownloadIcon color='warning' className=" border-2 rounded-md"></DownloadIcon></IconButton></div>
</Box>
...
}

Conclusions or how it is supposed to look

As a result, it will all look something like this:
You can also poke around on the site yourself.
If you missed everything above and just want to get the project in its current state, you can download it here. In the next article, we will create the ability for users to log in and register on our site.

Comments

(0)

captcha
Send
Response for
>
It's empty now. Be the first (o゚v゚)ノ

Other

Similar articles


Series of articles about creating and promoting SearchResultParser | Tim the webmaster

Clock
16.07.2024
An eye
276
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
This is an article that is going to introduce you to my new project/webtool, SearchResultParser. Also, from this article, you can navigate to any interesting article for you. See them …

Developing frontend part of a website with React on Django | SearchResultParser p. 2

Clock
16.08.2024
An eye
334
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
I show and tell how to develop a frontend for a site on React with a backend on django. I use MaterialUI and TailwindCSS, with source code and comments.

How to implement localization and translation for django website (python, js, templates and models) p. 5

Clock
06.02.2025
An eye
538
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article, I will show how you can add localization and translations to a Django website(i18n). We will translate Python, JS code, as well as templates and Django-models. Plus, …

Used termins


Related questions