React. Managing state with onClick function using index - javascript

Okay, So I am still fairly new to coding. I am trying to manage the state of my calculator using an onClick function. I have a list of ingredients in my database. I have it set up so I can pull all of them from the db and its listed on my component. I am trying to set it so when i click on the individual name it will change the properties listed below. I feel like im on the right track but cant seem to figure it out.
import React, { Component } from 'react';
import $ from 'jquery';
import '../index.css';
class StepFive extends Component{
constructor(props) {
super(props)
this.state = {
ingredients: false,
counter: 0
}
}
componentDidMount() {
var self = this;
$.ajax({
method: 'GET',
url: 'http://localhost:3002/api/ingredients',
})
.done(function(ingredients){
self.setState({ingredients: ingredients})
})
}
handleChange(e){
this.setState({counter: e.target.value})
}
listAll(){
return (
<div>
<div className="vertical-menu">
{this.state.ingredients.map(function(ingredients, i){
console.log(i)
return <div>
<span><a key={i} href="#" onClick={this.handleChange(i)}>{ingredients.name}</a> <a href='#'>+</a></span>
</div>
})}
</div>
</div>)
}
render() {
console.log(this.state, 'in render')
if(this.state.ingredients === false){
return(<div><span>loading</span></div>)
} else {
return(
<div className='stepFiveBox'>
{this.listAll()}
<ul>
<li>Hardness=
{this.state.ingredients[this.state.counter].hardness}</li>
<li>Cleansing={this.state.ingredients[this.state.counter].cleansing}</li>
<li>Condidtion={this.state.ingredients[this.state.counter].condition1}</li>
<li>Bubbly={this.state.ingredients[this.state.counter].bubbly}</li>
<li>Creamy={this.state.ingredients[this.state.counter].creamy}</li>
<li>Iodine={this.state.ingredients[this.state.counter].iodine}</li>
<li>INS={this.state.ingredients[this.state.counter].ins}</li>
<li>Lauric={this.state.ingredients[this.state.counter].lauric}</li>
<li>Myristic={this.state.ingredients[this.state.counter].myristic}</li>
<li>Palmitic={this.state.ingredients[this.state.counter].palmitic}</li>
<li>Stearic={this.state.ingredients[this.state.counter].stearic}</li>
<li>Ricinoleic={this.state.ingredients[this.state.counter].rincinoleic}</li>
<li>Oleic={this.state.ingredients[this.state.counter].oleic}</li>
<li>Linoleic={this.state.ingredients[this.state.counter].linoleic}</li>
<li>Linolenic={this.state.ingredients[this.state.counter].linolenic}</li>
<li>NaOH SAP={this.state.ingredients[this.state.counter].sap}</li>
</ul>
</div>
)
}
console.log(this.listAll(),)}
}
export default StepFive

A problem is in your onClick handler. You're doing
<span onClick={this.handleChange(i)}></span>
Which is invoking handleChange at render time.
In order to invoke the function on click, you just pass the function without calling it.
<span onClick={this.handleChange(i)}></span>
In order to pass specific arguments to the function, you have two options:
<span onClick={() => this.handleChange(i)}></span>
or:
<span onClick={this.handleChange.bind(this, i)}></span>
Here's an example using the .bind syntax:
https://codesandbox.io/s/Z461QVox8

can you try to replace this one :
handleChange(e){
this.setState({counter: e.target.value})
}
to this one :
handleChange = (e) => {
this.setState({counter: e.target.value})
}
and replace this one too :
<a key={i} href="#" onClick={this.handleChange(i)}>
to this one :
<a key={i} href="#" onClick={() => this.handleChange(i)}>
Hope can help you :)

it was this issues.
in the listAll function i added a
var self = this;
then switched all this to self. was getting error value undefined... so i realized i didnt need e.target.value i jsut needed e.
changed handelChange function to
handleChange = (e) => {
this.setState({counter: e});
}
badabingbadaboom

Related

Why am I getting an error here? React Todo list

I am trying to create a todo list using React but i cant seem to understand why I am getting the error: "Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state."
Here's the code:
import React from 'react'
import ReactDOM from 'react-dom'
class Todo extends React.Component{
constructor(props){
super(props)
this.state = {
input: '',
list: []
}
this.handleChange = this.handleChange.bind(this)
this.reset = this.reset.bind(this)
this.removeItem = this.removeItem.bind(this)
this.add = this.add.bind(this)
}
add(){ //Adds a new task
const newItem = {
value: this.state.input,
id: Math.random + Math.random
};
const listed = [...this.state.list]
listed.push(newItem)
this.setState({
input: '',
list: listed
})
}
removeItem(id){ //deletes a task
const list = [...this.state.list]
const updatedList = list.filter(obj => {
return obj.id !== id
})
this.setState({
list: updatedList
})
}
handleChange(e){
this.setState({input: e.target.value})
}
reset(e){
e.preventDefault()
}
render(){
return (
<div>
<form action="" onSubmit={this.reset}>
<input type="text" value={this.state.input} placeholder='Enter a task..' onChange={this.handleChange} />
<button onClick={this.add}>Add Task</button>
{this.state.list.map(item => { //updates when a task is added or removed
return (
<div key={item.id}>
<h1>{item.value}</h1>
<button onClick={this.removeItem(item.id)}>X</button>
</div>
)
})}
</form>
</div>
)
}
}
ReactDOM.render(<Todo />,document.getElementById('root'))
Because you are calling removeItem on render. It needs to be wrapped in a separate function:
<button onClick={() => this.removeItem(item.id)}>X</button>
So that you only call it onClick and not on render.
<button onClick={this.removeItem(item.id)}>X</button>
In this button the event handler you have provided runs immediately due to the presents of the () at the end. To prevent this and still provide your argument item.id you can enclose the handler this.removeItem(item.id) with in another function.
I like the arrow function for this so mine looks like this
<button onClick={ ()=>this.removeItem(item.id) }>X</button>.
Math.random + Math.random is not returning a number like you would want for the element key. This is because your have neglected to include () at telling JS to run the function and return an int.
After making these changes, I ran it in codepen.io and it seemed to work fine.

Is there a way I can use button in react to delete a item in an array that is stored in the state

I am trying to build a ToDoList app and I have two components. I have a main component that handles the state and another button component that renders a delete button next to every task that I render. The problem I have is that i cant seem to connect the delete button to the index of the array and delete that specific item in the array by clicking on the button next to it.
I have tried to connect the index by using the map key id to the delete function.
just need help with how my delete function should look like and how its going to get the index of the item that is next to it and delete it.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
toDoList : []
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.delete = this.delete.bind(this);
}
handleSubmit() {
const itemsArray = this.state.userInput.split(',');
this.setState({
toDoList: this.state.toDoList.concat(itemsArray),
userInput: ''
});
}
handleChange(e) {
this.setState({
userInput: e.target.value
});
}
delete(id) {
this.setState({
toDoList: this.state.toDoList.filter( (item) => id !== item.id )
})
}
render() {
return (
<div>
<textarea
onChange={this.handleChange}
value={this.state.userInput}
placeholder="Separate Items With Commas" /><br />
<button onClick={this.handleSubmit}>Create List</button>
<h1>My Daily To Do List:</h1>
<Button toDoList={this.state.toDoList} handleDelete={this.delete} />
</div>
);
}
};
class Button extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item.id}>{item.text} <button onClick={this.props.delete(item.id)}>Done!</button></li> )
}
</ul>
);
}
};
I reviewed your edited code and made a couple of changes.
I don’t get what exactly you want to achieve with you handleSubmit method but items it adds to the list are simple strings and don’t have neither ‘id’ nor ‘text’ properties you’re referring to in other places. Possibly you’re going to change this later but while your to do items are just strings I’ve edited your code so that it work properly under this condition.
Edited delete method now accepts not item.id as a parameter but the whole item object. Yet I'm using functional form of setState as it was correctly suggested by #Hamoghamdi
delete(itemToDelete) {
this.setState(state => ({
toDoList: state.toDoList.filter( (item) => itemToDelete !== item)
}))
}
Edited render method of Button class now displays items as text and properly bind delete handler...
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item}>
{item}
<button onClick={() => this.props.handleDelete(item)}>Done!</button>
</li> )
}
</ul>
);
}
BTW Button is a bad naming for the component that isn’t exactly a button. Yet it’s better to implement it as a functional component. Use class components only if the component has its own state.
you should try using an anonymous function with setState() instead of returning an object literal directly, specially when you want to do something affected by the previous or current state
using this.state inside of setState() won't give you any good results.
here, try this:
delete = (id) => {
this.setState((prevState) => {
return { toDoList: prevState.filter( (task) => id !== task.id )}
});
You need to bind the method in constructor for example:
constructor(props) {
//...
this.handleDelete = this.handleDelete.bind(this)
}
also you can find another ways how to bind methods
In terms of handling the deleting the items, you can use
handleDelete(index) {
// Use the splice array function: splice(index, deleteCount)
this.todoList.splice(index, 1);
}
And that is all that easy

"Cannot read property 'setState' of undefined" even though it is bound

I'm trying to just test that a function is indeed invoked when a click action occurs on a link in my component. I keep receiving the error Cannot read property 'setState' of undefined. But this function from another file is bound to the constructor in the component.
The component
import { toggleEditMode } from './otherFile.js'
class PersonalInformation extends Component {
constructor(props) {
super(props);
this.state = {editMode: false}
this.toggleEditMode = toggleEditMode.bind(this);
}
render(){
const { editMode } = this.state;
return(
<div>
{!editMode &&
<div className="col-md-4 hidden-sm-down">
<a
id="editingToggleButton"
className="display-block"
role="button"
href="javascript:void(0);"
onClick={() => this.toggleEditMode()}
>
<span className="icon icon-sm dls-icon-edit" />
<span className="pad-1-l">Edit</span>
</a>
</div>
}
</div>
)
}
}
The toggleEdit method
export function toggleEditMode() {
this.setState({ editMode: !this.state.editMode })
}
The test
describe('edit', () => {
it('should switch to editMode with click', () => {
const toggleEditMode = jest.fn();
const wrapper = mount(
<PersonalInformation
toggleEditMode={toggleEditMode}
/>
);
wrapper.find('#editingToggleButton').simulate('click');
expect(toggleEditMode).toHaveBeenCalled();
});
}
When it renders the error it specifically points to this.setState within the toggleEdit function.
Here is the problem
You bind the function in the constructor, but you use it as an arrow function.
What you should do is add the bind in the arrow function or only pass the function reference.
this.toggleEditMode = toggleEditMode.bind(this);
// and
onClick={this.toggleEditMode}
OR
// remove .bind from the constructor
this.toggleEditMode = toggleEditMode
// and
onClick={() => this.toggleEditMode.bind(this)}
But I think you should use the first case, because the second case will recreate the arrow function on every render.
You can simply use reactjs hooks to change the state
here is the demo.
function PersonalInformation() {
const [editMode, changeEditMode] = useState(false);
function toggleEditMode() {
changeEditMode(!editMode);
}
return (
<div>
<span className="pad-1-l" onClick={toggleEditMode}>
{!editMode
? `Not Editable (click to change)`
: `editable(click to change)`}
</span>
</div>
);
}
https://codesandbox.io/embed/determined-benz-c5km7

ReactJS remove dynamic element

i want to remove dynamic element in my program, but i think, i have problem with 'this'.When i click in 'X', nothing happens, console doesn't show any error. Maybe someone more experienced will help me.
('items' is array in state)
Main file:
removeItemCity(i){
let arr = this.state.items;
arr.splice(i, 1);
this.setState({items:arr})
}
renderItems(item,i){
return(<Tiles key = {'key_' + i} index = {i} delete = {() =>
{this.removeItemCity}}/>);
}
render() {
return(
<div className = "BodyAppContainer">
<div className = "grid" id="items">
{this.state.items.map(this.renderItems) }
</div>
</div>
);
}
And my component "Tiles"
import React from 'react';
class Tiles extends React.Component {
constructor(props) {
super(props);
}
remove(){
this.props.delete(this.props.index);
}
render() {
return (
<div className = "col-4_sm-6_xs-12 item">
<h2>City : {this.props.index}</h2>
<button className="removeButton" onClick={() => this.remove} >X</button>
</div>
);
}
}
export default Tiles;
Your onClick prop for the X button is not doing anything:
onClick={() => this.remove}
When you click, it calls that arrow function. But that arrow function only has this.remove, which is the definition to a method. The first step in helping you out is you should call that method using parentheses:
onClick={() => this.remove()}
The same thing applies to your renderItems(), where you are also missing parentheses to enact a function call in the delete prop passed to Tiles:
delete={() => {this.removeItemCity}}
Try this:
<button className="removeButton" onClick={this.remove} >X</button>

Remove child component on click of link

I am teaching myself react at the moment, I have a component that looks at the state and when a new item is added it appends a child component to itself. What I am now trying to do is remove the added child component via a click. However I cannot seem to get the natural event of a link to stop, if I do e.preventDefault() I get preventDefault is not a function of undefined.
Below is my code,
import React, { Component } from 'react';
import InvoiceForm from './InvoiceForm';
import InvoiceItemForm from './InvoiceItemForm';
class GenerateInvoice extends Component {
constructor(props) {
super(props);
this.state = {
invoice: {
items : []
}
};
this.onAddChild = this.onAddChild.bind(this);
this.removeItem = this.removeItem.bind(this);
}
render() {
const children = [];
for (var i = 0; i < this.state.invoice.items.length; i += 1) {
children.push(
<InvoiceItemForm
key={i}
number={i}
remove={this.removeItem} />
);
}
return(
<div>
<a href="" onClick={this.onAddChild}>Add New Item</a>
{children}
</div>
)
}
removeItem = (e, itemIndex) => {
e.stopPropagation();
alert("..removing...");
// let invoice = this.state.invoice;
// let updatedItems = this.state.invoice.items.splice(index, 1); //remove element
// let updateInvoice = { ...invoice, items:updatedItems}
// this.setState({ invoice }); //update state
}
onAddChild = (e) => {
e.preventDefault();
let invoice = this.state.invoice;
// creates an updated version of the items without changing the original value
let updatedItems = invoice.items.push({ 'id': 'INV001' });
// creates a new version of the invoice with the updated items
let updateInvoice = { ...invoice, items: updatedItems };
// update the invoice on the state to the new version
this.setState({ invoice });
}
}
export default GenerateInvoice;
child component
import React from 'react';
const InvoiceItemForm = (props) => {
console.log(props);
return(
<div>
<p>Hello {props.number}</p>
<a href="" onClick={props.remove(props.number)}>Remove</a>
</div>
)
}
export default InvoiceItemForm;
and a link to my sandbox,
https://codesandbox.io/s/0qx9w1qrwv
On the InvoiceItemForm component, onClick={props.remove(props.number)}, only here you have the reference to the event object.
You can change to to something like:
onClick={(e) => {
e.preventDefault();
props.remove(props.number);
}}
EDIT:
If you'd like to avoid creating a function each render, you can use something like:
class InvoiceItemForm extends React.Component {
handleClick = (e) => {
e.preventDefault();
this.props.remove(props.number);
}
render() {
console.log(this.props);
return(
<div>
<p>Hello {this.props.number}</p>
<a href="" onClick={this.handleClick}>Remove</a>
</div>
)
}
}
You should bind the index of the item to remove directly to the removeItem method.
Refactoring your render method:
render() {
return(
<div>
<a href="" onClick={this.onAddChild}>Add New Item</a>
{this.state.invoice.items.map((item, index) => {
return (
<InvoiceItemForm
key={index}
number={index}
remove={this.removeItem.bind(null, index)}
/>
);
})}
</div>
);
}
This will bind the index as the first argument to the removeItem function being passed in the props, while leaving the object binding untouched (so the context for the method remains the GenerateInvoice component. When the event is passed by the event handler, it will show up as the second argument.
So the handler definition should be:
removeItem(index, e) {
e.preventDefault();
...your removal code here...
}
And finally the super simple event handling in the child component:
<a href="" onClick={props.remove}>Remove</a>
Although I would use a <button> element instead to remove the whole default event handling & propagation altogether. <a> should be used exclusively for navigating content.

Categories