How I can show a loader while setTimeOut() function is processing - javascript

Need to show a loader wile setTimeOut() function is processing, without using states(react component's state).
When the function is processing the data till then the loader should be displayed on the screen after that the loader should be disappear automatically.
showData = () => {
if (!this.errorObject.isValid(this.getColHeader())) {
alert('Please correct the invalid cells and try again...')
} else {
setTimeout(() => {
const totalRows = this.hotTableComponent.current.hotInstance.countRows();
const { data :{ dimensions } } = this.props;
const nullString = dimensions.reduce((acc, currentValue) => acc + currentValue.delimiter, '');
// eslint-disable-next-line no-plusplus
for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) {
const rowData = this.hotTableComponent.current.hotInstance.getDataAtRow(rowIndex)
rowData.pop();
const genStr = rowData.reduce((acc, currentValue, index) => {
const fieldData = dimensions[index].field.data;
if (fieldData.valueListType === "value" && fieldData.valueType === "undefined") {
if (fieldData.defaultValue) {
currentValue = (currentValue) || fieldData.defaultValue;
}
} else if (fieldData.valueListType === "codeValue" && currentValue) {
currentValue = currentValue.slice(currentValue.indexOf('(') + 1, currentValue.length - 1);
}
if (currentValue === null) {
currentValue = ' ';
}
return acc + currentValue + dimensions[index].delimiter;
}, '');
if (nullString !== genStr) {
this.updateCell(rowData, rowIndex, genStr);
}
}
}, 100);
}
}

If you really, and I mean really, don't want to use states you can achieve this by having the loading icon always be rendered but having an opacity of 0. Then before you call setTimeout you can use a ref to the loading icon to set it's opacity to 1. Then you can set it to 0 again when the setTimeout is executing.
I would not advice going this route though and instead to just use a state to indicate whether or not a component (such as a loading icon) should be shown.

Related

How to automatically place the mouse cursor in an input at a precise index outside the callback() function with useRef?

I have an input in which the user enters an expression of this form:
"TERM":"MATCH_TERM"
I would like that when the user enters a quote in the input, a second quote of the same type is added (the user can use single and double quotes : " or ') and that the mouse cursor is placed between the two quotes that have just been created.
My code is in ReactJs.
I managed to automatically add a second quote when the user enters a first one, at the right place in the string. But I can't figure out how to then move my mouse cursor between the two new quotes.
To make my input component do this, I wrote the following code:
(I tried to simplify the code but normally it is reproducible)
import * as React from "react";
import { useEffect, useState, useRef } from "react";
const QuoteInput: React.FC = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const [inputChoose, setInputChoose] = useState("");
const [previousInputChoose, setPreviousInputChoose] = useState("");
const [testQuoteAddition, setTestQuoteAddition] = useState(false);
const [enterSingleQuote, setEnterSingleQuote] = useState(false);
const [enterDoubleQuote, setEnterDoubleQuote] = useState(false);
const inputHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
setPreviousRequestChoose(requestChoose);
const enteredRequest = event.target.value;
setRequestChoose(enteredRequest);
setTestQuoteAddition(true);
};
function addSingleQuote(indexDifference: number) {
let newString: string = requestChoose.slice(0,indexDifference + 1) + "'" + requestChoose.slice(indexDifference + 1);
setRequestChoose(newString);
if(inputRef !== null && inputRef !== undefined) {
if (inputRef.current !== null && inputRef.current !== undefined) {
console.log("3 ");
if (inputRef.current.setSelectionRange !== undefined) {
inputRef.current.setSelectionRange(indexDifference, indexDifference);
}
}
}
}
function addDoubleQuote(indexDifference: number) {
let newString: string = requestChoose.slice(0,indexDifference + 1) + '"' + requestChoose.slice(indexDifference + 1);
setRequestChoose(newString);
}
useEffect(()=>{
if(testQuoteAddition === true) {
for(let i=0; i<requestChoose.length; i++) {
if(previousRequestChoose.charAt(i) !== requestChoose.charAt(i))
{
if (requestChoose.charAt(i) === "'") {
setEnterSingleQuote(true);
} else if (requestChoose.charAt(i) === '"') {
setEnterDoubleQuote(true);
}
}
}
}
setTestQuoteAddition(false);
},[testQuoteAddition])
useEffect(()=>{
if(enterSingleQuote === true){
let indexDifferenceInRequest: number = requestChoose.length + 1;
let findDifference: boolean = false
for(let i=0; i<requestChoose.length; i++) {
if(previousRequestChoose.charAt(i) !== requestChoose.charAt(i) && findDifference === false)
{
indexDifferenceInRequest = i;
findDifference = true;
}
}
addSingleQuote(indexDifferenceInRequest);
setEnterSingleQuote(false);
} else if (enterDoubleQuote === true){
let indexDifferenceInRequest: number = requestChoose.length + 1;
let findDifference: boolean = false
for(let i=0; i<requestChoose.length; i++) {
if(previousRequestChoose.charAt(i) !== requestChoose.charAt(i) && findDifference === false)
{
indexDifferenceInRequest = i;
findDifference = true;
}
}
addDoubleQuote(indexDifferenceInRequest);
setEnterDoubleQuote(false);
}
},[enterSingleQuote, enterDoubleQuote])
return(
<div>
<input ref={inputRef} type="text" onChange={inputHandler} value={inputChoose} className="text-center" placeholder="enter an input" />
</div>
);
}
export default QuoteInput;
This code allows me to add a new pair of quotes but the mouse cursor is then placed at the end of the string.
If I put: inputRef.current?.setSelectionRange(3, 3); in the inputHandler (the callback function of my input element), every time the user writes the cursor is reset to the third position of the string.
But if I put this same line: inputRef.current?.setSelectionRange(indexDifference, indexDifference); in the function which add a quote, as in the code above, nothing happens, but if I put console.log in the loop it displays well so my conditions are well met and the statement should execute.
I don't see what I'm doing wrong at all, if you could point me in the right direction it would help me a lot.
I just noticed another problem with my add quote function while I was writing this question.
When I type at normal speed in the search bar everything works normally. But if I suddenly decide to write super fast, the function of adding a quote when a user has put a first one stops working and doesn't work at all afterwards even if I start writing again at a normal speed.
Once the users knows how to used my tool well, it may happen that they type very fast and so this use case may occur.
When I display my state variables with console.log, it seems that before typing very fast, everything is triggered in the inputHandler. But after typing very fast, only state variables concerning the old string and the new string entered by the user are activated, the boolean state variable to launch the verification test of adding a new quote is no longer activated and therefore never goes to true (It should be set to true in inputHandler (the callback function of the input element in the return()) which then enables the first useEffect since the boolean state variable is in the dependency array.).
Does anyone know what can cause this behavior between an input, its callback function and the state variables?
If anyone understands my problem and is willing to help me it would be a great help.
I didn't find the answer to my second question (stopping the feature from working if the user spams the input with a lot of characters) but I finally found my problem. I was missing a useEffect for my previous code to work normally, because of that, one of my state variables didn't have time to update and this creates the strange behaviors of repositioning the cursor in the input.
In case someone is interested I put the correct code here (I added some validation conditions in some useEffects to avoid bugs of randomly adding quotation marks especially when copying and pasting):
import * as React from "react";
import { useEffect, useState, useRef } from "react";
const QuoteInput: React.FC = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const [inputChoose, setInputChoose] = useState("");
const [previousInputChoose, setPreviousInputChoose] = useState("");
const [testQuoteAddition, setTestQuoteAddition] = useState(false);
const [enterSingleQuote, setEnterSingleQuote] = useState(false);
const [enterDoubleQuote, setEnterDoubleQuote] = useState(false);
const [writeNewSingleQuote, setWriteNewSingleQuote] = useState(false);
const [writeNewDoubleQuote, setWriteNewDoubleQuote] = useState(false);
const [repositionCaretBetweenQuotes, setRepositionCaretBetweenQuotes] = useState(false);
const [indexDifferenceInInput, setIndexDifferenceInInput] = useState(0);
const inputHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
setPreviousRequestChoose(requestChoose);
const enteredRequest = event.target.value;
setRequestChoose(enteredRequest);
setTestQuoteAddition(true);
};
useEffect(()=>{
if(testQuoteAddition === true && (previousInputChoose.length === inputChoose.length || previousInputChoose.length === inputChoose.length - 1)) {
for(let i=0; i<=inputChoose.length; i++) {
if(previousInputChoose.charAt(i) !== inputChoose.charAt(i))
{
let previousSingleQuoteNumber = previousRequestChoose.split("'").length - 1;
let previousDoubleQuoteNumber = previousRequestChoose.split('"').length - 1;
let previousQuoteNumber: number;
if (previousSingleQuoteNumber !== undefined && previousDoubleQuoteNumber !== undefined) {
previousQuoteNumber = previousSingleQuoteNumber + previousDoubleQuoteNumber;
} else if (previousSingleQuoteNumber !== undefined && previousDoubleQuoteNumber === undefined) {
previousQuoteNumber = previousSingleQuoteNumber;
} else if (previousSingleQuoteNumber === undefined && previousDoubleQuoteNumber !== undefined) {
previousQuoteNumber = previousDoubleQuoteNumber;
} else {
previousQuoteNumber = 0;
}
previousQuoteNumber = previousSingleQuoteNumber! + previousDoubleQuoteNumber!;
let currentSingleQuoteNumber = requestChoose.split("'").length - 1;
let currentDoubleQuoteNumber = requestChoose.split('"').length - 1;
let currentQuoteNumber: number;
if (currentSingleQuoteNumber !== undefined && currentDoubleQuoteNumber !== undefined) {
currentQuoteNumber = currentSingleQuoteNumber + currentDoubleQuoteNumber;
} else if (currentSingleQuoteNumber !== undefined && currentDoubleQuoteNumber === undefined) {
currentQuoteNumber = currentSingleQuoteNumber;
} else if (currentSingleQuoteNumber === undefined && currentDoubleQuoteNumber !== undefined) {
currentQuoteNumber = currentDoubleQuoteNumber;
} else {
currentQuoteNumber = 0;
}
currentQuoteNumber = currentSingleQuoteNumber! + currentDoubleQuoteNumber!;
if (inputChoose.charAt(i) === "'" && currentQuoteNumber !== previousQuoteNumber) {
setEnterSingleQuote(true);
} else if (inputChoose.charAt(i) === '"' && currentQuoteNumber !== previousQuoteNumber) {
setEnterDoubleQuote(true);
}
}
}
}
setTestQuoteAddition(false);
},[testQuoteAddition])
useEffect(()=>{
if(enterSingleQuote === true){
let findDifference: boolean = false
for(let i=0; i<inputChoose.length; i++) {
if(previousInputChoose.charAt(i) !== inputChoose.charAt(i) && findDifference === false)
{
setIndexDifferenceInInput(i);
findDifference = true;
}
}
setEnterSingleQuote(false);
setWriteNewSingleQuote(true);
} else if (enterDoubleQuote === true){
let findDifference: boolean = false
for(let i=0; i<inputChoose.length; i++) {
if(previousInputChoose.charAt(i) !== inputChoose.charAt(i) && findDifference === false)
{
setIndexDifferenceInInput(i);
findDifference = true;
}
}
setEnterDoubleQuote(false);
setWriteNewDoubleQuote(true);
}
},[enterSingleQuote, enterDoubleQuote])
useEffect(()=>{
if (writeNewSingleQuote === true) {
let newString: string = inputChoose.slice(0,indexDifferenceInInput + 1) + "'" + inputChoose.slice(indexDifferenceInInput + 1);
setInputChoose(newString);
if(inputRef !== null && inputRef !== undefined) {
if (inputRef.current !== null && inputRef.current !== undefined) {
if (inputRef.current.setSelectionRange !== undefined) {
setRepositionCaretBetweenQuotes(true);
}
}
}
} else if (writeNewDoubleQuote === true) {
let newString: string = inputChoose.slice(0,indexDifferenceInInput + 1) + '"' + inputChoose.slice(indexDifferenceInInput + 1);
setInputChoose(newString);
if(inputRef !== null && inputRef !== undefined) {
if (inputRef.current !== null && inputRef.current !== undefined) {
if (inputRef.current.setSelectionRange !== undefined) {
setRepositionCaretBetweenQuotes(true);
}
}
}
}
},[writeNewSingleQuote, writeNewDoubleQuote])
useEffect(()=>{
setWriteNewSingleQuote(false);
setWriteNewDoubleQuote(false);
if (repositionCaretBetweenQuotes === true) {
inputRef?.current?.setSelectionRange(indexDifferenceInInput + 1, indexDifferenceInInput + 1);
}
setRepositionCaretBetweenQuotes(false);
},[repositionCaretBetweenQuotes])
return(
<div>
<input ref={inputRef} type="text" onChange={inputHandler} value={inputChoose} className="text-center" placeholder="enter an input" />
</div>
);
}
export default QuoteInput;

Multiple elements -> one function while the elements do not colide with each other

I have multiple selectiontags on different picture slides but same page. Each slide has a set of selectiontags and I want users to only choose 1 selectiontag. I have written the code to do this but I wonder if there is another way.
So, basically I want:
Slide1 w. selectiontags1: Choose 1 selectiontag (out of 4)
Slide2 w.selectiontags2: Choose 1 selectiontag
Slide3 w. selectiontags3: Choose 1 selectiontag
Slide4 w. selectiontags4: Choose 1 selectiontag
This is my code so far.
var prevSelectedValue = null;
var prevSelectedValue2 = null;
var prevSelectedValue3 = null;
var prevSelectedValue4 = null;
$w.onReady(function () {
//TODO: write your page related code here...
let tags = $w('#selectionTags1');
if (tags.value.length === 1) {
prevSelectedValue = tags.value[0];
} else if (tags.value.length > 1) {
tags.value = [];
}
let tags2 = $w('#selectionTags2');
if (tags2.value.length === 1) {
prevSelectedValue2 = tags2.value[0];
} else if (tags2.value.length > 1) {
tags2.value = [];
}
let tags3 = $w('#selectionTags3');
if (tags3.value.length === 1) {
prevSelectedValue3 = tags3.value[0];
} else if (tags3.value.length > 1) {
tags3.value = [];
}
let tags4 = $w('#selectionTags4');
if (tags4.value.length === 1) {
prevSelectedValue4 = tags4.value[0];
} else if (tags4.value.length > 1) {
tags4.value = [];
}
});
export function selectionTags1_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue);
prevSelectedValue = event.target.value[0];
}
}
export function selectionTags2_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue2];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue2);
prevSelectedValue2 = event.target.value[0];
}
}
export function selectionTags3_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue3];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue3);
prevSelectedValue3 = event.target.value[0];
}
}
export function selectionTags4_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue4];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue4);
prevSelectedValue4 = event.target.value[0];
}
}
Just a couple notes to help clean up the code:
It is better to use event.target.value[event.target.value.length-1] instead of .filter(). While it doesn't make a huge performance difference since our array is so small, we know that the most recently selected item is the last in the array, so using length-1 is more efficient code.
It is better to manipulate the selector and not the event, so I would recommend replacing event.target.value = event.target.value.filter... with $w('#selectionTags2') = event.target.value[..... This will also make your code more readable as you know which element is being updated.
If you are not preloading anything into the $w('#selectionTags') value array, I am not sure the code in your onReady function is necessary (unless I am missing a step where you do populate that selection value array).

React component shows empty data on first load, but renders properly after a refresh?

I'm just taking some data from accounts, chunking it into separate pages then displaying the first page by default. It begins empty, but after I refresh it works properly.
const Dashboard = ({ accounts }) => {
const [pageNum, setPageNum] = useState(0);
const pages = useMemo(() => {
const pages = [];
for (let i = 0; i < accounts.length; i += 8) {
pages.push(accounts.slice(i, i + 8));
}
return pages;
}, [accounts]);
const numOfPages = pages.length;
useEffect(() => {
getAccount();
}, []);
const onClick = (num) => {
if (num == -1 && pageNum == 0) {
//Do nothing
} else if (num == 1 && pageNum >= numOfPages - 1) {
//Do nothing
} else {
setPageNum(pageNum + num);
}
};
...
<Fragment>
{pages[pageNum] !== null &&
pages[pageNum] !== undefined &&
pages[pageNum].map((account, index) => (
<SavedPassword
key={index}
site={account.site}
login={account.login}
password={account.password}
/>
))}
</Fragment>
The useEffect hook with get accounts isn't actually required, I added that in hopes it would make it refresh or something. The webpage actually loads the getAccount action upon entering, regardless of what page is loaded. I've tried putting all that information into the getEffect hook, but that was actually a total nightmare. Any ideas of why it's doing this?

amazon-chime-sdk-js display self video

I created a video application using chime js SDK with the help of the documentation https://aws.github.io/amazon-chime-sdk-js/index.html
const indexMap = {};
const acquireVideoElement = tileId => {
for (let i = 0; i < 16; i += 1) {
if (indexMap[i] === tileId) {
return videoElements[i];
}
}
for (let i = 0; i < 16; i += 1) {
if (!indexMap.hasOwnProperty(i)) {
indexMap[i] = tileId;
return videoElements[i];
}
}
throw new Error('no video element is available');
};
const releaseVideoElement = tileId => {
for (let i = 0; i < 16; i += 1) {
if (indexMap[i] === tileId) {
delete indexMap[i];
return;
}
}
};
const observer = {
videoTileDidUpdate: tileState => {
if (!tileState.boundAttendeeId || tileState.localTile || tileState.isContent) {
return;
}
meetingSession.audioVideo.bindVideoElement(tileState.tileId, acquireVideoElement(tileState.tileId));
},
videoTileWasRemoved: tileId => {
releaseVideoElement(tileId);
}
};
meetingSession.audioVideo.addObserver(observer);
const audioMix = document.getElementById('meeting-audio');
meetingSession.audioVideo.bindAudioElement(audioMix);
meetingSession.audioVideo.start();
meetingSession.audioVideo.startLocalVideoTile();
This is working good and I can see all the attendees who is joined in the meeting. But I need to show my video also in a tag. Is it possible?
In your videoTileDidUpdate, for your video tile to show, where are you binding the local tile, I see that if tileState.localTile is true you are returning and the bindVideoElement is not getting called hence your localTile is not showing up. Can you please remove the localTile check and see if that works as the initial step.
videoTileDidUpdate: tileState => {
// Ignore a tile without attendee ID, a local tile (your video), and a content share.
const { allUsers } = this.props;
if (!tileState.boundAttendeeId || tileState.isContent) {
return;
}
if( tileState.localTile ) {
if( Obj[tileState.boundExternalUserId]['tileId'] === tileState.tileId) {
return;
}
}
this.meetingSession.current.audioVideo.bindVideoElement( tileState.tileId,document.getElementById(tileState.boundExternalUserId) );
}

Sort only array elements JavaScript which has a CSS property

I want to create an unordered list. The order should be given by their id (Which is a number. The smallest is at the bottom.)
BUT if a certain li does not have a a CSS property (text-decoration:line-through in my case.) Then they should be at the bottom anyway.
I am trying to make a To Do list, where checked elements are at the bottom, but when you uncheck them, they jump back to place.
http://codepen.io/balazsorban44/pen/Gjzwbp
const inputArea = document.getElementById('todo-input');
const list = document.getElementById('tasks');
tasks = [];
function loaded() {
inputArea.focus();
}
function enter() {
if (event.keyCode == 13) {
addTask()
}
}
function refresh(array) {
var task = document.createElement('li');
const checkBox = document.createElement('input');
checkBox.setAttribute('type', 'checkbox');
checkBox.setAttribute('onclick', 'toggleCheckBox()');
task.appendChild(checkBox);
task.appendChild(document.createTextNode(array[array.length - 1].task));
task.id = array[array.length - 1].timestamp.getTime()
list.appendChild(task);
return list;
}
function addTask() {
if (inputArea.value != '') {
tasks.push({
timestamp: new Date(),
task: inputArea.value
});
inputArea.value = '';
inputArea.focus();
refresh(tasks);
doneOrUndone();
inputArea.placeholder = 'Click + or press Enter.'
} else {
inputArea.placeholder = 'Write something'
}
}
function doneOrUndone() {
var done = 0
const out = document.getElementById('out')
for (var i = 0; i < tasks.length; i++) {
if (document.getElementsByTagName('li')[i].style.textDecoration != '') {
done++
}
}
out.value = parseInt(done) + '/' + parseInt(tasks.length) + ':'
}
function toggleCheckBox() {
const task = event.target.parentNode
if (task.style.textDecoration == '') {
task.style.textDecoration = 'line-through';
list.appendChild(task)
doneOrUndone()
} else {
task.style.textDecoration = ''
for (var i = 0; i < tasks.length; i++) {
if (task.id < tasks[i].timestamp.getTime() && list.childNodes[0].style.textDecoration == 'line-through') {
if (task.id > list.childNodes[0].id) {
list.insertBefore(task, list.childNodes[0]);
break;
}
} else {
list.insertBefore(task, list.childNodes[i]);
break
}
}
doneOrUndone()
}
}
I think the real problem is that when setting style.textDecoration to line-through, the actual property that is set is textDecorationLine.
Your line if (task.style.textDecoration == '') is therefore always true.
You can replace textDecoration with textDecorationLine and it should work.
http://codepen.io/anon/pen/bwzzkP
The underlying cause of this problem is, in my opinion, that you're using an element's style attribute to monitor state. I'd advice to either:
Store state in javascript, or
Store state in a class, or
Store state in a custom data- attribute
For example:
const task = event.target.parentNode;
const isDone = task.getAttribute("data-done");
task.style.textDecoration = !isDone ? 'line-through' : '';
task.setAttribute("data-done", !isDone);

Categories

Resources