Modify React Elements in ES6 using a for loop and setTimeout - javascript

I am trying to create a typewriter animation like this in my es6 component (essentially, iteratively renders additional passed elements or letters). However, any time I execute / render this component, all that is rendered is the first element / letter, 'a', of the larger set, 'abc'. The timeout period is working fine, so I think that the for loop is failing. How do I properly run a for loop over a setTimeout function in es6 such that my new elements will render? Thanks.
import React from 'react';
import { CSSTransitionGroup } from 'react-transition-group';
import Radium from 'radium';
export default class Logo extends React.Component {
constructor(props) {
super();
this.state = {
final: ''
}
this.typeWriter = this.typeWriter.bind(this);
}
typeWriter(text, n) {
if (n < (text.length)) {
let k = text.substring(0, n+1);
this.setState({ final: k });
n++;
setTimeout( () => { this.typeWriter(text, n) }, 1000 );
}
}
render() {
this.typeWriter('abc', 0);
return (
<div>
<h1>{this.state.final}</h1>
</div>
);
}
}
module.exports = Radium(Logo);

Since this.typeWriter('abc', 0); is in the render function, whenever the state changes, it runs the typewriter method, which updates the state back to a.
Move the this.typeWriter('abc', 0); to componentDidMount(). It will start the type writer when the component has finished rendering.
class Logo extends React.Component {
constructor(props) {
super();
this.state = {
final: ''
}
this.typeWriter = this.typeWriter.bind(this);
}
typeWriter(text, n) {
if (n < (text.length)) {
let k = text.substring(0, n+1);
this.setState({ final: k });
n++;
setTimeout( () => { this.typeWriter(text, n) }, 1000 );
}
}
componentDidMount() {
this.typeWriter('abc', 0);
}
render() {
return (
<div>
<h1>{this.state.final}</h1>
</div>
);
}
}
ReactDOM.render(
<Logo />,
demo
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="demo"></div>

Related

The counter in react executed twice in both given component

I am tring to make a simple counter and display it to the page.
But it renders unexpected o/p.
The counter counts a value twice in example 1 but works perfect as i want in example 2.
What is the reason for not working in ex.1.
What is the background process for this.
// Example: 1
import React, { Component } from 'react';
class Counter extends Component {
constructor() {
super()
this.state = {
count: 0,
isFirstTime: true
}
}
in() {
console.log('How many time function called?'); // consoled one time
if (this.state.isFirstTime) {
this.setState({
isFirstTime: false
})
setInterval(() => {
this.setState({
count: this.state.count + 1
})
}, 1000)
}
}
render() {
return (
<div>
{this.state.isFirstTime && this.in.apply(this)}
Counter: {this.state.count}
</div>
)
}
}
export default Counter;
// Example: 2
import React, { Component } from 'react';
let isFirstTime = true;
class Counter extends Component {
constructor() {
super()
this.state = {
count: 0
}
}
in() {
console.log('How many time function called?'); // consoled one time
if (isFirstTime) {
isFirstTime = false
setInterval(() => {
this.setState({
count: this.state.count + 1
})
}, 1000)
}
}
render() {
return (
<div>
{isFirstTime && this.in.apply(this)}
Counter: {this.state.count}
</div>
)
}
}
export default Counter;
I am running it on React.StrictMode.

How can I Use getElementById in React?

I am trying to do auto text animation in my React project. I can make it work in VanillaJS but I don't know how can I do it in React. (I am beginner at React.)
import React, { Component } from 'react'
class AutoTextSection extends Component {
writeText = () => {
let idx = 1
const text = "This is the text sentence."
document.getElementById('auto-text').textContent = text.slice(0, idx)
idx++
if (idx > document.getElementById('auto-text').length) {
idx = 1
}
setTimeout(this.writeText, 1000)
}
render() {
return (
<section id="auto-text-sec">
<h2 className="text-light" id="auto-text">
{this.writeText()}
</h2>
</section>
)
}
}
Just I can see the first letter. Then it throws me this error :
TypeError: Cannot set property 'textContent' of null.
In react, you must use ref to be able to access the DOM element directly.
Change your code:
import React, { Component } from 'react'
class AutoTextSection extends Component {
constructor(props) {
super(props);
this.autoTextRef = React.createRef();
}
writeText = () => {
let idx = 1
const text = "This is the text sentence."
this.autoTextRef.current.textContent = text.slice(0, idx)
idx++
if (idx > this.autoTextRef.current.length) {
idx = 1
}
setTimeout(this.writeText, 1000)
}
render() {
return (
<section id="auto-text-sec">
<h2 className="text-light" ref={this.autoTextRef}>
{this.writeText()}
</h2>
</section>
)
}
}
In this line, most likely you wanted to use a text node, which is located inside the DOM element:
if (idx > this.autoTextRef.current.length) {
So use:
if (idx > this.autoTextRef.current.textContent.length) {
But your code still contains bugs. It is better to start typing in the componentDidMount lifecycle hook.
Another obvious problem is that when you call writeText, you will always have idx = 1; Therefore, this state must be endured higher. You can use state for this.
Also in the code there is no condition to terminate the recursive call.
The final minimal working code should look like this:
import React, { Component } from 'react'
class AutoTextSection extends Component {
constructor(props) {
super(props);
this.autoTextRef = React.createRef();
this.state = {
idx: 1,
}
}
componentDidMount() {
this.writeText()
}
writeText = () => {
console.log('writeText')
const text = "This is the text sentence."
this.autoTextRef.current.textContent = text.slice(0, this.state.idx)
this.setState((prev) => ({
idx: ++prev.idx,
}))
if (this.state.idx <= text.length) {
console.log('writeText recursion')
setTimeout(this.writeText, 100)
}
}
render() {
return (
<section id="auto-text-sec">
<h2 className="text-light" ref={this.autoTextRef} />
</section>
)
}
}

Passing a function with a parameter to child component and sending return value back to parent to be stored in parent state in reactjs

Please forgive me, I am new to programming and JavaScript/React...
This is the question from my assignment:
Make a counter application using React and Node.js. The user must have the ability to click a button to increase, decrease, or reset a counter. The app must have the following components: Display, DecreaseCount , IncreaseCount, ResetCount. Pass the appropriate functions to be used and current counter value to each component.
I'm not sure what the point is of creating components for those simple operations. I also don't understand what will make those arithmetical components unique if I'm passing them both a function and a value to work on. But I am assuming the point of the assignment is to show that you can pass state to a child, work on it within the child, and then pass the worked-on result back to the parent to be stored in its state.
Here is the main page, Display.js.
For now I'm just trying to get the add functionality to work:
import React, { Component } from 'react';
import IncreaseCount from './IncreaseCount';
import DecreaseCount from './DecreaseCount';
import ResetCount from './ResetCount';
class Display extends Component {
constructor(props) {
super(props);
this.increment = this.increment.bind(this);
this.state = {
count: 0
};
}
increment = numToInc => {
this.setState({ count: numToInc++ });
};
decrement = numToDec => {
this.setState({ count: numToDec-- });
};
reset = numToReset => {
numToReset = 0;
this.setState({ count: numToReset });
};
render() {
return (
<div>
<h2>{this.state.count} </h2>
<IncreaseCount count={this.state.count} operation={this.increment} />
<DecreaseCount count={this.state.count} operation={this.decrement} />
<IncreaseCount count={this.state.count} operation={this.reset} />
</div>
);
}
}
export default Display;
And here is the IncreaseCount component class:
import React, { Component } from 'react';
class IncreaseCount extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
buttonClick = () => {
this.setState({ count: this.props.count }); // I am pretty sure this isn't necessary
this.props.operation(this.state.count);
};
render() {
return <button onClick={this.buttonClick}></button>;
}
}
export default IncreaseCount;
It is not throwing any errors but is not changing the value of either the Increase count or the Display count properties. I was expecting both to be changing in lockstep. My goal is to send the incremented value back to Display to be displayed. Is there a problem with the way I've written and passed my increment function?
You need to use this.props.count within the IncreaseCount
class IncreaseCount extends Component {
buttonClick = () => {
this.props.operation(this.props.count);
};
...
}
A full example might look something like this:
class Display extends Component {
state = {
count: 0
};
increment = numToInc => {
this.setState({ count: numToInc + 1 });
};
decrement = numToDec => {
this.setState({ count: numToDec - 1 });
};
reset = () => {
this.setState({ count: 0 });
};
render() {
return (
<div>
<h2>{this.state.count} </h2>
<Operation
name="+"
count={this.state.count}
operation={this.increment}
/>
<Operation
name="-"
count={this.state.count}
operation={this.decrement}
/>
<Operation
name="Reset"
count={this.state.count}
operation={this.reset}
/>
</div>
);
}
}
class Operation extends Component {
buttonClick = () => {
this.props.operation(this.props.count);
};
render() {
return <button onClick={this.buttonClick}>{this.props.name}</button>;
}
}
Note that you don't have to pass the counter value to each Operation and use a functional setState:
increment = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
Using a single component like <Operation /> is certainly how I'd do it. However, per the requirements of the OP, I'm adding this example that uses all 4 components specified.
import React, { Component } from 'react';
class IncreaseCount extends Component {
render(props) {
return <button onClick={this.props.action}>+</button>;
}
}
class DecreaseCount extends Component {
render(props) {
return <button onClick={this.props.action}>-</button>;
}
}
class ResetCount extends Component {
render(props) {
return <button onClick={this.props.action}>reset</button>;
}
}
class Display extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
this.reset = this.reset.bind(this);
}
increment() {
this.setState({ count: this.state.count + 1 });
}
decrement() {
if (this.state.count > 0) {
this.setState({ count: this.state.count - 1 });
}
}
reset() {
this.setState({ count: 0 });
}
render() {
return (
<div>
<h2>{this.state.count}</h2>
<DecreaseCount count={this.state.count} action={this.decrement} />
<IncreaseCount count={this.state.count} action={this.increment} />
<ResetCount count={this.state.count} action={this.reset} />
</div>
);
}
}
export default Display;
This version also prevents the counter from going below 0.

How to make the background image change every X seconds in React?

FINAL EDIT:
See working code below:
import React, { Component } from 'react';
var images = [
"https://www.royalcanin.com/~/media/Royal-Canin/Product-Categories/cat-adult-landing-hero.ashx",
"https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_March_2010-1.jpg"
]
class App extends Component {
constructor(props) {
super(props);
this.state = { imgPath: "url(" + images[1] + ")" };
}
componentDidMount() {
this.interval = setInterval(() => {
this.setState({ imgPath: "url(" + images[0] + ")" })
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div className="App">
<div className='dynamicImage' style={{ backgroundImage: this.state.imgPath }} >
{console.log(this.state.imgPath)}
</div>
</div >
);
}
}
ORIGINAL THREAD:
I'm trying to use setInterval() to change the image dynamically every X seconds.
I just don't understand where setInterval is supposed to be placed within the code, or what its output is supposed to be.
My current code is:
import React, { Component } from 'react';
// Paths to my images
var images = [
"https://www.royalcanin.com/~/media/Royal-Canin/Product-Categories/cat-adult-landing-hero.ashx",
"https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_March_2010-1.jpg"
]
var imgPath = "url(" + images[1] + ")" // Set original value of path
function f1() {
imgPath = "url(" + images[0] + ")" // Change path when called ?
}
class App extends Component {
render() {
setInterval(f1, 500); // Run f1 every 500ms ?
return (
<div className="App">
<div className='dynamicImage' style={{ backgroundImage: imgPath }} > // Change background image to one specified by imgPath
</div>
</div >
);
}
}
export default App;
The current code outputs the first imgPath's URL, but fails to update it to the one specified within the function f1. To the best of my knowledge, the function f1 does appear to run, as removing it, or setting an undefined variable does return an error. I just can't get it to change imgPath.
Any ideas on what I'm doing wrong, or how I could improve my code?
Cheers
Edit: Commented code + removed unnecessary lines
I would move all your variables into your component and as Akash Salunkhe suggests, use componnentDidMount to setInterval. Don't forget to clear the interval when the component unmounts.
This answer will also work with using any number of images.
class App extends React.Component {
constructor(props) {
super(props);
const images = [
"https://www.royalcanin.com/~/media/Royal-Canin/Product-Categories/cat-adult-landing-hero.ashx",
"https://www.petfinder.com/wp-content/uploads/2013/09/cat-black-superstitious-fcs-cat-myths-162286659.jpg",
"https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_March_2010-1.jpg"
];
this.state = {
images,
currentImg: 0
}
}
componentDidMount() {
this.interval = setInterval(() => this.changeBackgroundImage(), 1000);
}
componentWillUnmount() {
if (this.interval) {
clearInterval(this.interval);
}
}
changeBackgroundImage() {
let newCurrentImg = 0;
const {images, currentImg} = this.state;
const noOfImages = images.length;
if (currentImg !== noOfImages - 1) {
newCurrentImg = currentImg + 1;
}
this.setState({currentImg: newCurrentImg});
}
render() {
const {images, currentImg} = this.state;
const urlString = `url('${images[currentImg]}')`;
return (
<div className="App">
<div className='dynamicImage' style={{backgroundImage: urlString}} >
</div>
</div >
);
}
}
You might want to use this.props or this.state to store the imgPath, otherwise React doesn't know you have changed anything.
Put image path in state and in componentDidMount, use setInterval and inside it use setState to change image path.
#Anurag is correct. You need to use setInterval in componentDidMount and ensure that you call this.setState if you want the render method to rerender. This of course requires that you store the image path in this.state
You can create an endless loop similar to this, you might want to use an array of image urls and write some logic for that. But as you can see I have an endless loop created for the function setImage():
constructor(props) {
super();
this.state = {
image1: props.imageUrls[0],
image2: props.imageUrls[1],
changeImage: true
};
this.setImage();
}
setImage() {
setTimeout(() => {
this.setState({ changeImage: !this.state.changeImage }, this.setImage());
}, 3000);
}
You need to use componentDidMount() React Lifecycle method to register your setInterval function.
Here is a working example
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [
"https://picsum.photos/200/300/?image=523",
"https://picsum.photos/200/300/?image=524"
],
selectedImage: "https://picsum.photos/200/300/?image=523"
};
}
componentDidMount() {
let intervalId = setInterval(() => {
this.setState(prevState => {
if (prevState.selectedImage === this.state.images[0]) {
return {
selectedImage: this.state.images[1]
};
} else {
return {
selectedImage: this.state.images[0]
};
}
});
}, 1000);
this.setState({
intervalId
});
}
componentWillUnmount() {
clearInterval(this.state.intervalId);
}
render() {
return (
<div className="App">
<img src={this.state.selectedImage} alt={"images"} />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can change around the code and can find live demo here:
https://codesandbox.io/s/0m12qmvprp

Changing setState in loop

How does one show a counter going from 1 to 2 to 3 to n on the click of a button. I've tried doing a setState in a for loop but thats not worked.
I know react's setState is async, i've even tried to use prevState, but its not worked.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
this.startCounter = this.startCounter.bind(this);
}
startCounter() {
const self = this;
for (let i = 0; i < 100; i++) {
this.setState(prevState => {
const counter = prevState.counter + 1;
return Object.assign({}, prevState, {counter: counter})
});
}
}
render() {
return (
<div>
Counter Value: {this.state.counter}
<button onClick={this.startCounter}>Start Counter</button>
</div>
)
}
}
export default App;
webpack bin below
https://www.webpackbin.com/bins/-KkU1NJA-ectflyDgf_S
I want to increase the count from 0 to n as a timer of sorts when clicked.
Something like this?
When you run the startCounter() function, you start the interval which increments the counter value by 1, each second. Once it reaches n (5 in this example), it resets.
class App extends React.Component {
constructor() {
super();
this.interval;
this.state = {
counter: 1,
n: 5
};
}
startCounter = () => {
if (this.interval) return; //if the timer is already running, do nothing.
this.interval = setInterval(() => {
let c = (this.state.counter % this.state.n) + 1;
this.setState({
counter: c
});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval); //remove the interval if the component is unmounted.
}
render() {
return (
<div>
Counter Value: {this.state.counter}
<button onClick={this.startCounter}>Start Counter</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Categories

Resources