How to change State from inside .Map function React - javascript

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={ () => { ... } }

Related

React key error although all elements have unique identifier

So I am getting used to React and I understand when using the map function each item needs to be assigned a unique key. I believe I am doing this correctly
return (
countries.map(country => {
return <>
<div key={country.name.common}>
{country.name.common} <button onClick={() => setCountries([country])}>show</button>
</div>
</>
})
)
I still receive an error in the console saying:
Warning: Each child in a list should have a unique "key" prop.
at CountryList.
at div.
Where CountryList being the file the code is extracted from. I have tried adding the same key to the button element and also tried giving the button element its own unique key.
descriptive key error message
countries is a call to "https://restcountries.com/v3.1/all" api.
useEffect(() => {
axios.get("https://restcountries.com/v3.1/all")
.then(response => setCountries(response.data))
}, [])
const handleFilterChange = (event) => {
setFilter(event.target.value)
const filtered = countries.filter(country => country.name.common.toLowerCase().includes(event.target.value))
setCountries(filtered)
}
console error message along with what the app looks like
The key must be placed on the parent element that you are returning from the map function.
Since it's a fragment in this case, you can't directly assign a key to it, unless you use the actual fragment component.
// Can't assign a key
<></>
// Can assign a key
<React.Fragment key={...}></React.Fragment>
Then again, if you only have the div here, why do you need the fragment?
A shorter syntax of your code would look like this:
return (
countries.map(country => (
<div key={country.name.common}>
{country.name.common}
<button onClick={() => setCountries([country])}>show</button>
</div>
))
)
You are using <> as a perent tag and add key in child <div> tag. remove <></> as you dont need this or use Fragment insted
return countries.map(country => (
<div key={country.name.common}>
{country.name.common}
<button onClick={() => setCountries([country])}>show</button>
</div>
))
or
return countries.map(country => {
return (
<React.Fragment key={country.name.common}>
<div>
{country.name.common} <button onClick={() => setCountries([country])}>show</button>
</div>
</React.Fragment>
)
})

Efficiently adding click listener to an arbitrary number of elements

I want to listen for click events on an arbitrary number of elements and inside the click event handler, I want to retrieve some info about the clicked element (info which was easily accessible during element creation).
A common solution to this problem is this:
render() {
return (
<div>
{this.props.users.map(user => (
<button onClick={() => this.buttonClicked(user.email)}>
{user.name}
</button>
))}
</div>
);
}
The flaw I see in this approach is that we're creating a new function for every element. Is that a problem worth solving? If it is, how do you feel about this solution:
render() {
return (
<div>
{this.props.users.map((user, index) => (
<button data-index={index} onClick={this.buttonClicked}>
{user.name}
</button>
))}
</div>
);
}
buttonClicked(event) {
const { index } = event.currentTarget.dataset;
const { email } = this.props.users[index];
// ...
}
Create another component and dispatch the event from it.
class Button extends React.PureComponent{
handleOnClick = ()=>{
const {onClick, ...rest} = this.props
if(onClick typeof ==='function'){
onClick(rest)
}
}
render(){
const {name} = this.props
return (
<button onClick={this.handleOnClick}>
{name}
</button>)
}
}
...
render() {
return (
<div>
{this.props.users.map(user => (
<Button {...user} key={user.email} onClick={this.buttonClicked} />
))}
</div>
);
}
I believe it is better to use a defined function instead of anonymous/inline functions, otherwise the onClick handler only gets created during the render stage.
In your second example, you can actually bind the argument to the handler:
render() {
return (
<div>
{this.props.users.map((user, index) => (
<button data-index={index} onClick={this.buttonClicked.bind(this, user.email)}>
{user.name}
</button>
))}
</div>
);
}
Secondly, wanted to point out that there is no performance issue with having many handlers. React uses one event handler at the root of your document.

Map iterator undefined React.JS

I'm trying to render a list, but when I try and map over the list I can not access each individual element, because I ReferenceError saying that 'e' is undefined. Am I writing this correctly?
render() {
return (
<div>
{console.log(Object.keys(this.props.emojis))} --> Returns the correct list
Object.keys(this.props.emojis).map(e => (
{console.log("EMOJI: ",e)}
<Emoji emote={e} />
))
</div>
)
}
Write it like this, it will work:
render() {
return (
<div>
{
Object.keys(this.props.emojis).map((e,i) => {
console.log("EMOJI: ",e);
return <Emoji key={i} emote={e}/>
})
}
</div>
)
}
Changes:
You are already inside a map function, so no need to use {} for console.log.
You are using () with map function and inside () you are using 2 statement, that is not allowed with (), if you want to do some calculation always use {}, and return something inside it.
Suggestion: Always assign key whenever creating the ui items dynamically.
Let me know if you need any help.
See if this work for you.
logging(e) {
console.log("EMOJI: ", e);
}
render() {
return (
<div>
Object.keys(this.props.emojis).map(e => (
this.logging(e);
<Emoji emote={e} />
))
</div>
)
}

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>;
}
}

ReactJs multiple functions call inline

My target is to call multiple functions onClick. Assume that some of them has taken event and some value, so it is unable to call them in single function. How can I achieve that?
render() {
//describe first function (1)
const amountSelectClearInput = function(event) {
// call two functions here
this.amountSelect(event);
this.clearInput();
}.bind(this);
var self = this;
return (
<div>
<div>
{amounts.map(function (name, index) {
return <input type="button" value={name}
className={self.state.active === name ? 'active' : ''}
//here I must call () => self.changeClass(name) (1), and amountSelectClearInput (2)
onClick={() => self.changeClass(name)} key={ name }/>;
})}
</div>
</div>
);
}
I need to call amountSelectClearInput and () => self.changeClass(name) at same time
onClick={function() { amountSelectClearInput(event); self.changeClass(name)}}

Categories

Resources