I've used Countdown on my project and it works like a charm. but when I combined it with simple input, it would be restart whenever my input changes. I want to restart it only and only if time over. Here is my code:
import React from 'react';
import { Input } from 'reactstrap';
import Countdown from 'react-countdown-now';
import { connect } from 'react-redux';
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
code: '',
disabled: false,
};
this.update = this.update.bind(this);
}
update(e) {
this.setState({ code: e.target.value });
}
render() {
return (
<div>
<Input
className="text-center activationCode__letter-spacing"
maxLength="4"
type="text"
pattern="\d*"
id="activationCode__input-fourth"
bsSize="lg"
value={this.state.code}
onChange={this.update}
/>{' '}
<Countdown date={Date.now() + 120000} renderer={renderer} />
</div>
);
}
}
const renderer = ({ minutes, seconds, completed }) => {
if (completed) {
return <p>reset</p>;
}
return (
<p>
{minutes}:{seconds}
</p>
);
};
const mapStateToProps = state => ({
user: state.auth,
});
export default connect(
mapStateToProps,
null,
)(Foo);
Any suggestion?
The reason why your counter is restarting, is because when you change the input field you're updating your state. Updating the state will cause a rerender which ultimately executes the Date.now() function again.
A solution for your problem would be to move the date for your countdown up in your constructor, so it is only set once, and then in your render function reference it through state.
constructor(props) {
super(props);
this.state = {
code: '',
disabled: false,
date: Date.now() + 120000
};
this.update = this.update.bind(this);
}
...
render() {
return (
<div>
<Input
className="text-center activationCode__letter-spacing"
maxLength="4"
type="text"
pattern="\d*"
id="activationCode__input-fourth"
bsSize="lg"
value={this.state.code}
onChange={this.update}
/>{' '}
<Countdown date={this.state.date} renderer={renderer} />
</div>
);
}
Related
I am building a To-Do List web app with React as my first project.
I want to implement local storage which works fine only that,I am unable to handle check and uncheck of the checkbox prefectly.
Here is a link to the deployed website so you can understand the problem I am having.
https://rapture-todo.netlify.app/
When you add a todo, and mark it complete.
on reload, the checkbox of the todo is unchecked but the todo is marked complete.
Here is my source code[github link- https://github.com/coolpythoncodes/React-ToDo-List].
For App.js
import React, { Component } from 'react';
import Header from './component/Header';
import Info from './component/Info';
import AddToDo from './component/AddToDo';
import TodoListItem from './component/TodoListItem';
import './sass/main.scss';
class App extends Component{
constructor(props){
super(props);
this.state= {
value: '',
list: [],
show: true,
};
this.handleChange= this.handleChange.bind(this);
this.handleSubmit= this.handleSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.deleteTask = this.deleteTask.bind(this);
}
componentDidMount() {
const list = window.localStorage.getItem('userTodo') ? JSON.parse(localStorage.getItem('userTodo')) : [];
this.setState({ list })
}
handleChange(e) {
this.setState({value:e.target.value})
}
// Handle submission of user todo item
handleSubmit(e) {
e.preventDefault();
const newTask = {
id: Date.now(),
userTodo: this.state.value,
isCompleted: false,
checked: false,
}
// Validate form so user doesn't add an empty to do
if (this.state.value.length > 0) {
this.setState({
list: [newTask, ...this.state.list],
value: '', // Clear input field
show: true, // Success message
}, ()=>{
window.localStorage.setItem('userTodo', JSON.stringify(this.state.list));
})
}
}
// Handles checkbox
handleInputChange(id) {
this.setState({list: this.state.list.map(item => {
if (item.id === id) {
item.isCompleted = !item.isCompleted;
item.checked = !this.state.checked;
}return item
})}, ()=>{
window.localStorage.setItem('userTodo', JSON.stringify(this.state.list));
})
}
// Delete a task
deleteTask(id){
this.setState({list: this.state.list.filter(item => item.id !== id )},()=>{
window.localStorage.setItem('userTodo', JSON.stringify(this.state.list))
})
console.log(this.state.list)
}
render(){
return(
<div>
<Header />
<Info />
<AddToDo onChange={this.handleChange} value={this.state.value} onSubmit={this.handleSubmit} />
<TodoListItem deleteTask={this.deleteTask} onChange={this.handleInputChange} list={this.state.list} defaultChecked={this.state.checked} />
</div>
)
}
}
export default App;
For TodoListItem.js
import React, { Component } from 'react';
import ToDoItem from './ToDoItem';
import '../sass/main.scss';
class ToDoListItem extends Component{
render(){
const {list, onChange, deleteTask, defaultChecked} = this.props;
return(
<div>
{list.map((todo)=>{
return (
<ToDoItem
key={todo.id}
userTodo={todo.userTodo}
isCompleted={todo.isCompleted}
onChange={onChange}
id={todo.id}
deleteTask={deleteTask}
defaultChecked={defaultChecked}
/>
)
})}
</div>
)
}
}
export default ToDoListItem;
For TodoItem.js
import React, { Component } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrashAlt } from '#fortawesome/free-solid-svg-icons'
import '../sass/main.scss';
class ToDoItem extends Component{
render(){
const {userTodo, isCompleted, onChange, id, deleteTask, defaultChecked} = this.props;
const checkStyle = isCompleted ? 'completed-todo' : 'not-completed-todo';
return(
<div className={`container ${checkStyle}`}>
<input type="checkbox" onChange={onChange.bind(this, id)} defaultChecked={defaultChecked}/>
<div >
<p className='title'>{userTodo}</p>
</div>
{/* Delete button */}
<button onClick={deleteTask.bind(this, id)}><FontAwesomeIcon className='remove-icon' icon={faTrashAlt} /></button>
</div>
)
}
}
export default ToDoItem;
Please note: I have gone through other questions similar to the problem I am having but I could not solve this problem.
If I did not state the question well, please let me know.
In the below code in App.js,
<TodoListItem deleteTask={this.deleteTask} onChange={this.handleInputChange} list={this.state.list} defaultChecked={this.state.checked} />
You are setting, defaultChecked={this.state.checked} Why do you do that? There is nothing called checked in the state.
In fact, there is no need to pass the defaultValue.
Make the following changes,
In App.js, remove defaultValue prop for TodoListItem
<TodoListItem deleteTask={this.deleteTask} onChange={this.handleInputChange} list={this.state.list}/>
In TodoListItem.js, remove defaultChecked={defaultChecked}
<ToDoItem
key={todo.id}
userTodo={todo.userTodo}
isCompleted={todo.isCompleted}
onChange={onChange}
id={todo.id}
deleteTask={deleteTask}
defaultChecked={defaultChecked} // Remove this.
/>
In ToDoItem.js,
<input type="checkbox"onChange={onChange.bind(this, id)}
defaultChecked={isCompleted} // Replace defaultValue with isCompleted
/>
I'm learning react, and I'm stuck with not updating list components.
The component shows all of the list elements that I add manually, but not rendering any changes.
I searched a lot for solutions.
All of my change handlers are binded, the setState inside handleSubmit should update ClockRow...
My App.js:
import React, { Component } from 'react';
import Clock from './Clock';
import ClockRow from './ClockRow';
class App extends Component {
constructor(props) {
super(props);
this.state = {items: [], tle: 'Teszt', ival: 200};
this.handleChangeTitle = this.handleChangeTitle.bind(this);
this.handleChangeInterval = this.handleChangeInterval.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChangeTitle(event) {
this.setState({tle: event.target.value});
}
handleChangeInterval(event) {
this.setState({ival: event.target.value});
}
handleSubmit(event) {
if(this.state.tle.length > 0 && this.state.ival > 9){
this.setState({items: [...this.state.items, <Clock interval={this.state.ival} title={this.state.tle} />]});
}
event.preventDefault();
}
render(){
return (
<div>
<div className="row">
<h1 className="col text-center">Hello, React!</h1>
</div>
<div className="row">
<form onSubmit={this.handleSubmit}>
<label>
Title: <input type="text" name="tle" value={this.state.tle} onChange={this.handleChangeTitle} />
</label>
<label>
Interval: <input type="number" name="ival" value={this.state.ival} onChange={this.handleChangeInterval} />
</label>
<input type="submit" value="Add" />
</form>
</div>
<ClockRow clockItems={this.state.items} />
</div>
);
}
}
export default App;
My ClockRow.js:
import React, { Component } from 'react';
class ClockRow extends Component{
constructor(props){
super(props);
this.state = {clocks: props.clockItems.map((x, i) => <div className="col" key={i}>{x}</div>) }
}
render(){
return(<div className="row">{this.state.clocks}</div>
)};
}
export default ClockRow;
My Clock.js:
import React, { Component } from 'react';
import {Card, CardTitle, CardBody, CardFooter} from 'reactstrap';
class Clock extends Component {
constructor(props){
super(props);
this.state = {counter: 0, interval: parseInt(props.interval), title: props.title};
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), this.state.interval);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState((state) => ({
counter: state.counter + 1
}));
}
render() {
return (
<Card>
<CardTitle>{this.state.title}</CardTitle>
<CardBody>{this.state.counter}</CardBody>
<CardFooter>{this.state.interval}</CardFooter>
</Card>
);
}
}
export default Clock;
CodeSandbox:
https://codesandbox.io/s/zxlzzv05n3
ClockRow.js is surplus
Clock.js is not changed
App.js is changed, and "React styled":
import React, { Component } from "react";
import Clock from "./Clock";
class App extends Component {
constructor(props) {
super(props);
this.state = { items: [], inputTle: "Teszt", inputIval: 200 };
}
handleChangeTitle = event => {
this.setState({ inputTle: event.target.value });
};
handleChangeInterval = event => {
this.setState({ inputIval: event.target.value });
};
handleSubmit = event => {
console.log(this.state);
if (this.state.inputTle.length > 0 && this.state.inputIval > 9) {
this.setState(prevState => {
return {
items: [
...prevState.items,
{
title: this.state.inputTle,
interval: this.state.inputIval
}
]
};
});
}
event.preventDefault();
};
render() {
return (
<div>
<div className="row">
<h1 className="col text-center">Hello, React!</h1>
</div>
<div className="row">
<form onSubmit={this.handleSubmit}>
<label>
Title:{" "}
<input
type="text"
name="tle"
value={this.state.inputTle}
onChange={this.handleChangeTitle}
/>
</label>
<label>
Interval:{" "}
<input
type="number"
name="ival"
value={this.state.inputIval}
onChange={this.handleChangeInterval}
/>
</label>
<input type="submit" value="Add" />
</form>
</div>
<div className="row">
{this.state.items.map((item, index) => (
<div className="col" key={index}>
<Clock {...item} />
</div>
))}
</div>
</div>
);
}
}
export default App;
Hello guys I started learning react recently and I'm having some issues. I'm trying to make simple react app, one of the components I'm making is stopwatch.
The issue I'm having is that when I start typing in my input for stopwatch the timer resets.
Here is my main component:
import React, { Component } from 'react';
import Clock from '../components/Clock.jsx';
import Stopwatch from '../components/Stopwatch.jsx';
import '../css/App.css';
import { Form, FormControl, Button } from 'react-bootstrap';
class App extends Component {
constructor(props) {
super(props);
this.state = {
deadline: 'December 25, 2018',
newDeadline: '',
timer: 60,
newTimer: '',
};
}
changeDeadline() {
this.setState({
deadline: this.state.newDeadline,
});
}
changeTimer() {
this.setState({
timer: this.state.newTimer,
});
}
render() {
return (
<div className='app'>
<div className='app_title'>
Countdown to {this.state.deadline}
</div>
<Clock
deadline = {this.state.deadline}
/>
<Form inline >
<FormControl
className="deadline_input"
type="text"
placeholder="New date"
onChange={event => this.setState({newDeadline: event.target.value})}
onKeyPress={event => {
if (event.key === 'Enter') {
event.preventDefault();
this.changeDeadline();
}
}}
/>
<Button onClick={() => this.changeDeadline()} >
Submit
</Button>
</Form>
<div className="stopwatch_title">
Stopwatch from {this.state.timer} seconds
</div>
<Form inline>
<FormControl
className="stopwatch_input"
type="text"
placeholder="Enter time"
onChange={event => this.setState({newTimer: event.target.value})}
onKeyPress={event => {
if (event.key === 'Enter') {
event.preventDefault();
this.changeTimer();
}
}}
/>
<Button onClick={() => this.changeTimer()} >
Submit
</Button>
</Form>
<Stopwatch
timer = {this.state.timer}
/>
</div>
);
}
}
export default App;
and my stopwatch component:
import React, {Component} from 'react';
import '../css/App.css';
import { Button } from 'react-bootstrap';
class Stopwatch extends Component {
constructor(props) {
super(props);
this.state = {
stopwatch: props.timer,
};
this.decrementer = null;
}
componentWillReceiveProps(nextProps) {
clearInterval(this.decrementer);
this.timerCountdown(nextProps.timer);
}
timerCountdown(newTimer) {
// First we update our stopwatch with new timer
this.setState({
stopwatch: newTimer
});
}
startTimer() {
// Then we decrement stopwatch by 1 every second
this.decrementer = setInterval( () => {
this.setState({
stopwatch: this.state.stopwatch -1,
});
},1000);
}
componentDidUpdate() {
if (this.state.stopwatch < 1) {
clearInterval(this.decrementer);
alert('Countdown finished');
}
}
render() {
return(
<div>
<Button onClick={() => this.startTimer()} >
Start
</Button>
<div className="stopwatch"> {this.state.stopwatch} </div>
</div>
);
}
}
export default Stopwatch;
Here is gif of the problem https://imgur.com/9xqMW96
As you can see my timer resets after I start typing in input. I would like for it to reset only when user presses enter key or uses submit button.
I've tried doing something like this:
<input value={this.state.newTimer} onChange={evt => this.updateInputValue(evt)}/>
updateInputValue: function(evt) {
this.setState({
newTimer: evt.target.value
});
}
but it didn't work for me.
You can see code in action here: https://karadjordje.github.io/countdown-stopwatch-react/
You are stopping the interval on each new prop that the component receives.
You can either handle the time in a local state or explicitly pass the proper new values from the parent.
I've made a small basic example so you can see the data flow and how each event is responsible for a small portion of the data.
Hope that helps.
class App extends React.Component {
state = {
startTime: 5,
currentTime: 5,
textInput: ''
}
startTimer = () => {
if (this.interval) {
clearInterval(this.interval);
}
this.interval = setInterval(() => {
this.setState(prev => {
if (prev.currentTime === 0) {
this.stopTimer();
return { ...prev, currentTime: prev.startTime };
} else {
return {
...prev,
currentTime: prev.currentTime - 1
}
}
})
}, 1000)
}
stopTimer = () => {
clearInterval(this.interval);
}
updateInput = ({ target }) => {
this.setState(prev => ({ textInput: target.value }));
}
setStartTime = () => {
this.stopTimer();
this.setState(({ textInput }) => ({ startTime: textInput, currentTime: textInput, textInput: '' }));
}
render() {
const { currentTime, textInput } = this.state;
return (
<div >
<div>{currentTime}</div>
<button onClick={this.startTimer}>Start timer</button>
<div>
<input placeholder="Enter start time" value={textInput} onChange={this.updateInput} />
<button onClick={this.setStartTime}>Set Start time</button>
</div>
</div>
);
}
}
ReactDOM.render(<App />, 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>
I have updated my code.
Instead of using componentWillUpdate I'm using componentDidUpdate and this is my code for it:
componentDidUpdate(prevProps) {
console.log('componentDidUpdate', this.props, prevProps);
if (prevProps.timer !== this.props.timer) {
this.updateTimer(this.props.timer);
clearInterval(this.decrementer);
}
if (this.state.stopwatch < 1) {
clearInterval(this.decrementer);
alert('Countdown finished');
}
}
Basically I'm only updating timer is previous timer is different from current.
This doesn't work in react 16, but this jsfiddle works fine: https://jsfiddle.net/kp04015o/9/
Can any one debug this error why? Cannot read property 'value' of null, at handleChange = debounce(e => this.setState({searchTerm: e.target.value}), 1000);
import React from 'react';
import ReactDOM from 'react-dom';
const debounce = (cb, wait) => {
let timeout;
return function() {
let callback = () => cb.apply(this, arguments);
clearTimeout(timeout);
timeout = setTimeout(callback, wait);
}
}
class Debounce extends React.Component {
state = {
searchTerm: '',
};
handleChange = debounce(e => this.setState({searchTerm: e.target.value}), 1000);
render() {
return (
<div>
<input type="text" onChange={this.handleChange}/>
<div>Search Value 2: {this.state.searchTerm}</div>
</div>
);
}
}
ReactDOM.render(<Debounce />, document.getElementById('root'));
Read about Event Pooling
Here you have a working code:
class Debounce extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
state = {
searchTerm: '',
};
setSearchTerm = debounce((searchTerm) => this.setState({ searchTerm }), 1000);
handleChange(e) {
this.setSearchTerm(e.target.value);
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
<div>Search Value 2: {this.state.searchTerm}</div>
</div>
);
}
}
or nicer with ES7 decorators and decko module:
import { debounce, bind } from 'decko';
class Debounce extends React.Component {
state = {
searchTerm: '',
};
#debounce(1000)
setSearchTerm(searchTerm) {
this.setState({ searchTerm });
}
#bind
handleChange(e) {
this.setSearchTerm(e.target.value);
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
<div>Search Value 2: {this.state.searchTerm}</div>
</div>
);
}
}
I feel like I know this.
Try changing this:
<input type="text" onChange={this.handleChange} />
to either:
<input type="text" onChange={this.handleChange.bind(this)} />
or:
<input type="text" onChange={(e) => this.handleChange(e)} />
That is my gut reaction every time I see something of undefined with a React event, and because your default state is handled. If this works, it's because the execution context or lexical environment is in a different dimension than it might appear.
I've made a Twitch API widget which you can see here: https://twitch-react-drhectapus.herokuapp.com/
At the moment, any time you search for something, there will be a list of suggestions. I'd like to make it so that when you click on one of the datalist options it will search for that user, rather than having to click on the 'Search' button. Basically the same search function as google has.
How do I go about implementing this?
Code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser, fetchSuggestions } from '../actions/index';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = {
term: ''
};
this.onInputChange = this.onInputChange.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onInputChange(event) {
this.setState({
term: event.target.value
});
setTimeout( this.props.fetchSuggestions(event.target.value), 300);
}
renderSuggestions(sug, i) {
return (
<option key={i} value={sug.display_name} />
)
}
onFormSubmit(event) {
event.preventDefault();
this.props.fetchUser(this.state.term);
this.setState({
term: ''
});
}
render() {
const { error, suggestions } = this.props;
return (
<form
className='input-group'
onSubmit={this.onFormSubmit}>
<input
className='form-control'
placeholder='Search for a Twitch user'
value={this.state.term}
onChange={this.onInputChange}
list='suggestions' />
<span className='input-group-btn'>
<button className='btn btn-primary'>
Search
</button>
</span>
<datalist id='suggestions'>
{suggestions.map(this.renderSuggestions)}
</datalist>
</form>
// {/* {error && <div className='alert alert-danger'>{error}</div>} */}
)
}
}
function mapStateToProps({ error, suggestions }) {
return { error, suggestions };
}
export default connect(mapStateToProps, { fetchUser, fetchSuggestions })(SearchBar);