I am trying to change the image displayed every 1 second the first image appears then switches to the alt display and does not continue switching the pictures
export default class Slideshow extends Component {
constructor(props) {
super(props);
this.getImageId = this.getImageId.bind(this);
this.switchImage = this.switchImage.bind(this);
this.init = this.init.bind(this);
this.state = {
currentImage: 0,
image: 0
};
}
getImageId() {
if(this.currentImage < 3) {
this.setState({
currentImage: this.state.currentImage +1
})
} else {
this.setState({
currentImage: 0
})
}
return this.currentImage;
}
switchImage() {
this.setState({
image: this.getImageId()
});
}
init() {
setInterval(this.switchImage, 1000)
}
render() {
const imagePath = [guy, girl, wash, swifer];
this.init();
return (
<div className="slideshow-container">
<img src={imagePath[this.state.image]} alt="cleaning images"/>
</div>
);
}
}
Pictures will switch every 1 seconds to the next picture in the array and go back to original after going through whole array
Try something like this instead: https://codesandbox.io/s/naughty-sara-q3m16
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.switchImage = this.switchImage.bind(this);
this.state = {
currentImage: 0,
images: [
"https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
"https://img.purch.com/w/660/aHR0cDovL3d3dy5saXZlc2NpZW5jZS5jb20vaW1hZ2VzL2kvMDAwLzEwNC84MzAvb3JpZ2luYWwvc2h1dHRlcnN0b2NrXzExMTA1NzIxNTkuanBn",
"https://d17fnq9dkz9hgj.cloudfront.net/uploads/2012/11/152964589-welcome-home-new-cat-632x475.jpg",
"https://i.ytimg.com/vi/jpsGLsaZKS0/maxresdefault.jpg"
]
};
}
switchImage() {
if (this.state.currentImage < this.state.images.length - 1) {
this.setState({
currentImage: this.state.currentImage + 1
});
} else {
this.setState({
currentImage: 0
});
}
return this.currentImage;
}
componentDidMount() {
setInterval(this.switchImage, 1000);
}
render() {
return (
<div className="slideshow-container">
<img
src={this.state.images[this.state.currentImage]}
alt="cleaning images"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
We can simplify your code by doing a couple of things:
Put the images-array in the state, so that we can iterate over
the image-paths and keep track of the current images index.
So now we can consolidate switchImage and getImageId into a
single function that serves the same purpose. We just check the
currentImage (index) against the length of the array.
React also has a life-cycle method called componentDidMount()
which executes logic right after a component is rendered the
first-time. I used this to replace the init() function. There is an issue with calling init() in render(). Every time a component re-renders, it executes the logic in render(), which means you would be creating a new setInterval() on every subsequent re-render. componentDidMount() only triggers a single time, making it perfect for defining intervals.
The main issue with your code is that you called init function with in render function, whenever state get update render executes as well, so init function call again and again each time render function execute
the solution is to set intervals in componentDidMount function
componentDidMount run only one time after component mount in the DOM, for help related to react life cycle function do visit the official documentation
https://reactjs.org/docs/react-component.html
also have a look this post image
https://reactjs.org/docs/react-component.html
Related
I am changing the state and I can see in the console.log the new state, however, the TextArea does not show the new state, but only when its first displayed.
Here is my implementation:
import React, {Component} from 'react';
import {TextArea} from "semantic-ui-react";
class Output extends Component {
static myInstance = null;
state = { keys:[] }
updateState(){
// this method is called from outside..
this.setState({ keys:this.state.keys.push(0) });
// I can see that keys are getting increase
console.log(this.state.keys);
}
render() {
return (
<TextArea value={this.state.keys.length} />
);
}
}
TextArea will keep showing 0, although the length of state.keys increases..
Any idea?
Never mutate the state.
To update the state, use this syntax:
this.setState(prevState => ({
keys: [...prevState.keys, newItem]
}))
you dont call your updateState function to update your state , it just exists over there, for pushing 0 to your state in order to reRender your component with new state, you can call your method in componentDidMount like below:
componentDidMount = () => {
this.updateState()
}
this will excute your updateState function immediately after component mounts into the dom.
Here is the working example
Firstly you should call your function to update the state
Example on jsfiddle
class Output extends React.Component {
static myInstance = null;
state = { keys:[] }
componentDidMount(){
this.updateState();
}
updateState() {
const newKeys = [...this.state.keys,0]
this.setState({ keys:newKeys });
}
onTextareaChange(){}
render() {
return (
<textarea value={this.state.keys.length} onChange= {this.onTextareaChange} />
);
}
}
ReactDOM.render(<Output />, document.querySelector("#app"))
I've got a may confusing question because it does not fit standard-behaviour how react and the virtual dom works but i would like to know the answer anyway.
Imagine i've got a simple react-component which is called "Container".
The Container-component has a "div" inside of the render-method which contains another component called "ChildContainer". The "div" which surrounds the "ChildContainer" has the id "wrappingDiv".
Example:
render() {
<Container>
<div id="wrappingDiv">
<ChildContainer/>
</div>
</Container
}
How can i destroy the "ChildContainer"-component-instance and create a completly new one. Which mean the "ComponentWillUnmount" of the old instance is called and the "ComponentDidMount" of the new component is called.
I don't want the old component to update by changing the state or props.
I need this behaviour, because an external library from our partner-company got a libary which change the dom-items and in React i'll get a "Node not found" exception when i Update the component.
If you give the component a key, and change that key when re-rendering, the old component instance will unmount and the new one will mount:
render() {
++this.childKey;
return <Container>
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
The child will have a new key each time, so React will assume it's part of a list and throw away the old one, creating the new one. Any state change in your component that causes it to re-render will force that unmount-and-recreated behavior on the child.
Live Example:
class Container extends React.Component {
render() {
return <div>{this.props.children}</div>;
}
}
class ChildContainer extends React.Component {
render() {
return <div>The child container</div>;
}
componentDidMount() {
console.log("componentDidMount");
}
componentWillUnmount() {
console.log("componentWillUnmount");
}
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.childKey = 0;
this.state = {
something: true
};
}
componentDidMount() {
let timer = setInterval(() => {
this.setState(({something}) => ({something: !something}));
}, 1000);
setTimeout(() => {
clearInterval(timer);
timer = 0;
}, 10000);
}
render() {
++this.childKey;
return <Container>
{this.state.something}
<div id="wrappingDiv">
<ChildContainer key={this.childKey}/>
</div>
</Container>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Having said that, there may well be a better answer to your underlying issue with the plugin. But the above addresses the question actually asked... :-)
Using hooks, first create a state variable to hold the key:
const [childKey, setChildKey] = useState(1);
Then use the useEffect hook to update the key on render:
useEffect(() => {
setChildKey(prev => prev + 1);
});
Note: you probably want something in the array parameter in useEffect to only update the key if a certain state changes
querying data from a graphql api, storing it in an array, now have to display it using setTimeout or interval ( imagine a data carousel which changes automatically after a specific time) how do i achieve it?
It's important to make sure to clear your interval when your component un-mounts. Otherwise, you'll end up with a nasty set of error messages mentioning how you are setting state on an "unmounted" component.
import React, { Component } from 'react'
class Carousel extends Component {
constructor(props) {
super(props);
this.state = {
intervalId = 0,
updatedProps: {}
}
}
componentDidMount() {
let self = this;
let id = setInterval(function() {
// GRAPHQL => data
this.setState({updatedProps: data, intervalId: id})
})
}
componentWillUnMount() {
clearInterval(this.state.intervalId)
}
render() {
return (
<div>
<Carousel
props={this.state.updatedProps}
/>
</div>
)
}
}
Here it is:
this.setState is the key to your question
setInterval(() => {
call.to.api(data => {
this.setState({graphql_data});
// this will update your ui, as soon as state is set
})
},miliseconds)
I am trying to build a page with some data initialized at first time mounted, and update when websocket server give a response msg when certain button click event is triggered, also I need to ban the button aka. disabled, and tell the user in how many seconds the button is clickable again.
My first thought is, single component, update via states, give a state to the counter, then use setTimeout to count down 1 every 1000ms, turned out that the counter "banCount" worked well, until I add the websocket.send(), then it counted down 2 every time.
I thought that would be because when the websocket server responsed, the state is change, so the whole component is updated, the counter is messed up.
So, I had an idea, separating it into a child component, with its own state, but do nothing when in the life cycle of componentWillReceiveProps, and it will not receive props, so it will just work with it is own state. But the result is with or without separating the counter into a child component, they worked the same.
parent component:
import React from 'react';
import ReactDOM from 'react-dom';
import TestChild from './testChild/testChild';
class TestParent extends React.Component {
constructor(props) {
super(props);
this.state = {
wsData: null,
};
}
componentWillMount() {
this.wsClient = new WebSocket("ws://localhost:9000/server", 'echo-protocol');
this.wsClient.onmessage = msg => {
if (msg) {
this.setState({
wsData: msg.data
});
}
};
}
render() {
const data = () => {
if (this.state.wsData) {
return this.state.wsData;
} else {
return "waiting data";
}
};
return (
<div>
<div>{data()}</div>
<TestChild wsClient={this.wsClient}/>
</div>
);
}
}
ReactDOM.render(
<TestParent />,
document.getElementById('reactWrapper')
);
and the Child Component:
import React from 'react';
class TestChild extends React.Component {
constructor(props) {
super(props);
this.count = null;
this.state = {
banCount: this.count
};
this.wsClient = this.props.wsClient;
this.countupdate = 0;
}
banCount() {
this.setState({
banCount: this.count
});
}
callNext(n) {
this.wsClient.send('can you hear me');
this.count = n;
this.banCount();
}
componentDidUpdate() {
if (this.count > 0) {
setTimeout(() => {
this.count -= 1;
this.banCount();
}, 1000);
} else if (this.count === 0) {
this.count = null;
this.banCount();
}
}
render() {
return <button onClick={() => this.callNext(3)}>click me {this.state.banCount}</button>;
}
}
export default TestChild;
Please ignore 'whether the server and websocket connection' works part, they are fine.
I don't know why, I even had not updated Child component, I am really new to React, I really do not know how to debug this, I read this code for hours, but it is just too complicated for me.
Why it counted down 2 every time? and for sure I am wrong, what is the right way.
Please help me with only React and vanilla Javascript, I had not use Redux or Flux and even did not know what they are, thank you.
This is NOT tested code, but should help you to build what you want, I didn't tested your component but I suspect that your setTimeout() is called several times.
import React from 'react';
class TestChild extends React.Component {
constructor(props) {
super(props);
this.state = {
count: null,
};
}
startCountDown() {
var newCount = this.state.count -1;
if(newCount === 0){
clearTimeout(this.timer);
}
this.setState({
count: newCount,
});
}
callNext(n) {
this.wsClient.send('can you hear me');
this.setState({
count: n,
});
this.timer = setTimeout(() => {
startCountDown();
}, 1000);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
return <button disabled={this.state.count>0} onClick={() =>
this.callNext(3)}>click me {this.state.count}</button>;
}
}
export default TestChild;
Finally I worked it out.
It is because React will re-render all the child component with or without setting children's new states. The only way to stop it from re-render is to use ShouldComponentUpdate, so:
shouldComponentUpdate() {
return this.state.banCount !== null;
}
will work, as when the child component receiving props after websocket.send(), this.count is still null, but right after the websocket.send(), this.count is set to 3, so the child component will update since.
Also another workround:
callNext(n) {
this.wsClient.send('can you hear me');
this.count = n;
}
componentWillReceiveProps(nextProps) {
this.data = nextProps.datas;
this.setState({
banCount: this.count
});
}
in this workround, without shouldComponentUpdate() the child component will always re-render when its parent receive websocket data, so in the click handler function, stop calling bancount(), so it would not update itself, but set the state when receive nextProps, that will trigger the re-render.
To sum all above:
child component will always re-render with or without setting state via new props unless shouldComponentUpdate return false, I alreay called bancount() in the click handler function, trigger child component to update the state itself, but after the parent component receiving websocket data, it triggered state updating again, that is why it run double times.
I'm building my first React app and am seemingly way over my head. Anyway, I'm trying to take a component like this:
export default class Timer extends Component {
constructor(props) {
super(props)
this.state = { clock: 0, time: '' }
}
componentDidMount() {
this.play()
}
componentWillUnmount() {
this.pause()
}
pause() {
if (interval) {
clearInterval(interval)
interval = null
}
}
render() {
return (
<div className="react-timer" pause={this.pause.bind(this)}>
<h3 className="seconds"> {this.state.time} {this.props.prefix}</h3>
<br />
</div>
)
}
}
Timer.propTypes = {
options: PropTypes.object
}
and access it's state and pause functions in another component that is it's parent because the timer is embedded in the other component.
Other component:
class Level1 extends Component {
constructor(props) {
super(props);
this.state = {x: 0, y: 0};
}
render () {
...
return (
<div>
<div ref="elem" onMouseMove={this._onMouseMove.bind(this)} id="gameBoard">
<img id="waldo1" src={require('../images/waldo1(1).jpg')} alt="waldo"/>
<h2> {x} , {y}</h2>
</div>
<button onClick={this.refs.mytimer.pause()}>Pause</button>
<Timer ref="mytimer" options={OPTIONS}/> <-- Here Timer
</div>
) // return
} // render
} //component
For example, I'm going to write a function like this:
var isWaldoFound = function (x , y ) {
if (true) {
Timer.pause()
hashHistory.push({ '/result' + this.Timer.state})
} else {
..Whatever..
}
}
I've tried using refs and props but when I log anything it's undefined. When I log timer it shows me the Timer but when I log Timer.pause or Timer.pause() it says it is undefined.
How can I go about doing this?
React is all about your UI being a function of your state; i.e., React is all about state. So, instead of doing things imperatively, you change your state and the components "react" to the change in state.
You might want to read:
https://zhenyong.github.io/react/docs/more-about-refs.html
and
https://facebook.github.io/react/docs/refs-and-the-dom.html
In your case, you might pass the "current state" as a prop to the Timer, and then in componentWillRecieveProps() in the Timer, check the new "current state" against the current one, and if there's a change in state, then have the timer transition itself to the new "current state", rather than trying to imperatively tell the timer to transition to a new state.
So, what you're trying to do is not really the "React" way, but you should still be able to make it work...
First, I'd recommend using a callback ref instead of a string ref, and ideally the callback is a class method on the outer component instead of a fat-arrow function so that a new function isn't generated with each render. Use the ref to capture the Timer instance on first render, store that instance as an instance variable of your Level1 component, and then use the instance later when the pause button is clicked.
Something like this maybe (untested of course):
class Level1 extends Component {
constructor(props) {
super(props);
this.captureTimer.bind(this)
this.onPauseTimerButtonClicked.bind(this)
this.state = {x: 0, y: 0};
}
captureTimer(ref) {
this.timer = ref
}
onPauseTimerButtonClicked() {
this.timer.pause()
}
render () {
...
return (
<div>
<div ref="elem" onMouseMove={this._onMouseMove.bind(this)} id="gameBoard">
<img id="waldo1" src={require('../images/waldo1(1).jpg')} alt="waldo"/>
<h2> {x} , {y}</h2>
</div>
<button onClick={this.onPauseTimerButtonClicked}>Pause</button>
<Timer ref={timer => this.captureTimer(timer)} options={OPTIONS}/> <-- Here Timer
</div>
) // return
} // render
} //component
The reason it's not working is probably because the pause function is not bound to the class.
Try binding it in the constructor of your component:
constructor(props) {
super(props)
this.state = { clock: 0, time: '' }
this.pause = this.pause.bind(this);
}
Try this pen: https://codepen.io/CarlosEME/pen/xgmYXZ
Assuming isWaldoFound is a method of Level1.. If so , replace :
Timer.pause()
By :
this.refs.mytimer.pause();