clearTimeout() is affecting something completely separate in React - javascript

Upon clicking the submit button many things happen including cancelling the countdown clock. I am using setTimeout() and it actually works just fine, but for some reason I can no longer delete items. When I remove the setTimeout() deleting the items works just fine. I really have no idea what's going on as they are completely separate things and use completely separate variables.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { Image } from './Image.js'
import { Button } from './Button.js'
import { images } from './assets/images.js'
import { Countdown } from './Countdown.js'
import { DisplayCount } from './DisplayCount.js'
import { Inputs } from './Inputs.js'
class Game extends React.Component{
constructor(props){
super(props)
this.timer = null
this.thoughts = []
this.thought = []
this.state = {
currentImg: 0,
timer: null,
ranNum: null,
thought: [],
isSubmitted: false
}
this.handleClick = this.handleClick.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleDeleteClick = this.handleDeleteClick.bind(this). < -------------
}
countdownClock = async (newRanNum) => {
const startingNum = newRanNum * 20;
for(let i = startingNum; i >= 0; i--) {
await new Promise(resolve => {
this.timer = setTimeout(() => {
this.setState({
timer: i
})
resolve()
}, 1000)
});
}
}
handleChange(event, index) {
const inputs = [...this.state.thought];
inputs[index] = event.target.value
this.setState({
thought: inputs
});
}
handleClick(){
clearTimeout(this.timer)
let newRanNum = Math.floor(Math.random() * 20);
this.countdownClock(newRanNum)
this.generateStateInputs(newRanNum)
// this.generateInputs(this.state.thought)
let current = this.state.currentImg;
let next = ++current % images.length;
this.setState({
currentImg: next,
ranNum: newRanNum
})
}
generateStateInputs(newRanNum){
let inputArray = []
for(let i = 0; i < newRanNum; i++){
inputArray.push('')
}
return this.setState({
thought: inputArray
});
}
handleSubmit(event) {
if(event){
clearTimeout(this.timer) <------------------------------------------
event.preventDefault();
let thought = this.state.thought.map(word => word + ' ');
console.log(thought)
this.thoughts.push(thought)
event.preventDefault()
this.setState({
thought: []
})
}
}
handleDeleteClick(index){ <-------------------------------------------------------
return this.thoughts = this.thoughts.filter(thought => thought !== this.thoughts[index])
}
render(){
let src = this.state.currentImg;
return(
<div>
<Countdown name={'Countdown: '} countdown={this.state.timer} />
<DisplayCount name='Word Count: ' count={this.state.ranNum} />
<Image src={images[src]} />
<Button onClick={this.handleClick} name='Generate Inputs' />
<form onSubmit={this.handleSubmit}>
<ol>
{this.state.thought.map((input, index) => (
<Inputs type='text' key={index} value={input} onChange={event => { this.handleChange(event, index) }} className='textInputs' />
))}
</ol>
<input type='submit' value='Submit' />
</form>
<div>
<ol>
{this.thoughts.map((thought, index )=>
<DisplayPoem key={index} onClick={() => { this.handleDeleteClick(index) }} name='Delete Thoughts' value={thought} /> <---------------------------------------------
)}
</ol>
</div>
</div>
)
}
}
class DisplayPoem extends React.Component {
render(){
return (
<li>
<span>{this.props.value}</span>
<button onClick={this.props.onClick}>{this.props.name}</button>
</li>
)
}
}
ReactDOM.render(
<Game />,
document.getElementById('root')
);

Related

Generating inputs in a React component

I am trying to generate inputs on a button click and the amount of inputs is generated by a random number. Here is what I have so far, but it isn't working. I am very confused and feel like it should be working. I am not sure what I am missing. Any help would be very much appreciated.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { Image } from './Image.js'
import { Button } from './Button.js'
import { images } from './assets/images.js'
import { Countdown } from './Countdown.js'
import { DisplayCount } from './DisplayCount.js'
import { Inputs } from './Inputs.js'
class Game extends React.Component{
constructor(props){
super(props)
this.timer = null
this.state = {
currentImg: 0,
timer: null,
ranNum: null
}
this.handleClick = this.handleClick.bind(this)
}
countdownClock = async (newRanNum) => {
const startingNum = newRanNum * 20;
for(let i = startingNum; i >= 0; i--) {
await new Promise(resolve => {
this.timer = setTimeout(() => {
this.setState({
timer: i
})
resolve()
}, 1000)
});
}
}
generateInputs = (newRanNum) => {
const inputs = []
for(let i = 1; i <= newRanNum; i++){
inputs.push(
<Inputs type='text' className='textInputs' />
)
}
return inputs;
}
handleClick(){
clearTimeout(this.timer)
let newRanNum = Math.floor(Math.random() * 20);
this.countdownClock(newRanNum)
this.generateInputs(newRanNum)
let current = this.state.currentImg;
let next = ++current % images.length;
this.setState({
currentImg: next,
ranNum: newRanNum
})
}
render(){
let src = this.state.currentImg;
return(
<div>
<Countdown name={'Countdown: '} countdown={this.state.timer} />
<DisplayCount name='Word Count: ' count={this.state.ranNum} />
<Image src={images[src]} />
<Button onClick={this.handleClick} />
<div>
<ul>
{this.generateInputs()}
</ul>
</div>
</div>
)
}
}
ReactDOM.render(
<Game />,
document.getElementById('root')
);
Inputs component:
import React from 'react'
export const Inputs = (props) => {
return (
<li className={props.className}>
<input value={props.value} />
</li>
)
}
I believe the issue is here...
generateInputs = (newRanNum) ...
and here...
{this.generateInputs()}
You're input rendering function is expecting a parameter it's not getting, and since that returns as undefined the loop never runs. :)
In general, it is most common to separate state (eg, number of inputs) from the rendering, and only have the rendering respond to state. Perhaps you had originally intended generateInputs() to produce the array, and not render them (?)
The first thing is your generateInputs() function takes a parameter for number of inputs you wanted to render but you are not passing any parameter to this.generateInputs()
The second thing is you are returning an array from this function but not mapping your array in the render function, So do in this way.
this.generateInputs(10) // pass parameter here
this.generateInputs(10).map((item, index)=>item)
In this Snack, I have done the same thing but with react-native
https://snack.expo.io/#waheed25/smiling-carrot

React doesn't render form autofill suggestions

I'm using react to create a panel to add a new product. I've created a separate auto-complete class which is supposed to render an input element and a list of suggested autofill items underneath. The input element shows but not the autofill suggestions. Have a look at the code
Autofill class
import React, { Component } from "react";
import firebase from "../Firebase";
export default class AutoCompleteDistID extends Component {
constructor() {
super();
this.state = {
sellerName: [],
sellerId: [],
suggestions: [],
};
}
componentDidMount() {
var sellerRef = firebase.database().ref().child("Sellers");
sellerRef.once("value", (snapshot) => {
snapshot.forEach((childSnap) => {
var distrName = childSnap.val().sellerName;
var distrId = childSnap.val().sellerName.sellerId;
// var distrName = [{ name: data.sellerName }];
this.setState((prevState) => {
return {
sellerName: [...prevState.sellerName, distrName],
sellerId: [...prevState.sellerId, distrId],
suggestions: [...prevState.suggestions, distrName],
};
});
});
});
}
onTextChange = (e) => {
var sellerNames = [this.state.sellerName];
const value = e.target.value;
let newSuggestions = [];
if (value.length > 0) {
const regex = new RegExp(`^${value}`, "i");
newSuggestions = sellerNames.sort().filter((v) => regex.test(v));
}
this.setState(() => ({ newSuggestions }));
};
renderSuggestions() {
const newSuggestions = this.state.suggestions;
if (newSuggestions.length === 0) {
return null;
}
return (
<ul>
{newSuggestions.map((item) => (
<li>{item}</li>
))}
</ul>
);
}
render() {
return (
<div>
<input onChange={this.onTextChange} />
{this.renderSuggestions}
</div>
);
}
}
Main form
import React, { Component } from "react";
import firebase from "../Firebase";
import AutoCompleteDistID from "./AutoCompleteDistID";
export default class Products extends Component {
constructor() {
super();
this.state = {
description: "",
prodQty: "",
};
this.pushProduct = this.pushProduct.bind(this);
}
handleFormChange = (event) => {
const target = event.target;
const colName = target.name;
this.setState({
[colName]: event.target.value,
});
};
pushProduct() {
const userRef = firebase.database().ref().child("Users"); //Get reference to Users DB
const prodData = this.state;
userRef.push(prodData);
}
render() {
return (
<div>
<br />
<form style={{ border: "solid", borderWidth: "1px", width: "600px" }}>
<br />
<input
type="text"
value={this.state.prodQty}
placeholder="Available Quantity"
onChange={this.handleFormChange}
name="prodQty"
/>
<input
type="text"
value={this.state.description}
placeholder="Description"
onChange={this.handleFormChange}
name="description"
/>
<AutoCompleteDistID />
<br />
<br />
</form>
<button onClick={this.pushProduct} type="button">
Add Product
</button>
<br />
</div>
);
}
}
State variable is suggestions but you are setting newSuggestions.
onTextChange = (e) => {
var sellerNames = [this.state.sellerName];
const value = e.target.value;
let newSuggestions = [];
if (value.length > 0) {
const regex = new RegExp(`^${value}`, "i");
newSuggestions = sellerNames.sort().filter((v) => regex.test(v));
}
// HERE IS THE MISTAKE
this.setState(() => ({ suggestions: newSuggestions }));
};
In AutoCompleteDistID render method
render() {
return (
<div>
<input onChange={this.onTextChange} />
{this.renderSuggestions()}
</div>
);
}

Changing state of child component from parent with refs

I have a TaskList. I'm trying to display a child component TaskEdit (edit box for the task) several components lower, onClick of a button.
TaskEdit has a state variable hidden, set to true by default. How do I change the state inside the child component from within the child component?
React documentation mentions a forwardRef() function in order to reference the child component, but I haven't any luck so far with it.
Here's my code:
import TaskEdit from '../TaskEdit';
class TasksBase extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
tasks: [],
};
this.markTaskAsCompleted = this.markTaskAsCompleted.bind(this);
this.deleteTask = this.deleteTask.bind(this);
this.incrementDate = this.incrementDate.bind(this);
this.toggleTaskEdit = this.toggleTaskEdit.bind(this);
}
componentDidMount() {
this.onListenForTasks();
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { tasks, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{tasks ? (
<TaskList tasks={tasks}
handleToggleEdit={this.toggleTaskEdit}
handleMarkCompleted={this.markTaskAsCompleted}
handleDeleteTask={this.deleteTask}
taskEditElement={this.editTaskElement}
/>
):(
<div>There are no tasks ...</div>
)}
</div>
);
}
onListenForTasks() {
this.setState({ loading: true });
this.unsubscribe = this.props.firebase
.tasks()
.orderBy('importance', 'desc')
.orderBy('urgency', 'desc')
.orderBy('created', 'desc')
.onSnapshot(snapshot => {
if (snapshot.size) {
let tasks = [];
snapshot.forEach(doc =>
tasks.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
tasks: tasks,
loading: false
});
} else {
this.setState({ tasks: null, loading: false });
}
});
}
markTaskAsCompleted(task){
// Some code
}
deleteTask(id){
// Some code
}
toggleEditTask(){
this.editTaskElement.current.toggle();
}
}
const Tasks = withFirebase(TasksBase);
const TaskList = ({ tasks, handleToggleEdit, handleMarkCompleted, handleDeleteTask }) => (
<ul className="tasks">
{tasks.map( task => (
<Task key={task.uid}
task={task}
handleToggleEdit={handleToggleEdit}
handleMarkCompleted={handleMarkCompleted}
handleDeleteTask={handleDeleteTask}
/>
))}
</ul>
);
const Task = ({ task, handleToggleEdit, handleMarkCompleted, handleDeleteTask}) => (
(!task.completed && !task.obsolete && !task.waitingForDependencies) && (!task.start || task.start.toMillis() <= Date.now()) &&
<li className="task">
<strong>{task.userId}</strong> {task.name}
{ task.estimate &&
<span>{task.estimate.amount + task.estimate.unit}</span>
}
<div className="icons">
<FontAwesomeIcon icon="edit" onClick={() => handleToggleEdit(task)} />
<FontAwesomeIcon icon="check-circle" onClick={() => handleMarkCompleted(task)} />
<FontAwesomeIcon icon="times-circle" onClick={() => handleDeleteTask(task.uid)}/>
</div>
<TaskEdit task={task} />
</li>
);
const condition = authUser => !!authUser;
export default compose(
withEmailVerification,
withAuthorization(condition),
)(Tasks);
TaskEdit:
import React, { Component } from 'react';
import { withFirebase } from '../Firebase';
const INITIAL_STATE = {
hidden: true,
};
class TaskEditBase extends Component {
constructor(props) {
super(props);
this.state = { ...INITIAL_STATE };
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
console.log("name " + name);
console.log("value " + value);
this.setState({
[name]: value
});
}
toggle(){
this.setState({
hidden: !this.prevState.hidden
})
}
onSumbitTaskEdit = (event) => {
event.preventDefault();
};
render(){
return(
!this.state.hidden &&
<div className="task-edit">
<form onSubmit={this.onSumbitTaskEdit}>
<div>A bunch of inputs</div>
<input type="submit" value="Edit"/>
</form>
</div>
)
}
}
const TaskEdit = withFirebase(TaskEditBase);
export default TaskEdit;

How to delete selected element react

I have two components Display.jsx and DisplayList.jsx. Components work together to display values from local storage. The problem is with DisplayList.JSX handleDelete() method loop.
I don't get it why it deletes first element instead of selected element? And how to fix that?
Github
Display.jsx
import {DisplayList} from './DisplayList';
class Display extends Component {
constructor(props){
let data = JSON.parse(localStorage.getItem('data'));
super(props)
this.state = {
data: data,
}
// Methods
this.displayValues = this.displayValues.bind(this);
}
displayValues(){
return this.state.data.map((data1, index) =>
<DisplayList
key = {index}
email = {data1.email}
password = {data1.password}
updateList = {this.updateList}
/>
)
}
// This is the method that will be called from the child component.
updateList = (data) => {
this.setState({
data
});
}
render() {
return (
<ul className="list-group">
{this.displayValues()}
</ul>
)
}
}
export default Display;
DisplayList.jsx
import React, { Component } from 'react'
import {Button, Modal, Form} from 'react-bootstrap';
export class DisplayList extends Component {
constructor(props){
super(props)
this.state = {
email: '',
password: '',
show: false,
};
// Methods
this.handleDelete = this.handleDelete.bind(this);
this.onChange = this.onChange.bind(this);
}
onChange(event){
this.setState({
[event.target.name]: event.target.value
})
};
handleDelete(){
const data = JSON.parse(localStorage.getItem('data'));
for (let index = 0; index < data.length; index++) {
if(this.props.email === data[index].email &&
this.props.password === data[index].password){
console.log(this.props.email);
console.log(data[index]);
data.splice(data[index], 1);
}
console.log('loop count');
}
localStorage.setItem('data', JSON.stringify(data));
this.props.updateList(data);
}
render() {
return (
<div className = "mt-4">
<li className="list-group-item text-justify">
Email: {this.props.email}
<br />
Password: {this.props.password}
<br />
<Button onClick = {this.handleShow} variant = "info mr-4 mt-1">Edit</Button>
<Button onClick = {this.handleDelete} variant = "danger mt-1">Delete</Button>
</li>
</div>
)
}
}
Instead of:
data.splice(data[index], 1);
Try rewriting entire array:
data = [
...data.slice(0, index),
...data.slice(index + 1)
];
handleDelete(){
const data = JSON.parse(localStorage.getItem('data'));
const filteredData = data.filter(
x => !(x.email === this.props.email && x.password === this.props.password)
);
localStorage.setItem('data', JSON.stringify(filteredData));
this.props.updateList(filteredData);
}

How to render the elements before to filter elements with ReactJS?

I'm doing a project which does a get of the json-server, and render them on the screen.
But when I added a filtering function on it, it only renders after I type a name to filter. I wanted him to render everyone and make the filter.
My Body.js (Where is my function of render):
import React from 'react';
import './Body.css';
import { Link } from "react-router-dom";
class Body extends React.Component {
constructor(props) {
super(props);
this.state = {
input: "",
employeeBody: this.props.employee,
}
}
getName = () => {
const { employee, add } = this.props;
const {employeeBody} = this.state;
return employee.map(name => (
<div className='item'>
<Link className="link" to={`/user/${name.id}`}>
<div onClick={() => add(name)} key={name.id}>
<img className="img"
src={`https://picsum.photos/${name.id}`}
/>
</div>
<h1 className="name2"> {name.name} </h1>
</Link>
</div>
));
};
---
getValueInput = (evt) => {
const inputValue = evt.target.value;
this.setState({ input: inputValue });
this.filterNames(inputValue);
console.log(this.state.employeeBody)
}
filterNames (inputValue) {
const { employee } = this.props;
this.setState({
employeeBody: employee.filter(item =>
item.name.includes(inputValue))
});
}
---
render() {
return (
<div>
<div className="body">
{this.getName()}
</div>
<div className='input'>
<input type="text" onChange={this.getValueInput} />
</div>
</div>
)
}
}
export default Body;
My App.js (Where i get the state by get of axios.):
import React from 'react';
import {
BrowserRouter as Router,
Route
} from "react-router-dom";
import './App.css';
import axios from 'axios';
import Body from './Body';
import User from './User';
import Header from './Header';
class AppRouter extends React.Component {
state = {
employeeCurrent: [],
employee: []
};
componentDidMount() {
axios
.get("http://127.0.0.1:3004/employee")
.then(response => this.setState({
employee: response.data
}));
}
add = name => {
this.setState(prevState => {
const copy = prevState.employeeCurrent.slice(1);
copy.push(name);
return {
employeeCurrent: copy
};
});
};
render() {
return ( <
Router >
<
div className = "router" >
<
Header / >
<
Route exact path = "/"
render = {
props => ( <
Body { ...props
}
add = {
this.add
}
employee = {
this.state.employee
}
employeeCurrent = {
this.state.employeeCurrent
}
/>
)
}
/> <
Route path = "/user/:id"
component = {
props => ( <
User { ...props
}
employee = {
this.state.employee
}
employeeCurrent = {
this.state.employeeCurrent
}
/>
)
}
/> <
/div> <
/Router>
);
}
}
export default AppRouter;
Someone would can help me ?
You should filter in the render method.
render() {
const { employee: employees } = this.props; // rename the variable {employee} to plural {employees}, it has more sense.
const { input } = this.state;
return (
<div>
<div className="body">
{employees
.filter(employee => employee.name.includes(input))
.map(employee => {
<div className='item'>
<Link className="link" to={`/user/${employee.id}`}>
<div onClick={() => add(employee)} key={employee.id}>
<img className="img"
src={`https://picsum.photos/${employee.id}`}
/>
</div>
<h1 className="name2"> {employee.name} </h1>
</Link>
</div>
})}
</div>
<div className='input'>
<input type="text" onChange={(e) => this.setState({ input: e.target.value })} />
</div>
</div>
);
}
Remember that the method includes is case sensitive, it should be lowerCase it before to compare.
P.S.: You could also create a variable / component / function and render split all the "logic" of rendering there.

Categories