Changing setState in loop - javascript

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>

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.

why I am getting warning-Functions are not valid as a React child. .......?

I was learning about various lifecycle components, so I have a counter in my code, which can be increased or decreased using buttons and I have a seed generator which should be called on button click and it should change the value of the counter to seed, I have added function for the seed to be set to Number.parseInt(Math.random() * 100)
when I run the code, it gives warning in chrome,
also when I click on increment , the counter is set to () => this.setState({ seed: Number.parseInt(Math.random() * 100) })1 , and when I press decrement(click) the counter is set to NaN.
There was a similar question related to this warning but that code was not related to mine.
APP COMPONENT
import React from "react";
import Counter from "./Counter";
import "./App.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mount: true,
ignoreProp: 0,
seed: 40
};
this.mountCounter = () => this.setState({ mount: true });
this.unmountCounter = () => this.setState({ mount: false });
this.ignoreProp = () => this.setState({ ignoreProp: Math.random() });
this.seedGenerator = () =>
this.setState({ seed: Number.parseInt(Math.random() * 100) });
}
render() {
let counter = null;
if (this.state.mount) {
counter = (
<Counter ignoreProp={this.state.ignoreProp} seed={this.seedGenerator} />
);
}
return (
<div className="App">
<button onClick={this.mountCounter} disabled={this.state.mount}>
Mount Counter
</button>
<button onClick={this.unmountCounter} disabled={!this.state.mount}>
Unmount Counter
</button>
<button onClick={this.ignoreProp}>Ignore Prop</button>
<button onClick={this.seedGenerator}>Generate seed</button>
{counter}
</div>
);
}
}
export default App;
COUNTER COMPONENT
import React from "react";
class Counter extends React.Component {
constructor(props) {
console.log("Constructor");
super(props);
this.state = {
counter: 0,
seed: 0
};
this.increment = () => this.setState({ counter: this.state.counter + 1 });
this.decrement = () => this.setState({ counter: this.state.counter - 1 });
}
static getDerivedStateFromProps(props, state) {
if (props.seed && state.seed !== props.seed) {
return {
seed: props.seed,
counter: props.seed
};
}
return null;
}
componentDidMount() {
console.log("Component Did Mount");
console.log("-------------------");
}
shouldComponentUpdate(nextProps, nextState) {
if (
nextProps.ignoreProp &&
this.props.ignoreProp !== nextProps.ignoreProp
) {
console.log("Should Component Update- DO NOT RENDER");
return false;
}
console.log("Should Component Update- RENDER");
return true;
}
render() {
console.log("Render");
return (
<div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
<div className="counter">Counter: {this.state.counter}</div>
</div>
);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("Component Did Update");
console.log("--------------------");
}
componentWillUnmount() {
console.log("Component Will Unmount");
console.log("----------------------");
}
}
export default Counter;
You pass seedGenerator, a function, as the seed prop down to Counter, and since you have
return {
seed: props.seed,
counter: props.seed
}
in getDerivedStateFromProps (likely a copy-paste typo?), the
Counter: {this.state.counter}
render fragment tries to render seedGenerator, a function.

setting the state at the beginning

function returns a random string every 10 seconds, I want to set a word from inside the array of strings at the beginning of the function
I tried to set state inside at the start of the life cycle
componentDidMount(){
this.setState({
randomItem:
this.setState({randomItem:this.randomItemGenerator()})
},
this.interval = setInterval(() => {
this.setState({randomItem:this.randomItemGenerator()})
}, 10000)
});
-Component
class Word extends Component {
state={
randomItem:''
}
myArray = [
"esplendor",
"diciendo",
"impredecible",
"problema",
"terreno",
"instante",
];
randomItemGenerator = () => (
this.myArray[Math.floor(Math.random()*this.myArray.length)]
)
componentDidMount(){
this.setState({
randomItem: this.setState({randomItem:this.randomItemGenerator()})
},
this.interval = setInterval(() => {
this.setState({randomItem:this.randomItemGenerator()})
}, 10000)
});
render(){
return(
<div><h3>{this.state.randomItem}</h3></div>
)
}
}
is there another lifecycle before componentdidmount?
Since the myArray and randomItemGenerator don't use props or other state, you can move the outside of the component, and use them when you initialise the state.
const myArray = [
"esplendor",
"diciendo",
"impredecible",
"problema",
"terreno",
"instante",
]
const randomItemGenerator = () => (
myArray[Math.floor(Math.random() * myArray.length)]
)
class Word extends React.Component {
state = {
randomItem: randomItemGenerator()
}
componentDidMount() {
this.interval = setInterval(() => {
this.setState({
randomItem: randomItemGenerator()
})
}, 10000)
}
componentWillUnmount() { // clear the interval when the component is unmounted
clearInterval(this.interval);
}
render() {
return (
<div><h3>{this.state.randomItem}</h3></div>
)
}
}
ReactDOM.render(
<Word />,
root
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
However, you might want to get the array of words from props. In this case, change the randomItemGenerator to accept an array as it's param, and pass the relevant property (words in the example) when you call the function.
const randomItemGenerator = (words) => (
words[Math.floor(Math.random() * myArray.length)]
)
class Word extends React.Component {
state = {
randomItem: randomItemGenerator(this.props.words)
}
componentDidMount() {
this.interval = setInterval(() => {
this.setState({
randomItem: randomItemGenerator(this.props.words)
})
}, 10000)
}
componentWillUnmount() { // clear the interval when the component is unmounted
clearInterval(this.interval);
}
render() {
return (
<div><h3>{this.state.randomItem}</h3></div>
)
}
}
const myArray = [
"esplendor",
"diciendo",
"impredecible",
"problema",
"terreno",
"instante",
]
ReactDOM.render(
<Word words={myArray} />,
root
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Just call your function in the state initialization:
class Word extends Component {
state = {
randomItem: this.randomItemGenerator()
}

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>

Categories

Resources