get value of list item with click event in React - javascript

I have two components
which displaying
each element of const elementObjects = [{id: 1, Element: "Orange", Weight: 55}, {id:2, Element: "Banana", Mass: 20}];
in an unorderd list
I want to log the value of a list item to the console if clicked
return <li onClick={(e)=> console.log(e.target.value)}>{props.value}</li>;
when clicked the eventHandler return 0 instead of Orange
how can I get the desired behavior ?
function ListItem(props) {
// --> displays the data / is reusable
return <li onClick={(e)=> console.log(e.target.value)}>{props.value}</li>;
}
function ChooseElements() {
const listItems = elementObjects.map((object) =>
<ListItem key={object.id.toString()} value={object.Element} />
);
return (
<ul>
{listItems}
</ul>
);
}
ReactDOM.render(
<ChooseElements />,
document.getElementById('root')
);

You don't need e.target. Your value is coming from your props. Your ListItem should look like this to log the value once clicked:
function ListItem(props) {
return <li onClick={() => console.log(props.value)}>{props.value}</li>;
}

here you go. use props.value for onClick
function ListItem(props) {
return <li onClick={()=> console.log(props.value)}>{props.value}</li>;
}

Can you use value from props instead of event target?
Something like this:
function ListItem(props) {
return <li onClick={() => console.log(props.value)}>{props.value}</li>;
}
?

li elements don't specifically have e.target.value
So you'll have to console.log props.value
function ListItem(props) {
// --> displays the data / is reusable
return <li onClick={(e)=> console.log(props.value)}>{props.value}</li>;
}

You could simply pass the props.value to the onClick event handler and there won't be a need for e.target.value.

Related

First click in child component is undefined in parent component React

I want to implement a "delete contact when clicked" in a simple contact manager app I'm making to learn React but it is very buggy.
The first time I click the item to delete it does nothing and when clicked another time it deletes the previous one. I'm new learning react and don't know why is this happening, someone can help?
const { useState } = React;
function AddPersonForm(props) {
const [person, setPerson] = useState("");
function handleChange(e) {
setPerson(e.target.value);
}
function handleSubmit(e) {
props.handleSubmit(person);
setPerson("");
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Add new contact"
onChange={handleChange}
value={person}
/>
<button type="submit">Add</button>
</form>
);
}
function PeopleList(props) {
const [person_, setPerson_] = useState("");
function handleCLick(e) {
setPerson_(e.target.textContent);
props.handleCLick(person_);
}
return (
<ul onClick={handleCLick}>
{props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
function ContactManager(props) {
const [contacts, setContacts] = useState(props.data);
function addPerson(name) {
setContacts([...contacts, name]);
}
function removePerson(name_) {
let filtered = contacts.filter(function (value) {
return value != name_;
});
setContacts(filtered);
}
return (
<div>
<AddPersonForm handleSubmit={addPerson} />
<PeopleList data={contacts} handleCLick={removePerson} />
</div>
);
}
const contacts = ["James Smith", "Thomas Anderson", "Bruce Wayne"];
ReactDOM.render(
<ContactManager data={contacts} />,
document.getElementById("root")
);
Issue
Having the onClick handler on the unordered list element seems a bit odd to me, but your issue a misunderstanding of when React state updates. React state updates are enqueued and asynchronously processed. In other words, when you enqueue the state update setPerson_(e.target.textContent) in handleClick in PeopleList, that person_ hasn't updated on the next line props.handleCLick(person_);. You are using state that hasn't updated yet.
Solution
Just send the click event object's value to the props.handleClick callback directly. You can also remove the local person_ state.
function PeopleList(props) {
function handleCLick(e) {
props.handleCLick(e.target.textContent);
}
return (
<ul onClick={handleCLick}>
{props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}

How pass to react list item to function as parameter at onClick

i wonder how i can pass as parameter to onClick function react list element
here is my list.
const items = this.state.tableData.items.map(
(item, index) => <td key={index} onClick={this.getItem}>{item.name}</td>
);
here is i define my function
constructor(props) {
super(props);
this.state = {
tableData: this.props.items
};
this.getItem = this.getItem.bind(this);
}
here is my function.
getItem(item) {
console.log(item)
}
I wan't to get item details onClick to item where i render my list.
i tried to make onClick={this.getItem(item)} i get with this way item details but not at onClick. Just all items details console logged when i open my page.
what can i do ?
There are two common ways: Using an arrow function, or using bind:
Arrow function:
const items = this.state.tableData.items.map(
(item, index) => <td key={index} onClick={e => this.getItem(item, e)}>{item.name}</td>
);
bind:
const items = this.state.tableData.items.map(
(item, index) => <td key={index} onClick={this.getItem.bind(this, item)}>{item.name}</td>
);
In both cases, with the above, getItem would get the item as its first argument, the event object as its second.
This is just an alternative answer. Separating the item into its own component would be an alternative. In this way, you can pass the onClick function without binding it then use it in the Item component like this.
const items = [{ id: 1, name: "foo" }, { id: 2, name: "bar" }];
const Item = ({ item, getItem}) => {
const handleClick = () => getItem(item);
return (
<div onClick={handleClick}>{item.name}</div>
)
}
const App = () => {
const getItem = item => console.log(item.id);
return (
<div className="App">
{items.map(item => (
<Item key={item.id} item={item} getItem={getItem} />
))}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
If there are so many items you can avoid rerendering all the items if none of their props changed since you don't use bind or any inline arrow function. But, as you can see it needs a separate component and some more coding.
Functional Components are going to win over classes.
So better to be used with this:
onClick={ ( item.id) => handleclick( item.id, [...])
Edit 1
Handleclick here intended as a Hook ( e. G. SetValue on useState).
Nb:
On purpose code dirty to fight against the awful copypasta SO practice. Thanks redditors. Read the code, then read the memes.

How to change State from inside .Map function React

I have this function
renderCompanies() {
if (this.props.companies)
return [
<div>
Dashboard hello <div>{this.renderProfile()}</div>
<div>
{this.props.companies.map(function(item, i) {
return (
<div>
<div
key={i}
onClick={item => {
this.setState({ currentCompany: item });
}}
>
{i}: {item.name}
</div>
<button>Delete Company</button>
</div>
);
})}
</div>
<AddCompanyPopUp />
</div>
];
}
I want to loop though this.props.companies and render a list of items. I want a user to be able to click on a specific item and have the item be saved to state.
This function runs inside another funtion
renderEitherMenuOrCompanyList() {
if (this.state.currentCompany) {
return <Menu companies={this.state.currentCompany} />;
} else {
return <div>{this.renderCompanies()}</div>;
}
}
Both are already bound to this
this.renderCompanies = this.renderCompanies.bind(this);
this.renderProfile = this.renderProfile.bind(this);
this.renderEitherMenuOrCompanyList = this.renderEitherMenuOrCompanyList.bind(this)
The renderEitherMenuOrCompanyList function is being called inside the render react function/method.
My problem is that I cannot set the state from the renderCompanies .map function. I keep getting "Cannot read property 'setState' of undefined" . This should be simple but I have not been able to do it
Make sure the function given to map is bound as well, or an arrow function:
{this.props.companies.map((item, i) => {
return (
<div>
<div
key={i}
onClick={() => {
this.setState({ currentCompany: item });
}}
>
{i}: {item.name}
</div>
<button>Delete Company</button>
</div>
);
})}
The function passed to this.props.companies.map isn’t an arrow function, so it creates a new this. Change it to an arrow function to preserve the this from outside of it.
this.props.companies.map( ( item, i ) => { ... } )
You’ve also named the argument to onClick item, but it’s actually the click event. You want the item already defined by the map function. Name the argument to onClick something else, or nothing, to avoid overwriting the item variable you actually want.
onClick={ () => { ... } }

Filtering an array in React

I am making a To-Do Application in React. I got stuck at some point.
I'm mapping through items in an array, and displaying it in an unordered list. I'm trying to use the filter function, to remove the deleted items from the array.
I assume that the problem in my code can be somewhere there, that I am passing the event object but pointing to the button, instead of the list item.
How can I do it in React? You can find my code attached below. Also it would be great to clear the input field after submitting an item.
import React, { Component } from 'react';
class ToDoList extends Component {
constructor(props) {
super(props);
this.state = {list: [], items: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRemove = this.handleRemove.bind(this);
}
handleChange(event) {
this.setState({items: event.target.value})
console.log(event.target.value);
}
handleSubmit(event) {
this.setState({ list: [...this.state.list, this.state.items]})
event.preventDefault();
}
handleRemove(event) {
const filteredArray = this.state.list.filter(item => item !== event.target.value)
this.setState({list: filteredArray});
console.log(event.target);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
<input
type="text"
value={this.state.value}
onChange={this.handleChange} />
</label>
<input
onClick={this.handleSubmit}
type="submit"
value="Submit" />
</form>
<ul>
{this.state.list.map((i, index) => (
<li key={index+1}>
{i}
<button
onClick={this.handleRemove}>
X
</button>
</li>
))}
</ul>
<p>Remaining: {this.state.list.length}</p>
</div>
);
}
}
export default ToDoList;
I would recommend using the optional additional arguments to .bind in order to change what's passed to your handler.
Call .bind(this. index) within your map method to pass the index of the element to be removed to the handler:
<ul>
{this.state.list.map((i, index) => (
<li key={index+1}>{i}<button onClick={this.handleRemove.bind(this, index)}>X</button></li>
))}
</ul>
And then update the handler to just remove the specified element:
handleRemove(index) {
const filteredArray = this.state.list.filter((_, i) => i !== index);
this.setState({
list: filteredArray
});
}
As to your second question, you should first fix up your input so its value is actually controlled by the state by changing value={this.state.value} to value={this.state.items}:
<input type="text" value={this.state.items} onChange={this.handleChange} />
And then simply clear this.state.items upon submission:
handleSubmit(event) {
this.setState({
list: [...this.state.list, this.state.items],
items: ''
})
event.preventDefault();
}
Check at this line:
<li key={index+1}>{i}<button onClick={this.handleRemove}>X</button></li>
Inside handleRemove, the instruction event.target point to the button. A button doesn't have a value attribute. So, you need to go up to parent and get it's content:
// childNodes[0] is a TextNode, so, to get it's content use data pop
const toRemove = event.target.parentNode.childNodes[0].data.trim();
const filteredArray = this.state.list.filter(item => item !== toRemove);
Another option is wrap the item in a element, like span:
<li key={index+1}><span>{i}</span><button onClick={this.handleRemove}>X</button></li>
And on remove get it to get the item value:
const toRemove = event.target.parentNode.children[0].textContent.trim();
const filteredArray = this.state.list.filter(item => item !==
event.target.value)

React: how to pass arguments to the callback

I have a list of elements inside my react component, and I want them to be clickable. On click I call some external function passing item ID in arguments:
render () {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id} onClick={() => {doSomething(item.id)}></li>
))}
</ul>
)
}
This code works, but it has a big performance drawback: a lot of new anonymous functions are being created on each call to render.
How can I pass that doSomething function as a reference here while still being able to provide a item.id to it?
You could use data-attributes, to set the correct id on each item while using the same function:
function doSomethingFromEvent(event){
return doSomething(event.target.dataset.id);
}
render () {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id} data-id={item.id} onClick={doSomethingFromEvent}></li>
))}
</ul>
)
}
When setting data-* attributes in your element, you can get it back with dataset, in the form of a hash. For example, in doSomethingFromEvent I have event.target.dataset = {id: *id*}. See more on MDN
This is even cleaner when updating a hash (the state for example), with <li key={item.id} data-myattriute={myvalue} onClick={this.handleClick}></li>, I can simply define handleClick such as:
handleClick(event){
// Here event.target.dataset = {myattribute: myvalue}
Object.assign(myObject, event.target.dataset);
// or
this.setState(event.target.dataset);
}
Coming back to your problem, the great thing with this approach is that if you ensure your container element (ul) cannot be clicked outside its children with data-attributes (li), which is your case, you can declare the function on it:
render () {
return (
<ul onClick={doSomethingFromEvent}>
{this.props.items.map(item => (
<li key={item.id} data-id={item.id}></li>
))}
</ul>
)
}
Now your function is created a single time, and is not even repeated in each item.
What you can do is create a partially applied or higher order function to enclose the item.id and pass it along. So let's look at a toy example of this:
class App {
partiallyApplied = id => e => {
console.log(id,'this is passed in first')
console.log(e,'this is passed in second')
}
render(){
return (
<button onClick={this.partiallyApplied(1234)}>Click Me</button>
)
}
}
Now you have access to 1234 along with your event object
This is use transform-class-properties babel plugin. If do not or cannot use that, you can probably do something like this:
partiallyApplied(id){
return function(e){
console.log(id,'this is id')
console.log(e,'this is event')
}
}
but then you will have to bind this during your call and I just don't like that everywhere.
You could create a new component for every item in the array and use the props, like this:
class Li extends React.Component {
render() {
return <li onClick={this.onClick}> {this.props.children} </li>;
}
onClick = () => {
console.log(this.props.item);
};
}
class App extends React.Component {
state = {
items: [
{id: 1, name: 'one'},
{id: 2, name: 'two'},
{id: 3, name: 'three'},
]
};
render() {
return <ul>
{this.state.items.map(i =>
<Li key={i.id} item={i}>{i.name}</Li>
)}
</ul>;
}
}

Categories

Resources