setState in ReactJS - javascript

I'm new to ReactJS and I can't seem to find out why the result of the following setState is not as I expect it to be (i.e. to increment the value every second by 1)
import React from 'react';
import ReactDOM from 'react-dom';
class Layout extends React.Component {
constructor() {
super();
this.state = {
name: "Behnam",
i: 0
}
}
render() {
setInterval(() => {
this.setState({ name : "Behnam" + this.state.i });
this.setState({ i: this.state.i + 1 });
}, 1000);
return (
<div className="container">
{this.state.name}
</div>
);
}
}
ReactDOM.render(<Layout />, document.getElementById('app'));
Instead the output string rapidly increases (I guess as fast as react is trying to keep its' virtual DOM updated). So I was wondering what is the right way to do this?

Every time you change the state, you rerender the component.
Because you initiated the setInterval in the render method, you get another interval, which changes the state, and rerenders, and so on.
Move the setInterval to componentDidMount, which is invoked only once, when the component mounts:
import React from 'react';
import ReactDOM from 'react-dom';
class Layout extends React.Component {
constructor() {
super();
this.state = {
name: "Behnam",
i: 0
}
}
componentDidMount() { set the interval after the component mounted, and save the reference
this.interval = setInterval(() => {
this.setState({
name: `Behnam${this.state.i}`,
i: this.state.i + 1
});
}, 1000);
}
componentWillUnmount() {
this.interval && clearInterval(this.interval); // clear the interval when the component unmounts
}
render() {
return (
<div className="container">
{this.state.name}
</div>
);
}
}
ReactDOM.render(<Layout />, document.getElementById('app'));

Currently, it is creating an interval every time the component is rendered, so there are multiple timers incrementing the value. You probably want to do it in componentDidMount() instead of render(). See docs.
import React from 'react';
import ReactDOM from 'react-dom';
class Layout extends React.Component {
constructor() {
super();
this.state = {
name: "Behnam",
i: 0
}
}
componentDidMount() {
setInterval(() => {
this.setState({ name : "Behnam" + this.state.i });
this.setState({ i: this.state.i + 1 });
}, 1000);
}
render() {
return (
<div className="container">
{this.state.name}
</div>
);
}
}
ReactDOM.render(<Layout />, document.getElementById('app'));

Every time a render is triggered, you're calling setInterval again, adding to the number of active intervals on the page.
You should perhaps make use of another lifecycle method, such as componentDidMount. You should remember to save the interval ID returned by setInterval, so that you can call clearInterval in componentWillUnmount.

Related

Functional component definition inside class component's render() method, state resets when created through JSX, but not when called directly

If I define a functional component inside of a class component's render() method, then the component's state is getting reset every time the class component's render() method is called. If I call the functional component directly though, the state does not reset.
Look at the following example:
import React from 'react';
import Counter from './Counter'
const MilliCounter = ({name}) => {
return <Counter name={name} initial={1e6} />
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
flag: false
}
}
onButtonClick = (event) => {
this.setState({flag: !this.state.flag});
};
render() {
const HundoCounter = ({name}) => {
return <Counter name={name} initial={100} />
};
return (<div>
<button onClick={this.onButtonClick}>Change State</button>
<div>{`Flag: ${this.state.flag}`}</div>
<HundoCounter name="Component Def Inside render() - Hundo JSX"/>
{HundoCounter({name: 'Component Def Inside render() - Hundo Function Call'})}
<MilliCounter name="Component Def Outside render() - Milli JSX"/>
{MilliCounter({name: 'Component Def Outside render() - Milli Function Call'})}
</div>)
}
}
export default App;
import * as React from 'react'
export default class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: props.initial
}
}
onButtonClick = (event) => {
this.setState({
count: this.state.count + 1
})
};
render() {
return (
<div style={{border: '1px solid black', margin: '1rem', padding: '0.67rem'}}>
<h6>{this.props.name}</h6>
<p>Count: {this.state.count}</p>
<button onClick={this.onButtonClick}>Click Me</button>
</div>
)
}
}
Here's a video showing the demo app in action.
https://i.imgur.com/WfS8DXJ.mp4
As you can see, when the button is clicked it changes the flag to true which forces a re-render. During this the state of the functional component HundoCounter defined with JSX is reset, but not the one that is called directly.
It makes sense to me that the state would reset, because it's creating a new definition of HundoCounter every time render() is called. How come the state for the HundoCounter that's called directly as a function does not get reset?
I believe the reason is because you're re-rendering the parent component, which then resets the initial={100} to set it back to 100, when the child component is re-rendered due to the parent re-render.
Which is the intended behaviour
As for why the second one isn't resetting i don't know, but it seems odd that it is not, since it's value should also be reset
Okay it seems odd. I think it is related with React's reconciliation and diff algorithm. When I add the key property to Counter component it behaves what we expect.
const HundoCounter = ({ name }) => {
console.log("hundo function")
return <Counter key={Math.random()} name={name} initial={100} />
};
I think render() method is called and the diff algorithm recurses on the previous result and the new result and somehow function surround the component and behaves like it is the same component. Btw I like this experiment :)

How to use setInterval with React?

I want the following app to print DAnce every second on the screen.
import React from 'react';
import ReactDOM from 'react-dom';
export class Sample extends React.Component {
sample(text, i) {
return <h1> DAnce</h1>;
}
render(){
return (
<div className="text-intro" id="site-type">
{setInterval(()=>this.sample('igod',1),1000)}
</div>
);
}
}
ReactDOM.render(
<Sample />,
document.getElementById('root')
);
Instead I get 5 printed on the screen.
How do I obtain the desired effect?
You could store a count in your state, and use an interval to increment this count every second and create count many Dance in the render method.
Example
class Sample extends React.Component {
state = { count: 0 };
componentDidMount() {
this.interval = setInterval(() => {
this.setState(previousState => {
return { count: previousState.count + 1 };
});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div className="text-intro" id="site-type">
{Array.from({ length: this.state.count }, () => <div>Dance</div>)}
</div>
);
}
}
ReactDOM.render(<Sample />, 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>
You have to think has the text as something that is changing in your app. That is ideal for state.
I don't know what your functions sample does. But let's say you have it declared.
const sample = (text, i) => { ... };
Then you can do it like this:
class Sample extends Component {
state = {
text: sample('igod', 1),
};
componentDidMount() {
setTimeout(() => {
this.setState({
text: this.sample('igod',1)
});
}, 1000);
}
render() {
return (
<div className="text-intro" id="site-type">
{this.state.text}
</div>
);
}
}
Basically what happens is, when your component mounts you will start a timeout where every 1 second it will update the state thus updating your UI.
Try this,
import React from 'react';
import ReactDOM from 'react-dom';
export class Sample extends React.Component {
sample(text, i) {
return <h1> DAnce</h1>;
}
render(){
return(
<div className="text-intro" id="site-type">
{setTimeout(()=>{this.sample('igod',1)}, 1000)}
</div>
);
}
}
ReactDOM.render(
<Sample />,
document.getElementById('root')
);
You have used setTimeout which will be called only number of times while component renders.
You need to use setInterval to work each second.
Replace with this. Hopw this will help you.
{setInterval(()=>this.sample('igod',1),1000)}
What if you use the react lifecycle:
export class Sample extends React.Component {
sample(text, i) {
this.setState({ text });
}
render(){
return(
<div className="text-intro" id="site-type">
<h1>{this.state.text}</h1>
{setTimeout(()=>this.sample('igod',1),1000)}
</div>
);
}

Timer does not countdown in ReactJS

I am building a simple stopwatch app in ReactJS (count down to 0).
So far I have an input where the user can set how long the timer should be and a button which updates the timer state with the input data.
However I am not able to have the timer countdown, even with using setInterval().
App.jsx
import React, {Component} from 'react';
import Timer from './timer';
import './app.css';
import { Form, FormControl, Button} from 'react-bootstrap';
export class App extends Component {
constructor(props) {
super(props);
this.state = {
timerPosition: '',
newTimerPosition: '',
};
}
startTimer() {
this.setState({timerPosition:this.state.newTimerPosition})
}
render() {
return (
<div className="App-title"> Stopwatch </div>
<Timer timerPosition={this.state.timerPosition} />
<Form inline>
<FormControl
className="Deadline-input"
onChange={event => this.setState({newTimerPosition: event.target.value})}
placeholder='Set timer'/>
<Button onClick={() => this.startTimer()}>Set new timer</Button>
</Form>
</div>
)}
}
export default App;
Timer.jsx
import React, {Component} from 'react';
import './app.css';
export class Timer extends Component {
constructor(props) {
super(props);
this.state = {
secondsRemaining: ''
}
}
componentWillMount(){
this.startTimer(this.props.timerPosition);
}
componentDidMount(){
setInterval(() => this.startTimer(this.props.timerPosition), 1000);
}
startTimer(timerCallback) {
this.setState({secondsRemaining: timerCallback --})
}
render() {
return (
<div>
<div></div>
{this.state.secondsRemaining} seconds remaining
</div>
)}
}
export default Timer;
The timer does not decrement every second and just stays at the original position.
The primary issue is in the interval callback in Timer's componentDidMount:
componentDidMount(){
setInterval(() => this.startTimer(this.props.timerPosition), 1000);
}
Note that you're constantly reusing this.props.timerPosition as the timer position, instead of using the current state. So you're setting the state back to the initial state from the props.
Instead, you want to use the current state. How you use that state will depend on how you want the timer to behave, but beware of two things:
Never call this.setState({foo: this.state.foo - 1}) or similar. State updates are asynchronous and can be combined. Instead, pass a callback to setState.
Nothing is guaranteed to happen at any particular time or on a specific interval, so don't just decrement your counter; instead, see how long it's been since you started, and use that information to subtract from your initial counter value. That way, if your timer is delayed, you still show the correct value.
The problem is you keep using the same prop value secondsRemaining. You decrement the same value in the propthis.props.timerPosition each time. So the state value secondsRemaining never changes.
Instead decrement the secondsRemaining value from state using the setState method which takes a callback. The first parameter of the callback is the current state. so you do:
this.setState((prevState, props) => {
return { secondsRemaining: prevState.secondsRemaining - 1 };
});
You also need to set your initial state value to the supplied props value:
componentWillMount(){
this.setState({ secondsRemaining: this.props.timerPosition });
... other code
}

Uncaught RangeError Maximum call stack size exceeded in React App

I'm learning React and for training, I want to create a basic Todo app. For the first step, I want to create a component called AddTodo that renders an input field and a button and every time I enter something in the input field and press the button, I want to pass the value of the input field to another component called TodoList and append it to the list.
The problem is when I launch the app, the AddTodo component renders successfully but when I enter something and press the button, the app stops responding for 2 seconds and after that, I get this: Uncaught RangeError: Maximum call stack size exceeded and nothing happens.
My app source code: Main.jsx
import React, {Component} from 'react';
import TodoList from 'TodoList';
import AddTodo from 'AddTodo';
class Main extends Component {
constructor(props) {
super(props);
this.setNewTodo = this.setNewTodo.bind(this);
this.state = {
newTodo: ''
};
}
setNewTodo(todo) {
this.setState({
newTodo: todo
});
}
render() {
var {newTodo} = this.state;
return (
<div>
<TodoList addToList={newTodo} />
<AddTodo setTodo={this.setNewTodo}/>
</div>
);
}
}
export default Main;
AddTodo.jsx
import React, {Component} from 'react';
class AddTodo extends Component {
constructor(props) {
super(props);
this.handleNewTodo = this.handleNewTodo.bind(this);
}
handleNewTodo() {
var todo = this.refs.todo.value;
this.refs.todo.value = '';
if (todo) {
this.props.setTodo(todo);
}
}
render() {
return (
<div>
<input type="text" ref="todo" />
<button onClick={this.handleNewTodo}>Add to Todo List</button>
</div>
);
}
}
AddTodo.propTypes = {
setTodo: React.PropTypes.func.isRequired
};
export default AddTodo;
TodoList.jsx
import React, {Component} from 'react';
class TodoList extends Component {
constructor(props) {
super(props);
this.renderItems = this.renderItems.bind(this);
this.state = {
todos: []
};
}
componentDidUpdate() {
var newTodo = this.props.addToList;
var todos = this.state.todos;
todos = todos.concat(newTodo);
this.setState({
todos: todos
});
}
renderItems() {
var todos = this.state.todos;
todos.map((item) => {
<h4>{item}</h4>
});
}
render() {
return (
<div>
{this.renderItems()}
</div>
);
}
}
export default TodoList;
First time componentDidUpdate is called (which happens after first change in its props/state, which in your case happens after adding first todo) it adds this.props.addToList to this.state.todo and updates state. Updating state will run componentDidUpdate again and it adds the value of this.props.addToList to 'this.state.todo` again and it goes infinitely.
You can fix it with some dirty hacks but your approach is a bad approach overall. Right thing to do is to keep todos in parent component (Main), append the new todo to it in setNewTodo (you may probably rename it to addTodo) and pass the todos list from Main state to TodoList: <TodoList todos={this.state.todos}/> for example.
The basic idea of react is whenever you call setState function, react component get updated which causes the function componentDidUpdate to be called again when the component is updated.
Now problem here is you are calling setState function inside componentDidUpdate which causes the component to update again and this chain goes on forever. And every time componentDidUpdate is called it concat a value to the todo. So a time come when the memory gets full and it throws an error. You should not call setState function inside functions like componentWillUpdate,componentDidUpdate etc.
One solution can be to use componentWillReceiveProps instead of componentDidUpdate function like this:
componentDidUpdate(nextProps) {
var newTodo = nextProps.addToList;
this.setState(prevState => ({
todos: prevState.todos.concat(newTodo)
}));
}

ReactJS - Updating parent state when child state is changed

I apologize for the lack of working code, but I'm not sure how to go about doing this, so non-working code it is. I am looking to update the this.state.count in the App class when the state of a ToggleBox is altered. I'm sure this has been asked before, thanks in advance.
import React, { Component } from 'react';
import ToggleBox from '../components/ToggleBox';
class App extends Component {
constructor(props) {
super(props);
this.state = {
total : 60,
count: 0
};
}
getToggles() {
let toggles = [];
for (let i = 0; i < this.state.count; i++) {
toggles.push(<ToggleBox checked={false} key={i} />);
}
return toggles;
}
render() {
let toggles = this.getToggles();
return (
<div className="App">
{{this.state.count}} - {{this.state.total}}
<div className="container-toggle-box">
{toggles}
</div>
</div>
);
}
}
export default App;
...and the component:
import React, {Component} from 'react';
class ToggleBox extends Component {
constructor(props) {
super(props);
this.state = {
active = this.props.checked
};
this.handleClick= this.handleClick.bind(this);
}
handleClick() {
this.setState({active: (this.state.active) ? false : true}
}
render() {
let mark = (this.state.active) ? 'x' : 'o'
return (
<span>
{mark}
</span>
);
}
}
export default ToggleBox;
You need to pass ToggleBox a function that updates the count.
For example:
toggles.push(<ToggleBox
checked={false}
key={i}
incrementCount={() => this.setState({count: this.state.count + 1})}
/>);
Then you just call that method in your child component:
handleClick() {
this.setState({active: (this.state.active) ? false : true};
this.props.incrementCount();
}
This pattern is often referred to as "Flux" (or, to be more accurate, it's a piece of the overall Flux pattern), and it's a core part of how React was designed to be used. By passing the function in in this way your child component doesn't have to know anything about how count works or how it's incremented. This makes things easy for the child, but more importantly it makes it a lot easier when you want to change how the count works, because there's only a single place (the parent component) which controls it.

Categories

Resources