I'd like some help understanding why my React Component isn't working. I'm trying work on my React/Js fundamentals by making a basic timer. The component is rendering great, just nothing is happening. I'm looking at the React Profiler on Chrome and my variables seem to be updating correctly.
I know it's basic, I'd just rather start and fix my mistakes and learn on the way.
Any help whatsoever would be greatly appreciated!
import React from 'react'
var sec = 0;
var min = 0;
var hrs = 0;
var timer_id;
// Helper Functions
function tick(){
sec++;
if (sec >= 60){
sec = 0;
min ++;
if (min >= 60){
min = 0;
hrs++;
};
};
}
function add(){
tick();
timer();
}
function timer(){
console.log("TIMER");
timer_id = setTimeout(add, 1000);
}
// Clock Component
class Clock extends React.Component{
render(){
timer();
return (
<div className="clock-face">
<h4> {hrs} : {min} : {sec}</h4>
<button
onClick = {() => clearTimeout(timer_id)}> Stop </button>
</div>
);
}
}
export default Clock;
React components only get rerendered when their props or state changes (or when forced to rerender, but we don't do that). You're updating hrs/min/sec in global variables; those aren't tracked by React.
In addition, it's not a good idea to have render() execute a side effect (timer() in your case); React may elect to render your function more than once.
Here's an example of your clock as a class component that ticks along and cleans the timer up after it's unmounted.
The magic that causes the rerendering here is this.setState being called.
import React from "react";
function divmod(a, b) {
return [Math.floor(a / b), a % b];
}
class Clock extends React.Component {
state = { time: 0 };
componentDidMount() {
this.timer = setInterval(this.tick, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
tick = () => {
this.setState(({ time }) => ({ time: time + 1 }));
};
render() {
const { time } = this.state;
const [hmins, secs] = divmod(time, 60);
const [hrs, mins] = divmod(hmins, 60);
return (
<div className="clock-face">
<h4>
{" "}
{hrs} : {mins} : {secs}
</h4>
</div>
);
}
}
Related
I am trying to make a typing effect, and its a very simple logic,,,but still i am not able to understand that why the 'e' in hello is always missing everything else is working fine. have not made the blinking cursor yet !!!!
CODE:
import { useState } from "react";
export function Type() {
let str = "hello my name is prateek"
const [char, setChar] = useState("");
function type() {
let i = 0;
let id = setInterval(() => {
setChar(prev => prev + str[i]);
//console.log(i,"i")
// console.log(str[i])
i++;
if (i === str.length - 1) {
//console.log("hello")
clearInterval(id)
}
}, 1000);
}
return (<div>
<h1>{char}</h1>
<button onClick={type}>Type</button>
</div>)
}
OUTPUT
hllo my name is prateek
I think you might have an asynchronous race condition:
Async callbacks go into an event queue.
The delay argument is like a minimum, see MDN for more on reasons delays can take longer
Though I could use help finding the source for dispatcher (source code), I strongly suspect React Hooks like useState use async callbacks and the event queue.
Assuming useState is an async callback, I suspect it has no delay.
One suggestion: use a loop and setTimeout() with 1000*i delay. The loop ensures you'll add each letter, the delay will add each letter ~1s apart.
Another suggestion: Dan Abramov did a really interesting blog post digging into this: Making setInterval declarative with React Hooks (2019). His solution explores a custom hook he writes, useInterval (not part of React Hooks API), and explains what's happening with React render cycles and "sliding delay".
const { useState } = React;
function Type(){
let str = "hello my name is prateek"
const [char,setChar] = useState("");
function type(){
let i = 0;
const id = setInterval(()=>{
setChar(prev=>prev+str[i]);
//console.log(i,"i")
// console.log(str[i])
i++;
if(i === str.length-1){
//console.log("hello")
clearInterval(id)
}
},1000);
}
return <div>
<h2>{char}</h2>
<button onClick={type}>Type</button>
</div>
}
function TypeWorking(){
let str = "hello my name is prateek"
const [char,setChar] = useState("");
function type(){
for(let i=0; i<str.length; i++) {
setTimeout(()=> setChar(prev=>prev+str[i]), 1000*(i+1));
}
}
return <div>
<h2>{char}</h2>
<button onClick={type}>Type</button>
</div>
}
ReactDOM.createRoot(
document.getElementById('app-broken')
).render(<Type />)
ReactDOM.createRoot(
document.getElementById('app-working')
).render(<TypeWorking />)
<h1>Question (bug)</h1>
<div id="app-broken"></div>
<h1>Working</h1>
<div id="app-working"></div>
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
The question also mentions an animated cursor. One idea would be to use ::before/::after pseudo-elements (or another element like a <span>) with an infinite CSS animation.
I reworked Type function and it has worked as what you want. Let's try it.
function type() {
const timerId = setInterval(() => {
setChar((prev) => {
if (prev.length >= str.length) {
clearInterval(timerId);
return prev;
}
return prev + str[prev.length - 1 + 1];
});
}, 200);
}
You can refer at this link: https://codesandbox.io/s/musing-joji-rrpk6i?file=/src/App.js:155-451
I have a dynamic Countdown Timer In React:
This Timer is a component (MovementTimer.js) & the main component (GameRoom.js) is calling Timer's render method, passing thru props.
This is the render Timer function that's being called below upon every GameRoom render
renderTimer = () => {
console.log('*** Timer RERENDER ***');
console.log(this.state.turn_time);
console.log(this.state.last_time);
if(this.state.turn_time != null && this.state.last_time != null){
const props = {
time : this.state.turn_time,
last_time : this.state.last_time
}
return(
<MovementTimer {...props}/>
)
} else {
return(
<p>Undefined Timer.</p>
)
}
}
This is the render method of GameRoom.js
render() {
if(this.state.guest){
console.log('loaded in ! ' + this.state.guest);
// Put into return statement for DEBUG {this.state.turn ? "TRUE" : "FALSE"}
return (
<div className="main">
{this.renderBoard()}
{this.renderTimer()}
{}
<p className="welcome">
{this.state.userResponseData}
{
this.state.guest[0] ? (this.state.turn ? "It's your opponent's turn." : "It's your turn, move!") : (this.state.turn ? "It's your turn, move!" : "It's your opponent's turn.")
}</p>
</div>
)
}
return (
<div className="main">
{this.renderBoard()}
<p className="welcome"> {this.state.turn ? "TRUE" : "FALSE"} Loading {this.state.userResponseData}</p>
</div>
)
}
}
Basically the issue is, whenever GameRoom.js rerender's, the renderTimer() function passes in updated props to the MovementTimer, however the timer doesn't reset. I was trying to use a useRef state boolean but it was buggy and wasn't working.
MovementTimer.js component
import React, {useRef, clearState,useState,useCallback} from 'react'
const MovementTimer = (props) => {
console.log('Re initizalized')
const time = parseInt(props.time);
const last_time = parseInt(props.last_time);
//clearState()
console.log(
"Estimated Time:\n"+
((last_time+time*1000) - Date.now() )/1000
);
let [counter, setCounter] = React.useState(Math.round( ((last_time+time*1000) - Date.now() )/1000 ) );
// const reinit = () => {
// setCounter(time)
// }
console.log(counter);
console.log(last_time);
React.useEffect(() => {
counter > 0 && setTimeout(() => setCounter(
counter - 1
), 1000); //Set this too Due time - last time / 1000 (for secs)
if(counter === 0){
const socketProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const socket = new WebSocket(socketProtocol+(window.location.href).split(':')[1]+':5000');
socket.onopen = async () => {
await socket.send(JSON.stringify({
query_type : 't0',
room_id: (window.location.pathname).slice(6)
}));
socket.close();
}
setCounter(Math.round( ((last_time+time*1000) - Date.now() )/1000 ))
}
}, [counter]);
return (
<div>
{counter}
</div>
)
}
export default MovementTimer
In this component, I useEffect for the timer countdown, and pass in the time difference in seconds (using props that have been passed down) Is there anyway to check if props change, and reset the useEffect?
In that if statement (counter === 0), I manually set it back using setCounter. however i dont know how to check for prop change. whenever last_time prop is updated.
The second parameter in your useEffect is what the function listens to for changes. If you just want to rerun the function in useEffect when a specific variable changes, add it to the array. For example, if you want to rerun it when variable x changes, you would write it like this:
React.useEffect(() => { /* do stuff */ }, [counter, x]);
Documentation: Conditionally firing an effect
I'm new to learning React and Gatsby, and am trying to find the best way to apply simple a Javascript animation to a DOM element within a component. I know how to handle component events with onClick etc, but say for example I want to continuously change the colour of a <span> in my Header.js component every 2 seconds.
import React from 'react';
export default function Header() {
return (
<header>
<p>This is a <span>test!</span></p>
</header>
)
}
I'd then want to use some JS like:
const spanEl = document.querySelector('header span');
let counter = 0;
const changeColor = () => {
if (counter % 2 == 0) {
spanEl.style.color = "red";
} else {
spanEl.style.color = "blue";
}
counter++;
if (counter == 10) counter = 0;
}
setInterval(changeColor, 2000);
I found that I could put this inside a script tag in html.js before the closing body tag, but is there a way to keep this functionality within the component? Do I need to completely rethink my approach when working within this framework?
If you want to approach this with idiomatic React, then I would recommend expressing this behavior using hooks, component lifecycles, and effects.
The official React docs for hooks and effects are very good, I would start there.
import React from 'react';
const noop = () => null;
// Encapsulate the interval behavior
const useInterval = (callback, delay) => {
const savedCallback = useRef(noop);
useEffect(() => {
savedCallback.current = callback;
savedCallback.current();
}, [callback]);
useEffect(() => {
const id = setInterval(savedCallback.current, delay);
return () => clearInterval(id);
}, [delay]);
};
export default function Header() {
const [color, setColor] = useState("blue");
// setColor causes a re-render of the component
const updateColor = setColor(color === "blue" ? "red" : "blue");
useInterval(updateColor, 2000);
// Use the jsx to change the color instead of reaching into the dom
return (
<header>
<p>This is a <span style={{ color }}>test!</span></p>
</header>
)
}
[EDIT: I've just seen the answer from #windowsill, which I think is better than mine; I would recommend going with that solution.]
In a React functional component, you need to use the useReference hook to target an element (rather than selecting it with document.querySelector()) and the useEffecet hook to set and clear the timeout when the component mounts/unmounts:
import React, {
useEffect,
useRef,
useCallback
} from 'react';
export function Header() {
const animatedText = useRef(null);
const runAnimation = useCallback(elem => {
const currColour = elem.style.color;
elem.style.color = (currColour === 'red' && 'blue') || 'red';
}, []);
useEffect(() => {
const animationInterval = setInterval(() => {
runAnimation(animatedText.current);
}, 2000);
return () => {
clearInterval(animationInterval);
}
}, [runAnimation]);
return (
<header>
<p>This is a <span ref={animatedText}>test!</span></p>
</header>
);
}
The useCallback hook is used for optimization purposes and prevent the function runAnimation from being re-defined and initialized every time the component re-renders.
This one is hard one. I spent some days now to solve this issue but all the solutions I tried wasn't accurate.
The thing is that I'm trying to build 'typing text effect'. This should also handle change of the sentences, ie. the first sentence is typed, then it disappears and the other sentences is starting to be typed again, and so on, it goes in the loop. The issue here is that while the first sentence is finished I would like to leave my blinker for a while, for example for 2 sec. This seems to be a perfect place to use setTimeout but all of my trials were unfortunately wrong.
I would really appreciate if you could help me! Thank you
Here's my code:
import React, { Component } from 'react';
import '../../sass/main.scss';
class TypeAnimation extends Component {
constructor(props) {
super(props);
this.state = {
sec: 0,
sec2: 0,
currentSentence: 0,
blinker: '|',
};
}
componentDidMount() {
this.textInterval = setInterval(() => {
this.setState(
prevState => ({
sec: prevState.sec !== this.props.text[0].length ? prevState.sec + 1 : 0,
currentSentence: prevState.sec === this.props.text[0].length ?
prevState.currentSentence + 1 : prevState.currentSentence,
})
);
}, 100);
}
render() {
let otherSentences;
const nextSentences = this.props.text; // here comes the array of few sentences
const currentText = nextSentences[this.state.currentSentence];
if (this.state.currentSentence !== nextSentences.length) {
otherSentences = currentText.substr(0, this.state.sec);
} else {
clearInterval(this.textInterval);
otherSentences = nextSentences[nextSentences.length - 1];
}
return (
<div>
<h2>
{otherSentences}
<span className='blinker'> {this.state.blinker} </span>
</h2>
</div>
);
}
}
export default TypeAnimation;
I've been having a go at learning React.js by writing a small calculator application. I thought things were going quite well until I learned that setState is asynchronous and my mutations therefore do not get immediately applied.
So my question is, what is the best way to keep a running total based upon the values being added to an input. Take the following example:
var Calculator = React.createClass({
total : 0,
getInitialState : function(){
return {
value : '0'
};
},
onValueClicked : function (value) {
var actual, total, current = this.state.value;
if(value === '+') {
actual = this.total = parseInt(this.total, 10) + parseInt(current, 10);
} else {
if(parseInt(current, 10) === 0) {
actual = value;
} else {
actual = current.toString() + value;
}
}
this.setState({ value : actual });
},
render : function () {
return (
<div className="calc-main">
<CalcDisplay value={this.state.value} />
<CalcButtonGroup range="0-10" onClick={this.onValueClicked} />
<CalcOpButton type="+" onClick={this.onValueClicked} />
</div>
)
}
});
var CalcDisplay = React.createClass({
render : function () {
return (
<input type="text" name="display" value={this.props.value} />
);
}
});
var CalcButtonGroup = React.createClass({
render : function () {
var i, buttons = [], range = this.props.range.split('-');
for(i = range[0]; i < range[1]; i++) {
var handler = this.props.onClick.bind(null, i);
buttons.push(<CalcNumberButton key={i} onClick={ handler } />);
}
return (
<div className="calc-btn-group">{ buttons }</div>
);
}
});
var CalcNumberButton = React.createClass({
render : function () {
return (
<button onClick={this.props.onClick}>{this.props.key}</button>
);
}
});
var CalcOpButton = React.createClass({
render : function () {
var handler, op = this.props.type;
handler = this.props.onClick.bind(null, op);
return (
<button onClick={handler}>{op}</button>
);
}
});
React.renderComponent(<Calculator />, document.getElementById('container'));
In the example above I gave up completely on storing the total within the state and kept it outside. I've read that you can have a callback run when setState has finished but in the case of a calculator I need it to be snappy and update quickly. If the state isn't getting updated with each button press and I quickly hit the buttons - things are going to fall out of sync. Is the callback all I am missing or am I thinking about this in completely the wrong way?
Any help appreciated!
It's asynchronous, but much faster than the fastest possible human click.
Aside from that, you should declare instance variables in componentDidMount, e.g.
componentDidMount: function(){
this.total = 0;
}
... but in this case you probably want to store it in state.
.split returns an array of strings, you want to be using numbers:
range = this.props.range.split('-').map(Number)
Or avoid the strings altogether (prefered) with one of these:
<CalcButtonGroup range={[0, 10]} onClick={this.onValueClicked} />
<CalcButtonGroup range={{from: 0, till: 10}} onClick={this.onValueClicked} />
You have define the total variable for your business logic state. Why not store more information like that?
var Calculator = React.createClass({
previous: 0, // <-- previous result
current: 0, // <-- current display
op: '', // <-- pending operator
getInitialState : function(){
return {
value : '0'
};
},
onValueClicked : function (value) {
var actual;
if(value === '+') {
this.previous = this.current;
this.op = '+';
actual = 0; // start a new number
} else if (value === '=') {
if (this.op === '+') {
actual = this.previous + this.current;
} else {
actual = this.current; // no-op
}
} else {
actual = current * 10 + value;
}
this.current = actual; // <-- business logic state update is synchronous
this.setState({ value : String(actual) }); // <-- this.state is only for UI state, asynchronous just fine
},
render : function () {
return (
<div className="calc-main">
<CalcDisplay value={this.state.value} />
<CalcButtonGroup range="0-10" onClick={this.onValueClicked} />
<CalcOpButton type="+" onClick={this.onValueClicked} />
<CalcOpButton type="=" onClick={this.onValueClicked} />
</div>
)
}
});
The basic idea to resolve this.state is use other variables to store your business logic state, and reserve this.state only for UI state.
PS. A real calculator has more complex business logic than this. You should define every state and state machine clearly in spec.