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

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

Related

Trying to get a counter to work with React and multiple components

I am working on trying to get this counter for pintsLeft to work. This is my first project with React and I feel that I am either not passing the property of the array correctly or my function code is not set correctly.
^^^^KegDetail.js^^^^
import React from "react";
import PropTypes from "prop-types";
function KegDetail(props){
const { keg, onClickingDelete} = props
return (
<React.Fragment>
<hr/>
<h2>{keg.name} Made By {keg.brewery}</h2>
<p>abv {keg.abv}</p>
<h3>price {keg.price}</h3>
<p>{keg.pintsLeft} total pints left</p> {/* Make this a percentage */}
<hr/>
<button onClick={ props.onClickingEdit }>Update Keg</button>
<button onClick={()=> onClickingDelete(keg.id) }>Delete Keg</button>
<button onClick={()=> this.onSellingPint()}>Sell A Pint!</button>
</React.Fragment>
);
}
KegDetail.propTypes = {
keg: PropTypes.object,
onClickingDelete: PropTypes.func,
onClickingEdit:PropTypes.func,
onSellingPint:PropTypes.func
}
export default KegDetail;
That was my KegDetail.js
import React, {useState} from "react";
import NewKegForm from "./NewKegForm";
import DraftList from "./DraftList";
import KegDetail from "./KegDetail";
import EditKegForm from "./EditKegForm";
class DraftControl extends React.Component {
constructor(props){
super(props);
this.state = {
kegFormVisibleOnPage: false,
fullDraftList: [],
selectedKeg: null,
editing: false,
pints: 127,
};
this.handleClick = this.handleClick.bind(this);
this.handleSellingPint = this.handleSellingPint.bind(this);
}
handleClick = () => {
if (this.state.selectedKeg != null){
this.setState({
kegFormVisibleOnPage: false,
selectedKeg: null,
editing: false
});
} else {
this.setState(prevState => ({
kegFormVisibleOnPage: !prevState.kegFormVisibleOnPage,
}));
}
}
handleSellingPint = () => {
this.setState({
pints:this.state.pints-1
})
};
render() {
let currentlyVisibleState = null;
let buttonText = null;
if (this.state.editing){
currentlyVisibleState = <EditKegForm keg = {this.state.selectedKeg} onEditKeg = {this.handleEditingKegInDraftList} />
buttonText = "Return to the Draft List"
}
else if (this.state.selectedKeg != null){
currentlyVisibleState = <KegDetail keg = {this.state.selectedKeg} onClickingDelete = {this.handleDeletingKeg}
onClickingEdit = {this.handleEditClick} onSellingPint = {this.handleSellingPint}/>
buttonText = "Return to the Keg List"
My DraftControl.js code
I don't know what I am doing wrong. I cant get the keg.pintsLeft to pass a number when I console.log, So I may be targeting it incorrectly.
Thanks again!
Try it like this:
handleSellingPint = () => {
this.setState(prevState => {
return {
pints: prevState.pints-1
}
})
};
edit
Also, you invoke the onSellingPint() in a wrong way.
It's not a class component, so React doesn't know what does this refer to.
The function itself is passed in as a prop, so you should reference it like this: <button onClick={() => props.onSellingPint() />
handleSellingPint = (id) => {
const clonedArray = [...this.state.fullDraftList]
for (let i = 0; i < this.state.fullDraftList.length; i++){
if (clonedArray[i].id === id){
clonedArray[i].pintsLeft -= 1
}
}
this.setState({
fullDraftList: clone
});
}
Is what I came up with.
Since you are alteriting a state within an array, you need to clone the array and work on that array, not the "real" one.
Thanks for all your help!

this.transformer.anchorSize is not a function

I was trying to make some changes in Transformer Component in React Konva and faced this error whenever I was trying to change anchor size. I followed the syntax as stated in document - https://konvajs.github.io/api/Konva.Transformer.html#validateAnchors__anchor
The issue is in anchorSize. Please help me. Here's the code -
class TransformerComponent extends React.Component {
componentDidMount () {
this.checkNode ();
}
componentDidUpdate () {
this.checkNode ();
}
checkNode() {
const stage = this.transformer.getStage();
const { selectedShapeName } = this.props;
const selectedNode = stage.findOne ('.' + selectedShapeName);
this.transformer.rotateEnabled (false);
this.transformer.anchorSize (5);
if (selectedNode === this.transformer.node()) {
return;
}
if (selectedNode) {
this.transformer.attachTo (selectedNode);
}
else {
this.transformer.detach();
}
this.transformer.getLayer().batchDraw();
}
render() {
return (
<Transformer
ref = {node => {
this.transformer = node;
}}
/>
);
}
}
anchorSize is a very new property introduces recently. Try to update Konva to the latest version.
You need to bind this for checkNode() as down below:
class TransformerComponent extends React.Component {
constructor(props) {
super(props)
this.checkNode = this.checkNode.bind(this)
}
...

on React Button onClick, start and stop a function(method)

Got a nagging issue and was wondering if anyone can shed some light.
I made a function that automates the routing for my react app...but i am trying to attach a button to this function to ensure it starts and stops on button click. However, when i try the code below...nothing happens
class App extends React.Component {
constructor (props) {
super(props);
this.state = { tabControl: true };
this.handleClick = this.handleClick.bind(this);
this.tabControl = this.tabControl.bind(this);
}
tabControl(props){
RoutePaths(this.props);
}
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
render() {
return (
<div className="clearfix" id="topContent">
<Sidebar />
<div className="white-bg" id="page-wrapper">
<Header tagline="Welcome to JuDGE" />
<button className="AutoTab" onClick={this.handleClick}>
Toggle
</button>
........
but when i try the second code, the tabbing function starts onClick of the button but of course doesn't stop when you click the button again.
class App extends React.Component {
constructor (props) {
super(props);
this.state = { tabControl: true };
this.handleClick = this.handleClick.bind(this);
this.tabControl = this.tabControl.bind(this);
}
tabControl(props){
RoutePaths(this.props);
}
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
render() {
return (
<div className="clearfix" id="topContent">
<Sidebar />
<div className="white-bg" id="page-wrapper">
<Header tagline="Welcome to JuDGE" />
<button className="AutoTab" onClick={this.tabControl}>
Toggle
</button>
Try using the current state instead of the optional callback inside setState:
handleClick() {
this.setState({ tabControl: !this.state.tabControl });
}
I'm not sure i fully get what you are trying to do but it seems to me that you forgot a condition.
You say if you invoke this method:
tabControl(props){
RoutePaths(this.props);
}
it works but won't stop.
Well, you are not running it conditionally.
In this method:
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
You are setting the tabControl state. I think you forgot to check it before running tabControl().
tabControl(props){
const {tabControl} = this.state;
tabControl && RoutePaths(this.props); // invoke of tabControl is true
}
Edit
After seeing the code for RoutePaths as you posted on comments:
function RoutePaths(props) {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let index = 0;
let interval = 3000;
setInterval(() => {
props.history.push(pathUrls[index]);
index = (index + 1) % paths;
}, interval);
}
It seems to me that you will have another problem. you need the id of the interval that returned from setInterval in order to stop it, but you didn't stored it anywhere.
Quote from the docs:
... It returns an interval ID which uniquely identifies the interval,
so you can remove it later by calling clearInterval() ...
So you will need to store it somewhere and call clearInterval with ID.
this.intervalId = setInterval(() => {...});
And somewhere else in your class:
clearInterval(this.interval);
Edit #2
As a followup to your comment, here is a simple usage of interval with react:
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
ticks: 0
};
}
onStart = () => {
this.intervalId = setInterval(() => {
this.setState({ ticks: this.state.ticks + 1 })
}, 500);
}
onStop = () => {
clearInterval(this.intervalId)
}
render() {
const { ticks } = this.state;
return (
<div>
<button onClick={this.onStart}>Start</button>
<button onClick={this.onStop}>Stop</button>
<div>{ticks}</div>
</div>
);
}
}
ReactDOM.render(<Timer />, document.getElementById("root"));
<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="root"></div>
So you can try this approach,
RoutePaths will return the interval id:
function RoutePaths(props) {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let index = 0;
let interval = 3000;
return setInterval(() => {
props.history.push(pathUrls[index]);
index = (index + 1) % paths;
}, interval);
}
and tabControl will store the id and conditionally call or clear the interval:
tabControl() {
const { tabControl } = this.state;
if (tabControl && this.intervalId) { // i'm not sure this is the condition you want, but you can play with it
clearInterval(this.intervalId);
} else {
this.intervalId = RoutePaths(this.props);
}
}
I haven't tested this code but i think it can lead you to a good start.
You don't need tabControl state for what you are trying to do. However, you need to call clearInterval somewhere. Change your handleClick to something like this:
handleClick() {
// change RoutePath to return the id that setInterval returns.
if (this.routePathInterval) {
clearInterval(this.routePathInterval);
this.routePathInterval = null;
} else {
this.routePathInterval = RoutePath(this.props);
}
}
Also, when you call clearInterval and then start it again, your index will start over from zero. You may want to keep the current index in state and pass it to RoutePaths, if you want to resume from the index that you were on.
edit:
On second thought, you don't need to keep the index in state, since you don't want to trigger a re-render when you increment it. However, you should make index an instance variable and make RoutePath an instance method of your App component.
First, initialize this.index = 0; in your constructor and then:
routePaths() {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let interval = 3000;
return setInterval(() => {
this.props.history.push(pathUrls[index]);
this.index = (this.index + 1) % paths;
}, interval);
}

Modify React Elements in ES6 using a for loop and setTimeout

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>

ES6 - Warning: setState(…): Cannot update during an existing state transition

I am rewriting some old ReactJS code, and got stuck fixing this error (the error repeats about 1700 times in the console, the DOM does not render at all):
Warning: setState(...): Cannot update during an existing state
transition (such as within render or another component's
constructor). Render methods should be a pure function of props and
state; constructor side-effects are an anti-pattern, but can be moved
to componentWillMount.
I am a Component that passes it's state down to a component that should render some controls. Based on the clicked controls, the state should change, and new controls should render.
So this is my Container component:
class TeaTimer extends Component {
constructor(props) {
super(props);
this.state = {
count: 120,
countdownStatus: 'started'
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus) {
switch (this.state.countdownStatus) {
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count:0});
}
}
}
componentWillUnmount() {
clearInterval(this.timer);
delete this.timer;
}
startTimer() {
this.timer = setInterval(() => {
let newCount = this.state.count -1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
if(newCount === 0) {
this.setState({countdownStatus: 'stopped'});
}
}, 1000)
}
handleStatusChange(newStatus) {
this.setState({ countdownStatus: newStatus });
}
render() {
let {count, countdownStatus} = this.state;
let renderStartStop = () => {
if (countdownStatus !== 'stopped') {
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
} else {
return <div>This will be the slider form</div>
}
};
return(
<div className={styles.container}>
<p>This is the TeaTimer component</p>
<Clock totalSeconds={count}/>
{renderStartStop()}
</div>
)
}
}
And this is my controls component:
class StartStop extends Component {
constructor(props) {
super(props);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={()=> this.onStatusChange('stopped')}>Reset</button>;
} else {
return <button onClick={()=> this.onStatusChange('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
I am sorry about the wall of text, but I really can;t figure out where the error is coming from - and therefor don't know which part of the code I can leave out.
I have tried implementing the solution found in a seemingly related question, but can't get it to work either.
I think you have a typo in this line:
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
It should be:
return <StartStop countdownStatus={countdownStatus} onStatusChange={() => this.handleStatusChange}/>
You seem to be calling the method handleStatusChange instead of passing it as a callback.
Your metods call each other so you must define two instance of your metods.
class StartStop extends Component {
constructor(props) {
super(props);
this.onStatusChangeReset=this.onStatusChange.bind(this);
this.onStatusChangeStart=this.onStatusChange.bind(this);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={this.onStatusChangeReset('stopped')}>Reset</button>;
} else {
return <button onClick={this.onStatusChangeStart('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
In this line in your return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/> gives the warning, the handleStatusChanged function is called on pressing a button which tries to change the state by setState keyword. whenever the state is changed render function is called again but in your case render function was in progress of returning while the render function is called again by setState keyword.

Categories

Resources