I have got problem with my react component which renders form. Basically, when I enter countdown page, the form just doesn't work (by that I mean it doesnt act at all, I write for example 123 which is 2 min and 3 seconds and nothing happens, just nothing). But, for example, if I go on to main page and back to countdown page, it works. I have noticed that when entering this page the first time, componentWillMount works, but componentDidMount doesn't (it won't console.log the message).
Link to heroku: http://fathomless-lowlands-79063.herokuapp.com/?#/countdown?_k=mj1on6
CountdownForm.jsx
var React = require('react');
var CountdownForm = React.createClass({
onSubmit: function (e) {
e.preventDefault();
var strSeconds = this.refs.seconds.value;
if (strSeconds.match(/^[0-9]*$/)){
this.refs.seconds.value = '';
this.props.onSetCountdown(parseInt(strSeconds, 10));
}
},
render: function () {
return(
<div>
<form ref="form" onSubmit={this.onSubmit} className="countdown-form">
<input type="text" placeholder="Enter time in seconds" ref="seconds" />
<button className="button expanded">Start
</button>
</form>
</div>
);
}
});
module.exports = CountdownForm;
Countdown.jsx
var React = require('react');
var Clock = require('Clock');
var CountdownForm = require('CountdownForm');
var Controls = require('Controls');
var Countdown = React.createClass({
getInitialState: function () {
return {
count: 0,
countdownStatus: 'stopped'
};
},
componentDidUpdate: function (prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus)
{
switch (this.state.countdownStatus){
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count: 0})
case 'paused':
clearInterval(this.timer)
this.timer = undefined;
break;
}
}
},
componentDidMount: function() {
console.log("componentDidMount");
},
componentWillMount: function () {
console.log("componentWillMount");
},
componentWillUnmount: function () {
console.log('componentDidUnmount');
},
startTimer: function () {
this.timer = setInterval(() => {
var newCount = this.state.count - 1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
}, 1000);
},
handleSetCountdown: function (seconds){
this.setState({
count: seconds,
countdownStatus: 'started'
});
},
handleStatusChange: function (newStatus) {
this.setState({
countdownStatus: newStatus
});
},
render: function () {
var {count, countdownStatus} = this.state;
var renderControlArea = () => {
if (countdownStatus !== 'stopped') {
return <Controls countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange} />
} else {
return <CountdownForm onSetCountdown={this.handleSetCountdown} />
}
};
return(
<div>
<Clock totalSeconds={count} />
{renderControlArea()}
</div>
);
}
});
module.exports = Countdown;
I have solved the problem. Main issue was error: "Uncaught Error: Stateless function components cannot have refs.
at invariant" . The problem was with stateless components, so that's why I instead of using arrow functions in Main.jsx and Nav.jsx, I used the React.createClass({}).
Related
I am new to react and decided to practice by implementing a simple stop watch using both class and functional components.
I successfully implemented the stop watch using a class component. Below is the code:
Class Component
class Stopwatch extends Component {
state = {
status: false,
ms: 0,
seconds: 0,
minutes: 0,
};
stopms;
stopSeconds;
stopMinutes;
handleClick = () => {
this.changeStatus();
if (this.state.status) {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
} else {
this.stopms = setInterval(this.changeMs, 1);
this.stopSeconds = setInterval(this.changeSeconds, 1000);
this.stopMinutes = setInterval(this.changeMinutes, 60000);
}
};
changeStatus = () => {
return this.setState((state) => {
return { status: !state.status };
});
};
changeMs = () => {
return this.setState((state) => {
if (state.ms === 99) {
return { ms: 0 };
} else {
return { ms: state.ms + 1 };
}
});
};
changeSeconds = () => {
return this.setState((state) => {
if (state.seconds === 59) {
return { seconds: 0 };
} else {
return { seconds: state.seconds + 1 };
}
});
};
changeMinutes = () => {
return this.setState((state) => {
if (state.seconds === 59) {
return { minutes: 0 };
} else {
return { minutes: state.minutes + 1 };
}
});
};
handleReset = () => {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
this.setState({ seconds: 0, status: false, minutes: 0, ms: 0 });
};
componentWillUnmount() {
clearInterval(this.stopms);
clearInterval(this.stopSeconds);
clearInterval(this.stopMinutes);
}
render() {
return (
<div>
<h1>
{this.state.minutes} : {this.state.seconds} .{" "}
<span>{this.state.ms}</span>
</h1>
<button className="btn btn-lg btn-dark" onClick={this.handleClick}>
{this.state.status === false ? "Start" : "Pause"}
</button>
<button className="btn btn-lg btn-dark" onClick={this.handleReset}>
Reset
</button>
</div>
);
}
}
export default Stopwatch;
Now I'm trying to implement the same code above but using a functional component as shown below:
Functional Component
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
});
let stopms;
let stopSeconds;
let stopMinutes;
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
} else {
stopms = setInterval(changeMs, 1);
stopSeconds = setInterval(changeSeconds, 1000);
stopMinutes = setInterval(changeMinutes, 60000);
}
};
const changeStatus = () => {
return setTimeState((prevState) => {
return { ...prevState, status: !prevState.status };
});
};
const changeMs = () => {
return setTimeState((prevState) => {
if (prevState.ms === 99) {
return { ...prevState, ms: 0 };
} else {
return { ...prevState, ms: prevState.ms + 1 };
}
});
};
const changeSeconds = () => {
return setTimeState((prevState) => {
if (prevState.seconds === 59) {
return { ...prevState, seconds: 0 };
} else {
return { ...prevState, seconds: prevState.seconds + 1 };
}
});
};
const changeMinutes = () => {
return setTimeState((prevState) => {
if (prevState.seconds === 59) {
return { ...prevState, minutes: 0 };
} else {
return { ...prevState, minutes: prevState.minutes + 1 };
}
});
};
const handleReset = () => {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
setTimeState({ seconds: 0, status: false, minutes: 0, ms: 0 });
};
return (
<div>
<h1>
{timeState.minutes} : {timeState.seconds} . <span>{timeState.ms}</span>
</h1>
<button className="btn btn-lg btn-dark" onClick={handleClick}>
{timeState.status === false ? "Start" : "Stop"}
</button>
<button className="btn btn-lg btn-dark" onClick={handleReset}>
Reset
</button>
</div>
);
}
export default Stopwatch;
The Problem
In the class component, I implemented the "Pause" functionality using the handleClick function which calls clearInterval with it's argument as the global variables stopms, stopSeconds, stopMinutes that I declared initially. This worked just fine because these global variables were holding values returned from the respective setInterval when the stop watch started counting.
Now in the functional component, I replicated the same logic by declaring the same global variables using the "let" keyword. But the "Pause" functionality is not working. When the "Start" button hit and the handleClick function called, the setIntervals were called and their return values were stored in the respective global variables. But when the "Pause" button was hit, all the global variables had "undefined" as their values.
Please I would like to know if there's any other way I can declare global variables and use them to hold values throughout a component's life cycle asides using state.
Functional components are executed from top to bottom whenever state changes, so the whole function is re-executed and that's how it returns the new JSX, compare this to class components where only render() function is executed on render, that's how functional components work.
The problem is that your global variables are in fact not global and a part of the function, hence they are re-initialized each time render is happening.
Two ways to solve this
Move your variables to the state
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
stopms : null,
stopSeconds : null,
stopMinutes: null,
});
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(timeState.stopms);
clearInterval(timeState.stopSeconds);
clearInterval(timeState.stopMinutes);
} else {
let stopms = setInterval(changeMs, 1);
let stopSeconds = setInterval(changeSeconds, 1000);
let stopMinutes = setInterval(changeMinutes, 60000);
setTimeState(prev => ({..prev, stopms, stopSeconds, stopMinutes})); // update the values in state
}
};
......
const handleReset = () => {
clearInterval(timeState.stopms); // use the same values to clear them
clearInterval(timeState.stopSeconds);
clearInterval(timeState.stopMinutes);
.....
};
.....
}
Or make them global by placing them outside of your component, Will work but not recommended.
In your component file.
// declare them just above your function
let stopms;
let stopSeconds;
let stopMinutes;
function Stopwatch() {
const [timeState, setTimeState] = useState({
status: false,
ms: 0,
seconds: 0,
minutes: 0,
});
.....
const handleClick = () => {
changeStatus();
if (timeState.status) {
clearInterval(stopms);
clearInterval(stopSeconds);
clearInterval(stopMinutes);
} else {
stopms = setInterval(changeMs, 1);
stopSeconds = setInterval(changeSeconds, 1000);
stopMinutes = setInterval(changeMinutes, 60000);
}
.......
};
I'm playing with ReactJs, and i have this simple code
var User = React.createClass({
render: function () {
return(
<li>
{this.props.email}
</li>
);
}
});
var UserList = React.createClass({
reload: function () {
var xhr = new XMLHttpRequest();
xhr.open('get', "http://localhost:64501/Home/GetUsers", true);
xhr.onload = function () {
var result = JSON.parse(xhr.responseText);
this.setState({ data: result });
console.log(JSON.stringify(result));
}.bind(this);
xhr.send();
},
getInitialState: function () {
return {
data: [
{ email: "bob#gmail.com", id: "1" },
{ email: "boby#gmail.com", id: "2" }
]
};
},
componentDidMount: function () {
window.setInterval(this.reload, 3000);
},
render: function () {
if (this.props.data != null) {
var userNodes = this.props.data.map(function (user) {
return (
<User email={user.email} key={user.id } ></User>
);
});
return (
<div>
<ul>{userNodes}</ul>
</div>
);
}
else {
console.log("this.props.data is null");
return null;
}
}
});
ReactDOM.render(<UserList />, document.getElementById('root'));
I got 2 issues :
1 - the datas returned by getInitialState function are not rendered by the component.
2 - setState does not refresh the component in the reload function.
You are reading this.props.data and you should be reading this.state.data.
Note the difference between component properties, which come externally, and component internal state.
Just replace all this.props.data with this.state.data and your code should work.
I am new to React (I'm used to working with Angular) and I am currently working on filtering my Todo list app based on a category selection.
I cloned the Todo list app from http://todomvc.com/examples/react/#/ . I added a 'category' input, which works, but now I am trying to filter by category once the list is shown.
I currently don't have any search function for categories and am looking for some guidance as to where to start. I'll post the code below but here is the link to my repo if you want to clone it: https://github.com/aenser/todo-react
app.jsx
var app = app || {};
(function () {
'use strict';
app.ALL_TODOS = 'all';
app.ACTIVE_TODOS = 'active';
app.COMPLETED_TODOS = 'completed';
var TodoFooter = app.TodoFooter;
var TodoItem = app.TodoItem;
var ENTER_KEY = 13;
var TodoApp = React.createClass({
getInitialState: function () {
return {
nowShowing: app.ALL_TODOS,
editing: null,
newTodo: '',
newCategory: ''
};
},
componentDidMount: function () {
var setState = this.setState;
var router = Router({
'/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
'/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
'/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
});
router.init('/');
},
handleChange: function (event) {
this.setState({newTodo: event.target.value});
},
handleCategoryChange: function (event) {
this.setState({newCategory: event.target.value});
},
handleNewTodoKeyDown: function (event) {
if (event.keyCode !== ENTER_KEY) {
return;
}
event.preventDefault();
var val = this.state.newTodo.trim();
var cat = this.state.newCategory.trim();
if (val, cat) {
this.props.model.addTodo(val, cat);
this.setState({newTodo: '', newCategory: ''});
}
},
toggleAll: function (event) {
var checked = event.target.checked;
this.props.model.toggleAll(checked);
},
toggle: function (todoToToggle) {
this.props.model.toggle(todoToToggle);
},
destroy: function (todo) {
this.props.model.destroy(todo);
},
edit: function (todo) {
this.setState({editing: todo.id});
},
save: function (todoToSave, text, cat) {
this.props.model.save(todoToSave, text, cat);
this.setState({editing: null});
},
cancel: function () {
this.setState({editing: null});
},
clearCompleted: function () {
this.props.model.clearCompleted();
},
render: function () {
var footer;
var main;
var todos = this.props.model.todos;
var shownTodos = todos.filter(function (todo) {
switch (this.state.nowShowing) {
case app.ACTIVE_TODOS:
return !todo.completed;
case app.COMPLETED_TODOS:
return todo.completed;
default:
return true;
}
}, this);
var todoItems = shownTodos.map(function (todo) {
return (
<TodoItem
key={todo.id}
todo={todo}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
onEdit={this.edit.bind(this, todo)}
editing={this.state.editing === todo.id}
onSave={this.save.bind(this, todo)}
onCancel={this.cancel}
/>
);
}, this);
var activeTodoCount = todos.reduce(function (accum, todo) {
return todo.completed ? accum : accum + 1;
}, 0);
var completedCount = todos.length - activeTodoCount;
if (activeTodoCount || completedCount) {
footer =
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
nowShowing={this.state.nowShowing}
onClearCompleted={this.clearCompleted}
/>;
}
if (todos.length) {
main = (
<section className="main">
<input
className="toggle-all"
type="checkbox"
onChange={this.toggleAll}
checked={activeTodoCount === 0}
/>
<ul className="todo-list">
{todoItems}
</ul>
</section>
);
}
return (
<div>
<header className="header">
<h1>todos</h1>
<form onKeyDown={this.handleNewTodoKeyDown}>
<input
placeholder="What needs to be done?"
value={this.state.newTodo}
autoFocus={true}
className="new-todo"
onChange={this.handleChange}
/>
<select value={this.state.newCategory} className="new-todo"
onChange={this.handleCategoryChange}>
<option value="">Select a Category</option>
<option value="Urgent">Urgent</option>
<option value="Soon">Soon</option>
<option value="Anytime">Anytime</option>
</select>
</form>
</header>
{main}
{footer}
</div>
);
}
});
var model = new app.TodoModel('react-todos');
function render() {
React.render(
<TodoApp model={model}/>,
document.getElementsByClassName('todoapp')[0]
);
}
model.subscribe(render);
render();
})();
todoModel.js
var app = app || {};
(function () {
'use strict';
var Utils = app.Utils;
// Generic "model" object. You can use whatever
// framework you want. For this application it
// may not even be worth separating this logic
// out, but we do this to demonstrate one way to
// separate out parts of your application.
app.TodoModel = function (key) {
this.key = key;
this.todos = Utils.store(key);
this.onChanges = [];
};
app.TodoModel.prototype.subscribe = function (onChange) {
this.onChanges.push(onChange);
};
app.TodoModel.prototype.inform = function () {
Utils.store(this.key, this.todos);
this.onChanges.forEach(function (cb) { cb(); });
};
app.TodoModel.prototype.addTodo = function (title, category) {
this.todos = this.todos.concat({
id: Utils.uuid(),
title: title,
category: category,
completed: false
});
this.inform();
};
app.TodoModel.prototype.toggleAll = function (checked) {
// Note: it's usually better to use immutable data structures since they're
// easier to reason about and React works very well with them. That's why
// we use map() and filter() everywhere instead of mutating the array or
// todo items themselves.
this.todos = this.todos.map(function (todo) {
return Utils.extend({}, todo, {completed: checked});
});
this.inform();
};
app.TodoModel.prototype.filterAll = function () {
this.todos = this.todos.map(function (todo) {
return Utils.extend({}, todo);
});
this.inform();
};
app.TodoModel.prototype.toggle = function (todoToToggle) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToToggle ?
todo :
Utils.extend({}, todo, {completed: !todo.completed});
});
this.inform();
};
app.TodoModel.prototype.destroy = function (todo) {
this.todos = this.todos.filter(function (candidate) {
return candidate !== todo;
});
this.inform();
};
app.TodoModel.prototype.save = function (todoToSave, text, cat) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text}, {category: cat});
});
this.inform();
};
app.TodoModel.prototype.clearCompleted = function () {
this.todos = this.todos.filter(function (todo) {
return !todo.completed;
});
this.inform();
};
})();
todoItem.jsx
var app = app || {};
(function () {
'use strict';
var ESCAPE_KEY = 27;
var ENTER_KEY = 13;
app.TodoItem = React.createClass({
handleSubmit: function (event) {
var val = this.state.editText.trim();
var cat = this.state.editCategoryText.trim();
if (val || cat) {
this.props.onSave(val, cat);
this.setState({editText: this.props.todo.title, editCategoryText: this.props.todo.category});
} else {
this.props.onDestroy();
}
},
handleEdit: function (event) {
this.props.onEdit();
this.setState({editText: this.props.todo.title, editCategoryText: this.props.todo.category});
},
handleKeyDown: function (event) {
if (event.which === ESCAPE_KEY) {
this.setState({editText: this.props.todo.title});
this.props.onCancel(event);
} else if (event.which === ENTER_KEY) {
this.handleSubmit(event);
}
},
handleChange: function (event) {
if (this.props.editing) {
this.setState({editText: event.target.value});
}
},
handleCategoryChange: function (event) {
if (this.props.editing) {
this.setState({editCategoryText: event.target.value});
}
},
getInitialState: function () {
return {editText: this.props.todo.title, editCategoryText: this.props.todo.category};
},
/**
* This is a completely optional performance enhancement that you can
* implement on any React component. If you were to delete this method
* the app would still work correctly (and still be very performant!), we
* just use it as an example of how little code it takes to get an order
* of magnitude performance improvement.
*/
shouldComponentUpdate: function (nextProps, nextState) {
return (
nextProps.todo !== this.props.todo ||
nextProps.editing !== this.props.editing ||
nextState.editText !== this.state.editText ||
nextState.editCategoryText !== this.state.editCategoryText
);
},
/**
* Safely manipulate the DOM after updating the state when invoking
* `this.props.onEdit()` in the `handleEdit` method above.
* For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate
* and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
*/
componentDidUpdate: function (prevProps) {
if (!prevProps.editing && this.props.editing) {
var node = React.findDOMNode(this.refs.editField);
node.focus();
node.setSelectionRange(node.value.length, node.value.length);
}
},
render: function () {
return (
<li className={classNames({
completed: this.props.todo.completed,
editing: this.props.editing
})}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={this.props.todo.completed}
onChange={this.props.onToggle}
/>
<label onDoubleClick={this.handleEdit}>
{this.props.todo.title}
</label>
<label onDoubleClick={this.handleEdit}>
{this.props.todo.category}
</label>
<button className="destroy" onClick={this.props.onDestroy} />
</div>
<input
ref="editField"
value={this.state.editText}
className="edit"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
/>
<select value={this.state.EditCategoryText} className="edit" onChange={this.handleCategoryChange} defaultValue={this.props.todo.category} onKeyDown={this.handleKeyDown}>
<option value="Urgent">Urgent</option>
<option value="Soon">Soon</option>
<option value="Anytime">Anytime</option>
</select>
</li>
);
}
});
})();
Thank You for taking the time to help me figure out how to filter my search based on category selection.
Your interface is a little confusing as you seem to use the same input select for both assigning categories to todos and filtering, I'll get to that at the end of the answer, but for now, I just used the category selector for both entering data and filtering by category.
The answer to your question is extremely simple. You just filter by the category as well as by the completed state. Like this:
var shownTodos = todos.filter(function(todo) {
return(todo.category === this.state.newCategory);
}, this).filter(function (todo) {
switch (this.state.nowShowing) {
case app.ACTIVE_TODOS:
return !todo.completed;
case app.COMPLETED_TODOS:
return todo.completed;
default:
return true;
}
}, this);
I would add some more buttons along the bottom for the categorical currently on display. You would also add a new set of statuses like nowShowing for the category like nowShowingCategory. The buttons would set this to the 3 values of category, and you would use that variable in the above filter instead of newCategory from my example.
I'm building a sort of clock with React that has an option to increment or decrement a number (25 as default) in one component, and in another component it updates the timer (25:00 since we start at 25) to whatever the number is incremented or decremented to.
I have two components (Session and Clock) successfully performing their own actions, however I'm stumped as to how I can get the counter (Session component) to update the state of the timer in the Clock component. More specifically, I've been toying with this.props.minutes to no avail.
Question: How can I go about sharing the this.state.minutes property among components? Thank you in advance. I'm still a total beginner at React.
Session:
const Session = React.createClass({
getInitialState: function() {
return {
minutes: 25,
seconds: 0
};
},
increment: function() {
this.setState({ minutes: this.state.minutes + 1 });
},
decrement: function() {
this.setState({ minutes: this.state.minutes - 1 });
},
timeToString: function(time) {
return time + ':00';
},
render: function() {
return (
<section>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
{this.state.minutes}
<Clock/>
</section>
);
}
});
module.exports = Session;
Clock:
const Clock = React.createClass({
getInitialState: function() {
return { currentCount: 10 };
},
startTimer: function() {
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},
pauseTimer: function() {
clearInterval(this.state.intervalId);
this.setState({ intervalId: this.state.currentCount });
},
timer: function() {
var newCount = this.state.currentCount - 1;
if (newCount >= 0) {
this.setState({ currentCount: newCount });
} else {
clearInterval(this.state.intervalId);
}
},
render: function() {
return (
<section>
<button onClick={this.startTimer}>Start</button>
<button onClick={this.pauseTimer}>Pause</button>
{this.state.currentCount}
</section>
);
}
});
module.exports = Clock;
You need to pass in the state from Session to Clock like so:
<Clock time={this.state.minutes} /> in your Session component
Then the 'state' is now available to your Clock component as this.props.time
or whatever you call it in the above code.
The moral of the story is that state passed down to from a parent component to a child component is done so using props
Relevant Docs:
https://facebook.github.io/react/docs/multiple-components.html
Edit: another key link in the docs:
https://facebook.github.io/react/tips/communicate-between-components.html
I don't know if I worded this right, so bear with me. Basically, I have a component that is a functioning counter (increments or decrements). The other component is a timer that counts down from (by default) 25 to 0.
Previously, I had the timer just set to the value of 25, but I am trying to have the timer change as the value of the counter changes, and when the use presses the "start" button, the timer will count down from whatever number was set by the counter.
I can get the components working individually, but not together.
I've tried setting this.state.currentCount to the value of this.props.time, and then changing the value of this.state.currentCount, but no luck. Either the timer doesn't budge or it doesn't reflect the value of the counter.
Not sure if I should be using componentWillReceiveProps instead.
Any help would be greatly appreciated. There's a screenshot at the bottom if that helps at all.
Session Component:
const Session = React.createClass({
getInitialState: function() {
return {
minutes: 25,
seconds: 0
};
},
increment: function() {
this.setState({ minutes: this.state.minutes + 1 });
},
decrement: function() {
this.setState({ minutes: this.state.minutes - 1 });
},
timeToString: function(time) {
return time + ':00';
},
render: function() {
return (
<section>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
{this.state.minutes}
<Clock time={this.state.minutes}/>
</section>
);
}
});
module.exports = Session;
Clock Component:
const Clock = React.createClass({
getInitialState: function() {
return { currentCount: this.props.time };
},
startTimer: function() {
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},
pauseTimer: function() {
clearInterval(this.state.intervalId);
this.setState({ intervalId: this.props.time });
},
timer: function() {
var newCount = this.state.currentCount - 1;
if (newCount >= 0) {
this.setState({ currentCount: newCount });
} else {
clearInterval(this.state.intervalId);
}
},
render: function() {
return (
<section>
<button onClick={this.startTimer}>Start</button>
<button onClick={this.pauseTimer}>Pause</button>
{this.props.time}
<br></br>
{this.state.currentCount}
</section>
);
}
});
module.exports = Clock;
getInitialState only runs when the component is first initialized so on next
updates from the parent component it won't run that function. You are correct
in that you want to use one of the lifecycle events and in this case
componentWillReceiveProps sounds like the most appropriate because you can
setState there and you don't need to wait for the component to render (otherwise
you would use componentDidUpdate).
I haven't checked this code but I think it should work with this addition:
const Clock = React.createClass({
...
componentWillReceiveProps: function(nextProps) {
// Perhaps pause timer here as well?
this.setState({
currentCount: nextProps.time
})
},
...
});
because your timer depends on Start button. it would be good if you set state of currentCount in startTimer method.
startTimer: function() {
if(this.state.intervalId)
clearInterval(this.state.intervalId); //clear the running interval
this.setState({ currentCount: this.props.time }); // reset currentcount
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},