Good morning/afternoon everyone,
I've been using React for a few months. I'm trying to avoid using the React Components, instead I use the React Hooks, but I have to admit that there are times when my goals get complicated.
One of those moments is when executing a function only once after rendering the component. In my case I want to execute a recursive function (typeText) only once after all the components have been rendered.
Here is the link to CodeSandbox of the project (React): https://codesandbox.io/s/execute-after-rendering-jzz87
This is what I would like to achieve (Static): https://codesandbox.io/s/type-effect-jfzhl
Below there is an example of the react project:
import React, { useEffect, useState } from "react";
export default function App() {
const [word, setWord] = useState("");
const list = ["Bag", "Door", "Shelving"];
let isWriting = true;
let selectedWord = 0,
position = 0,
delay = 0;
const typeText = () => {
if (isWriting === true) {
if (position < list[selectedWord].length) {
setWord(word + list[selectedWord].charAt(position++));
delay = 100;
} else {
isWriting = false;
delay = 1500;
}
} else {
if (word.length > 0) {
setWord(word.substring(0, word.length - 1));
delay = 40;
} else {
isWriting = true;
selectedWord = (selectedWord + 1) % list.length;
position = 0;
delay = 300;
}
}
setTimeout(() => typeText(), delay);
};
useEffect(() => {
typeText();
}, []);
return (
<div className="App">
<h1>{word}</h1>
</div>
);
}
Thank you very much to all of you for your help, greetings and a hug!
What you posted isn't React code though, would you mind posting the React part? In any case, in hooks, to run something once after rendering, just use the useEffect hook:
const MyComponent = () => {
useEffect(() => {
// your code here
}, []);
return <Whatever you="are rendering" />;
};
Problem solved, you can see the changes in https://codesandbox.io/s/execute-after-rendering-jzz87.
Thank you all!
Related
I have a typing test web app built with a timer and text for the user to type. The user input is recorded via event listeners in a useEffect hook. The timer also uses an useEffect hook. Everything works just fine when they are separate, but when they are together and the user types the text, the timer is paused and does not rerender. It's not a delay. Not sure why this is happening, below is the code for the timer.
const Timer = ({initMin, initSec, setFin}) => {
const [ minutes, setMinutes ] = useState(parseInt(initMin));
const [ seconds, setSeconds ] = useState(parseInt(initSec));
// const [ timeEnd, setTimeEnd ] = useState(false);
useEffect(() => {
const myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds( old => old - 1 );
}
if (seconds === 0) {
if (minutes === 0) {
setFin(true); // Ends the game
clearInterval(myInterval)
} else {
setMinutes( old => old - 1 );
setSeconds(59);
}
}
}, 1000)
return () => { clearInterval(myInterval); };
});
return (
<h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds} </h1>
);
}
And for the typing component.
export default function TypeTest({setStlLst, word}) {
const [test1Arr, setTest] = useState(word.split(' '));
const tempStyles = [];
for (var i = 0; i < test1Arr.length; ++i){
var letterStyles = [];
for (var j = 0; j < test1Arr[i].length; ++j){
if (!i && !j) letterStyles.push('current');
letterStyles.push('');
}
tempStyles.push(letterStyles);
}
const [styles, setStyles] = useState(tempStyles);
const [currWord, setWord] = useState(0);
const [currChar, setChar] = useState(0);
const [prevPos, setPrev] = useState(0);
useEffect(() => {
setStlLst(styles);
const alterStyle = (e) => {
var temp = styles.slice();
test1Arr[0][0] === e.key ? temp[0][0] = 'correct' : temp[0][0] = 'incorrect';
setStyles(temp);
}
window.addEventListener("keydown", alterStyle);
return () => window.removeEventListener("keydown", alterStyle);
}, [currChar, currWord, styles, prevPos, test1Arr, setStlLst]);
return (
<div className='outer-word-container'>
<div className="word-container">
{test1Arr.map((monoWord, idx) => <Word key={idx} inputWord = {monoWord} letterStyles={styles[idx]}/>)}
</div>
</div>
)
}
Any guidance would be appreciated!
Inside my functional component I have defined two hooks started:false and sec:0
let interval = null
const Home = () => {
const [sec, setSec] = useState(0)
const [started, setStarted] = useState(false)
So as the name suggests, every second I want to increment this counter.
I have method called setTimer which should increment my sec every second.
function setTimer() {
console.log(started)
if (!started) {
console.log(started)
setStarted(true);
interval = setInterval(() => {
setSec(sec+1)
console.log("ADDED",sec)
}, 1000)
}
}
But it seems that the sec counter never goes above 1. Is there a reason for this?
You should uses a functional state update, so instead of setSec(sec+1) write setSec(prevSec => prevSec + 1)
See the React Hooks API for reference: https://reactjs.org/docs/hooks-reference.html#usestate
let started = false;
let sec = 0;
let setSec = function(seconds) { sec = seconds; }
let setStarted = function() { started = true; }
function setTimer() {
console.log(started)
if (!started) {
setStarted(true);
console.log(started)
interval = setInterval(() => {
setSec(sec+1)
console.log("ADDED",sec)
}, 1000)
}
}
setTimer();
var setStarted = false;
setSec = 0;
function setTimer() {
console.log(setStarted)
if (!setStarted) {
console.log(setStarted)
setStarted = true;
setInterval(() => {
setSec +=1;
console.log("ADDED",setSec);
}, 1000)
}
}
setTimer();
setInterval has closed over the initial value of your state(sec). Every time you are modifiying it, you are doing setSec(0+1), essentially making it 1. This is the problem of stale state.
You can use useRef to have access to the current value always.
import "./styles.css";
import { useState, useRef } from "react";
export default function App() {
const [sec, setSec] = useState(0);
const [started, setStarted] = useState(false);
let interval = null;
let realSec = useRef(0);
function setTimer() {
console.log(started);
if (!started) {
console.log(started);
setStarted(true);
interval = setInterval(() => {
setSec(realSec.current + 1);
realSec.current++;
console.log("ADDED", sec);
}, 1000);
}
}
return (
<>
<p>{sec}</p>
<button onClick={setTimer}>X</button>
</>
);
}
I'm trying to return the number of seconds whilst holding in a button.
eg: "click+ hold, inits -> counts & displays 1, 2, 3, 4, 5 -> leaves button -> resets back to 0"
I've gotten close. It works fine, in my console, but whenever I try to update the state it ends up in an infinite loop.
import React, { useState, useEffect } from "react";
const Emergency = () => {
let counter = 0;
let timerinterval;
const [ms, setMs] = useState(counter);
const timer = start => {
console.log("tick tock");
console.log(start);
if (start === true && counter >= 1) {
timerinterval = setInterval(() => {
counter += 1;
console.log(counter);
setMs(counter); //When I remove this, the infinite loop disappears.
}, [1000]);
} else {
setMs(0);
}
};
const pressingDown = e => {
console.log("start");
e.preventDefault();
counter = 1;
timer(true);
};
const notPressingDown = e => {
console.log("stop");
e.preventDefault();
timer(false);
setMs(0);
clearInterval(timerinterval);
};
return (
<>
<button
onMouseDown={pressingDown}
onMouseUp={notPressingDown}
onTouchStart={pressingDown}
onTouchEnd={notPressingDown}
className="button is-primary mt-3"
>
Emergency
</button>
<br />
Time holding it is.... {ms}
</>
);
};
export default Emergency;
An easy way would be to calculate the time difference between mouseDown and mouseUp, but for the sake of UX, I would like to {ms} to update live as I'm holding the button.
Any suggestions?
Thanks!
There are two problems with your code:
You are not clearing interval. timeInterval is a new variable whenever your component is re-rendered. You need to use ref (const timeInterval = React.useRef(null); ... timeInterval.current = ... ; clearInterval(timeInterval.current);
Also you need to remove counter = 1; from your pressingDowm function, because before each setMs you are incrementing it by one
const Emergency = () => {
let counter = 0;
let timerinterval = React.useRef((null as unknown) as any);
const [ms, setMs] = React.useState(counter);
const timer = (start: any) => {
console.log('tick tock');
console.log(start);
if (start === true && counter >= 1) {
timerinterval.current = setInterval(() => {
console.log(counter);
setMs(counter); //When I remove this, the infinite loop disappears.
counter += 1;
//#ts-ignore
}, [1000]);
} else {
setMs(0);
}
};
const pressingDown = (e: any) => {
console.log('start');
e.preventDefault();
counter = 1;
timer(true);
};
const notPressingDown = (e: any) => {
console.log('stop');
e.preventDefault();
timer(false);
setMs(0);
clearInterval(timerinterval.current);
};
return (
<>
<button
onMouseDown={pressingDown}
onMouseUp={notPressingDown}
onTouchStart={pressingDown}
onTouchEnd={notPressingDown}
className="button is-primary mt-3"
>
Emergency
</button>
<br />
Time holding it is.... {ms}
</>
);
};
This is edited code (with some TypeScript stuff, sorry for that)
I need to callback. a. function after setting a state within a nested function which triggers on click, How can I approach this, I know that calling hooks in nested functions doesn't work but it's necessary in my case. I tried using useEffect and useCallback but as expected it wont work The code looks like this in a nutshell:
const App = () => {
const [elements, setElements] = useState(true);
function UseEffectSkipFirst(fn, arr) {
const isFirst = useRef(true);
useCallback(() => {
if (isFirst.current) {
isFirst.current = false;
return;
}
fn();
}, arr);
}
const Shape = (g) => {
// let colorMatrix = new PIXI.filters.ColorMatrixFilter();
// let color = 0xffffff
// let tint = 0xffffff;
function onClick(event) {
setElements(false);
UseEffectSkipFirst(
() => {
if (elements === true) {
this.data = event.data;
this.dragging = true;
this.offX = this.x - this.data.getLocalPosition(this.parent).x;
this.offY = this.y - this.data.getLocalPosition(this.parent).y;
} else {
g.clear();
}
},
[elements]
);
}
// ...
};
return (
//...
);
};
can anyone help please, thanks
I have an eventlistener that looks like this:
window.addEventListener('scroll', scroll.throttle(
triggered,
{state: state, wrapper: wrapper, children: children, scroll: scroll},
50
));
And I have a class that looks like this:
Scroll = class{
constructor(){
this.on = true;
}
throttle(fn, v, wait){
var time = Date.now();
return () => {
if ((time + wait - Date.now()) < 0 && this.on) {
fn(v);
time = Date.now();
}
}
}
triggered(o){
if(o.state.check !== 0){
o.scroll.on = false;
o.wrapper.classList.toggle('flic-down', o.state.check === 1)
o.wrapper.classList.toggle('flic-up', o.state.check === -1)
o.state.update();
o.wrapper.classList.add('flic-transition')
setTimeout(()=>{this.changeDone(o)}, 1200);
}
}
changeDone(o) {
o.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
o.children.setClasses(o.state.state);
o.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
o.scroll.on = true;
}
},
I don't like passing state, wrapper, children and scroll as variables. I would prefer to store them in the class when instantiating them. I understand the problem is that "this" won't be passed correctly and that it can be bound. But because the throttle function I don't understand how to pass this.
I would recommend to separate the throttling from the scrolling.
class Throttle {
constructor() {
this.on = true;
}
get(fn, wait) {
var time = Date.now();
return (...args) => {
if ((time + wait - Date.now()) < 0 && this.on) {
fn(...args);
time = Date.now();
}
}
}
}
class Scroll {
constructor(state, wrapper, children) {
this.state = state;
this.wrapper = wrapper;
this.children = children;
this.throttle = new Throttle();
}
triggered() {
if (this.state.check !== 0) {
this.throttle.on = false;
this.wrapper.classList.toggle('flic-down', this.state.check === 1)
this.wrapper.classList.toggle('flic-up', this.state.check === -1)
this.state.update();
this.wrapper.classList.add('flic-transition')
setTimeout(()=>{this.changeDone()}, 1200);
}
}
changeDone() {
this.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
this.children.setClasses(this.state.state);
this.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
this.throttle.on = true;
}
}
You then would do
const scroll = new Scroll(state, wrapper, children);
window.addEventListener('scroll', scroll.throttle.get(() => scroll.triggered(), 50));
Notice the arrow function being passed to throttle.get that calls triggered on the scroll instance, instead of passing the method without a context.
If you want to mix them both in the same class, I don't see why throttle would take a fn and v as parameters. You're only using it to call triggered anyway:
class ThrottledScroll {
constructor(state, wrapper, children) {
this.state = state;
this.wrapper = wrapper;
this.children = children;
this.throttle = new Throttle();
this.on = true;
}
get(wait) {
var time = Date.now();
return () => {
if ((time + wait - Date.now()) < 0 && this.on) {
this.triggered();
time = Date.now();
}
}
}
triggered() {
if (this.state.check !== 0) {
this.on = false;
this.wrapper.classList.toggle('flic-down', this.state.check === 1)
this.wrapper.classList.toggle('flic-up', this.state.check === -1)
this.state.update();
this.wrapper.classList.add('flic-transition')
setTimeout(()=>{this.changeDone()}, 1200);
}
}
changeDone() {
this.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
this.children.setClasses(this.state.state);
this.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
this.on = true;
}
}
with
const scroll = new ThrottledScroll(state, wrapper, children);
window.addEventListener('scroll', scroll.get(50));
In your class constructor, you can bind your class methods' context to the class with this.methodName = this.methodName.bind(this). ReyHaynes is right, this can also be accomplished by defining class methods as arrow functions, but I believe that's still an uncomfirmed ES7 feature, so you might not want to use that.
If that's unclear or unhelpful, you might find some helpful context here. It's a React resource, but, if I understand correctly, your problem is one dealt with in React all the time.
Adding the bind to the constructor worked for the class, that handles the binding issue when called out of context:
Scroll = class{
constructor(){
this.on = true;
this.triggered = this.triggered.bind(this)
this.changeDone = this.changeDone.bind(this)
}
throttle(fn, v, wait){
var time = Date.now();
return () => {
if ((time + wait - Date.now()) < 0 && this.on) {
fn(v);
time = Date.now();
}
}
}
triggered(o){
if(o.state.check !== 0){
o.scroll.on = false;
o.wrapper.classList.toggle('flic-down', o.state.check === 1)
o.wrapper.classList.toggle('flic-up', o.state.check === -1)
o.state.update();
o.wrapper.classList.add('flic-transition')
setTimeout(()=>{this.changeDone(o)}, 1200);
}
}
changeDone(o) {
o.wrapper.classList.remove('flic-transition', 'flic-up', 'flic-down');
o.children.setClasses(o.state.state);
o.wrapper.getElementsByClassName('flic-active')[0].scrollIntoView(true);
o.scroll.on = true;
}
}
The event listener needed to have scroll.triggered vs triggered unless you deconstructed (const { triggered } = scroll) before it and we didn't see that:
window.addEventListener('scroll', scroll.throttle(
scroll.triggered,
{state: state, wrapper: wrapper, children: children, scroll: scroll},
50
))