How can I setState of in a nested array - javascript

I was practicing a React and tried to build Arya's kill list. I played around and implemented some features, one supposed to change a person when double clicked. In state I have array (list of people) of array <--- and I want to setState to this array.
I tried to use a ternary operator which suppose to change state from false to true and opposite. But it doesn't work.
Here are examples of a code with solutions I tried to implement:
class App extends React.Component {
state = {
toKill: [
{ name: 'Cersei Lannister',
formDouble: false
},
{ name: 'Ser Ilyn Payne',
formDouble: false
},
{ name: 'The Mountain',
formDouble: false
},
{ name: 'The Hound',
formDouble: false
}
],
}
doubleClickHandler = (index) => {
this.setState({
toKill: this.state.toKill.map(obj =>
obj[index].formDouble ? false : true)
})
// console.log(this.state.toKill[index].formDouble)
// this.state.toKill[index].formDouble === false ?
// this.setState({
// [this.state.toKill[index].formDouble]: true
// }) : this.setState({
// [this.state.toKill[index].formDouble]: false
// })
}
(...)
<div>
{this.state.toKill.map((toKill, index) => {
return <ToKill
double ={() => this.doubleClickHandler(index)}
formDouble={toKill.formDouble}
click ={() => this.deleteToKillHandler(index)}
key={index + toKill.name}
name={toKill.name}
cause={toKill.cause}
img={toKill.img} />
})}
</div>
In doubleClickHandler you can see what I tried to implement and it didn't work.
Here is toKill component:
const ToKill = (props) => {
return (
<div className="hero-card" onDoubleClick={props.double}>
{props.formDouble !== true? <h1>test</h1>
: <>
<div className="hero-img-container">
<img
src={props.img}
alt={props.name}
className="hero-img"
/>
</div>
<div className="hero-desc">
<h3 className="hero-name">{props.name}</h3>
<p className="hero-cause">{props.cause}</p>
<button onClick={props.click}>Delete</button>
</div>
</>
}
</div>
)
}
So what I expect is once I double click on a specific element for example 'The mountain' it will show me it's profile while the rest will show <h1>test</h1>.

Most probably your doubleClickHandler is incorrect. As I understand you want to set formDouble to true for only single element, which you've clicked. In such case doubleClickHandler should be
doubleClickHandler = (index) => {
this.setState({
toKill: this.state.toKill.map((obj, objIndex) =>
objIndex === index ? {...obj, formDouble: true} : {...obj, formDouble: false})
})
}
A snippet using this code:
class App extends React.Component {
state = {
toKill: [
{ name: "Cersei Lannister", formDouble: false },
{ name: "Ser Ilyn Payne", formDouble: false },
{ name: "The Mountain", formDouble: false },
{ name: "The Hound", formDouble: false }
]
};
doubleClickHandler = index => {
this.setState({
toKill: this.state.toKill.map((obj, objIndex) =>
objIndex === index
? { ...obj, formDouble: true }
: { ...obj, formDouble: false }
)
});
};
render() {
return (
<div>
{this.state.toKill.map((toKill, index) => {
return (
<ToKill
double={() => this.doubleClickHandler(index)}
formDouble={toKill.formDouble}
click={() => this.deleteToKillHandler(index)}
key={index + toKill.name}
name={toKill.name}
cause={toKill.cause}
img={toKill.img}
/>
);
})}
</div>
);
}
}
const ToKill = props => {
return (
<div className="hero-card" onDoubleClick={props.double}>
{props.formDouble !== true ? (
<h1>test</h1>
) : (
<React.Fragment>
<div className="hero-img-container">
<img src={props.img} alt={props.name} className="hero-img" />
</div>
<div className="hero-desc">
<h3 className="hero-name">{props.name}</h3>
<p className="hero-cause">{props.cause}</p>
<button onClick={props.click}>Delete</button>
</div>
</React.Fragment>
)}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
<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="app"></div>

Related

'key' is not defined no-undef setState ReactJS

export default class Home extends Component {
state = {
items : [ { text: 'Buy grocery', done: true},
{ text: 'Play guitar', done: false},
{ text: 'Romantic dinner', done: false}
]
}
onItemClick = () =>{
this.setState(
prevState => ({
items: prevState.items.map(
el => el.key === key? { ...el, done: true }: el
)
})
)
}
render() {
return (
<Fragment>
<h1>
Home Page
</h1>
<TodoList items={this.state.items} clickAction={this.onItemClick} />
</Fragment>
)
}
export default class TodoList extends Component {
render() {
const itemlist = this.props.items.map((item, index) =>{
return <div key={index}>
<h4 onClick={this.props.clickAction}> {item.text + " " + item.done}</h4> </div>
})
return (
<Fragment>
<h5>
TodoList Page
</h5>
<section>
{itemlist}
</section>
</Fragment>
)
}
}
I want to update a single object property inside the array. onItemClick function, which should be called when user clicks an item in the list, if the item is marked "false" as done. Otherwise the onItemClick should not be called and the click event itself should not be propagated further.
getting error
'key' is not defined no-undef
When you are doing this:
el => el.key === key? { ...el, done: true }: el
you comapre el.key to key but the variable key is never defined

Adding clicked items to new array in React

I am making API calls and rendering different components within an object. One of those is illustrated below:
class Bases extends Component {
constructor() {
super();
this.state = {
'basesObject': {}
}
}
componentDidMount() {
this.getBases();
}
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
render() {
let {basesObject} = this.state;
let {bases} = basesObject;
console.log(bases);
//FALSY values: undefined, null, NaN, 0, false, ""
return (
<div>
{bases && bases.map(item =>
<button key={item.key} className="boxes">
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
)
}
}
The above renders a set of buttons. All my components look basically the same.
I render my components here:
class App extends Component {
state = {
ordersArray: []
}
render() {
return (
<div>
<h1>Bases</h1>
<Bases />
<h1>Frostings</h1>
<Frostings />
<h1>Toppings</h1>
<Toppings />
</div>
);
}
}
I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.
Try this
We can use the same component for all categories. All the data is handled by the parent (stateless component).
function Buttons({ list, handleClick }) {
return (
<div>
{list.map(({ key, name, price, isSelected }) => (
<button
className={isSelected ? "active" : ""}
key={key}
onClick={() => handleClick(key)}
>
<span>{name}</span>
<span>${price}</span>
</button>
))}
</div>
);
}
Fetch data in App component, pass the data and handleClick method into Buttons.
class App extends Component {
state = {
basesArray: [],
toppingsArray: []
};
componentDidMount() {
// Get bases and toppings list, and add isSelected attribute with default value false
this.setState({
basesArray: [
{ key: "bases1", name: "bases1", price: 1, isSelected: false },
{ key: "bases2", name: "bases2", price: 2, isSelected: false },
{ key: "bases3", name: "bases3", price: 3, isSelected: false }
],
toppingsArray: [
{ key: "topping1", name: "topping1", price: 1, isSelected: false },
{ key: "topping2", name: "topping2", price: 2, isSelected: false },
{ key: "topping3", name: "topping3", price: 3, isSelected: false }
]
});
}
// for single selected category
handleSingleSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => ({
...item,
isSelected: item.key === key
}))
}));
};
// for multiple selected category
handleMultiSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => {
if (item.key === key) {
return {
...item,
isSelected: !item.isSelected
};
}
return item;
})
}));
};
// get final selected item
handleSubmit = () => {
const { basesArray, toppingsArray } = this.state;
const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);
// submit the result here
}
render() {
const { basesArray, toppingsArray } = this.state;
return (
<div>
<h1>Bases</h1>
<Buttons
list={basesArray}
handleClick={this.handleSingleSelected("basesArray")}
/>
<h1>Toppings</h1>
<Buttons
list={toppingsArray}
handleClick={this.handleMultiSelected("toppingsArray")}
/>
</div>
);
}
}
export default App;
CSS
button {
margin: 5px;
}
button.active {
background: lightblue;
}
I think the following example would be a good start for your case.
Define a handleClick function where you can set state with setState as the following:
handleClick(item) {
this.setState(prevState => {
return {
...prevState,
clickedItems: [...prevState.clickedItems, item.key]
};
});
}
Create an array called clickedItems in constructor for state and bind handleClick:
constructor() {
super();
this.state = {
basesObject: {},
clickedItems: [],
}
this.handleClick = this.handleClick.bind(this);
}
You need to add a onClick={() => handleClick(item)} handler for onClick:
<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
I hope that helps!

map doesn't return correct array of object

Tried to do a map to set a value of selected to true and false but it doesn't change existing true value away, what's wrong?
selectTab = tab => e => {
const { data } = this.state;
this.setState(
{
data: data.map(o =>
o.tab === tab ? { selected: true, ...o } : { selected: false, ...o }
)
},
() => console.log(this.state.data)
);
};
https://codesandbox.io/s/885l8q537l
Try and spread the object first, and then add the selected property. Otherwise the selected property in o will overwrite it every time.
selectTab = tab => e => {
this.setState(
prevState => ({
data: prevState.data.map(o =>
o.tab === tab ? { ...o, selected: true } : { ...o, selected: false }
)
}),
() => console.log(this.state.data)
);
};
class App extends React.Component {
state = {
data: [
{
tab: 1,
content: "Tab 1 content",
selected: true
},
{
tab: 2,
content: "Tab 2 content"
},
{
tab: 3,
content: "Tab 3 content"
}
]
};
selectTab = tab => e => {
this.setState(
prevState => ({
data: prevState.data.map(o =>
o.tab === tab ? { ...o, selected: true } : { ...o, selected: false }
)
}),
() => console.log(this.state.data)
);
};
render() {
const { activeTab, data } = this.state;
return (
<div>
{data.map(obj => (
<div onClick={this.selectTab(obj.tab)}>{obj.tab}</div>
))}
{data.map(obj => (
<div>{obj.selected && obj.content}</div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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"></div>

How can I disable a particular button after click, in a buttons array?

My question is, how can I disable a particular button in a Button array depends on a click?
Below there is a SearchField component which consists of multiple buttons in it, I want to disable just the button clicked, but all the buttons turn to disabled, how can I solve this?
state = {
redirect: false,
loading: false,
alreadyAddedFav: false,
disabled: false
}
onClickedHandler = (recipe_id, token) => {
if (!this.props.isAuthenticated) {
this.setState({ redirect: true })
}
else {
const favData = {
recipe_id: recipe_id,
userId: this.props.userId
}
if (this.props.favRecipes.length > 0) {
if (!this.props.favRecipes.some(item => item.recipe_id === recipe_id)) {
console.log("added in the loop!")
this.props.onFav(favData, token);
this.setState({ disabled: true });
} else {
this.setState({ alreadyAddedFav: true })
console.log("it is already in the fav list!")
console.log(recipe_id)
}
} else {
this.props.onFav(favData, token);
this.setState({ disabled: true });
}
}
}
render() {
return (
<SearchResult
disabled={this.state.disabled}
key={ig.recipe_id}
title={ig.title}
imgSrc={ig.image_url}
clicked={() => this.onClickedHandler(ig.recipe_id, this.props.token)}
>
</SearchResult>)}
Here is a simple example, maybe you can enhance this according to your situation.
class App extends React.Component {
state = {
disableds: [],
};
handleClick = i =>
this.setState( currentState => ( {
disableds: [ ...currentState.disableds, i ],
} ) );
render() {
return (
<div className="App">
<Buttons onClick={this.handleClick} disableds={this.state.disableds} />
</div>
);
}
}
const Buttons = ( props ) => {
const buttons = [ { text: "foo" }, { text: "bar" }, { text: "baz" } ];
return (
<div>
{buttons.map( ( button, i ) => (
<button
key={button.text}
disabled={props.disableds.includes( i )}
onClick={() => props.onClick( i )}
>
{button.text}
</button>
) )}
</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>
What I don't like here is the way I added the onClick handler to the button element. In this way, this arrow function will be recreated in every render. Not so big deal but I prefer using function references. To avoid this we can extract each button element into its component and use a separate handler again.

Asynchronously concatenating clicked numbers in a cleared string

The goal of my small React experiment is "clear the initial value of this.state.numString (outputs an empty string), then concatenate the clicked numbers into this.state.numString". To make it execute asynchronously, I took advantage of this.setState's callback where the concatenation of number strings happen.
class App extends Component {
state = {
numString: '12'
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
this.setState({
numString: ''
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}
The result was not what I expected; it only adds the current number I click into the empty string then change it into the one I click next, no concatenation of string numbers happens.
Here is one way of doing this. As I said in my comment you are resetting the string in every setState. So, you need some kind of condition to do that.
class App extends React.Component {
state = {
numString: '12',
resetted: false,
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if ( !this.state.resetted ) {
this.setState({
numString: '',
resetted: true,
}, () => {
this.setState( prevState => ({
numString: prevState.numString.concat(num)
}))
})
} else {
this.setState(prevState => ({
numString: prevState.numString.concat(num)
}))
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = { padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem' };
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</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>
You can do something like below to clear the state immediately and concatenate the state with previous state value
this.setState({
numString: ''
}, () => {
this.setState( prevState => ({
numString: prevState.numString + num
}));
});
The code above in your question , in first setState you are setting variable to empty and in the second setState it is concatenating new value with empty string state. Thats why it is not working.
Try something like below:
class App extends Component {
state = {
numString: '12',
isFirstTime: true
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if(this.state.isFirstTime){
this.setState({
numString: '',
isFirstTime: false
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}else{
this.setState({
numString: this.state.numString.concat(num)
})
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data- num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}

Categories

Resources