checkCard(cardName, index) {
const item = {
name: cardName,
indexNumber: index
};
// check the part where you should look at
const newFlipStatus = this.state.flipped[index];
this.setState({
tempArray: [...this.state.tempArray, item],
flipped: !newFlipStatus
}, () => {
this.cardCounter(this.state.tempArray)
});
}
render() {
return (
<ul>
<button onClick={() => this.shuffleCard()}>Click</button>
{this.state.cardArray.map((items, index) => (
<li className={this.state.flipped ? 'card' : 'card true'} key={index} onClick={() => this.checkCard(items, index)}>{items}</li>
))}
</ul>
)
}
<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>
When the user clicks on a specific card in this case a specific element, but the problem I have now is that it is looping through all the element and change them all.
https://codeshare.io/5O0M7j
this.state = {
memoryCards: ['mario', 'luigi', 'browser', 'peach', 'wario', 'toad', 'yoshi', 'bowser-junior', 'waluigi', 'diddy-kong-jr' ],
cardArray: [],
newArray: [],
tempArray: [],
arrayHere: [],
count: 0,
score: 0,
flipped: false
}
You only have a single flipped property which is representing the whole deck of cards. This is the same property you are checking in:
<li className={this.state.flipped ? 'card' : 'card true'}
So when you click on it, you update the state, but this state property is used on all the cards, so all the li elements will get the card classname.
Your check:
const newFlipStatus = this.state.flipped[index];
Will also return false, as you defined flipped as a boolean at the start. Then you assign it, which will be true.
flipped: !newFlipStatus
If you update your flipped state to an Object:
this.state = {
flipped: {}
...
}
You can update your Render function to check the property, on the Flipped Object, which related to the Index of the card:
<li className={this.state.flipped[index] ? 'card' : 'card true'}....
And then within your checkCard Function, you can check can flip the value and reassign it to your Flipped Object:
this.setState(state => ({
tempArray: [...state.tempArray, item],
flipped: {
...state.flipped,
[index]: !state.flipped[index]
}
}), () => {
this.cardCounter(this.state.tempArray)
});
Related
const listItems = items.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
)
Especially what is the difference between item.id and id? and why use : item at the end?
Full code below, Note that the code isn't something I'm working on, it is a tutorial and there is still more to code
import React from "react";
import { useState } from "react";
import { FaTrashAlt } from "react-icons/fa";
const Content = () => {
const [items, setItems] = useState([
{
id: 1,
checked: false,
item: "One half bag of cocoa covered",
},
{
id: 2,
checked: false,
item: "Item 2",
},
{
id: 3,
checked: false,
item: "Item 3",
},
]);
const handleCheck = (id) => {
console.log(`key: ${id}`);
const listItems = items.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
);
setItems(listItems);
localStorage.setItem("shoppinglist", JSON.stringify(listItems));
};
return (
<main>
<main>
<ul>
{items.map((item) => (
<li className="item" key={item.id}>
<input
type="checkbox"
onChange={() => handleCheck(item.id)}
checked={item.checked}
/>
<label
style={item.checked ? { color: "red" } : { color: "green" }}
onClick={() => handleCheck(item.id)}
>
{item.item}
</label>
{/* <button>Delete</button> */}
<FaTrashAlt
onClick={() => handleCheck(item.id)}
role="button"
tabIndex="0"
/>
</li>
))}
</ul>
</main>
</main>
);
};
export default Content;
items.map(item => ) is mapping over an array of items, item is the object of the current item being mapped
id is the id that comes from handleCheck function(triggered onClick), which is the id of the clicked item, while item.id is the id of the current item from the array of objects
item.id === id ? { ...item, checked: !item.checked } : item uses the ternary operator which is a shorthand if/else to check if the id of the current item is the same as the id of the clicked item
if it is(true) it adds to the current item object checked: !item.checked, if it isn't(false) it returns the item object as it is
Here this code trying to keep items array immutable,if you change item.checked value it without using
It will modify items array too
{ ...item, checked: !item.checked }
And Id is used to collect only particular item and modifications of it.
The map function iterates through all the items in a list. Each item has an id, which is the list.id variable. The function takes id as a param.
Then if item.id equals the passed in id, a modified item will be returned. If not, the iten is returned unmodified. That's what :item at the end is for.
A few basics to start since I'm not sure where the confusion lies:
map() takes the contents of a list and returns another list / array
based on those concepts. e.g. [1,2,3].map(x => x *2) will return [ 2, 4, 6 ]
The ternary ?: operator takes three args (hence the name) If the
first is true, returns the second, otherwise the third
e.g. true ? 'yes' : 'no' returns 'yes'
The spread operator populates one object with the contents of
another. e.g. If a = { foo: 'Hello' } then
{ ...a, bar: 'World' } means { foo: 'Hello', bar: 'World' }
So your example takes the first list and If the ID matches the checkbox ID being targetted, it returns a new object with the state flipped, otherwise it returns the original object.
The net effect is a list of IDs with the "checked" status flipped on one of the items. Make sense?
suppose i am having an array of like
[
{ id: 1, name: "John" , selected: true}
{ id: 2, name: "Jim" , selected: false }
{ id: 3, name: "James" , selected: false }
]
I am having function which get id and on the basis of that id I want to change the selected property of object to true and other selected property object to false
here is my function what i have tried but its throwing error
const handleOnClick = (id) => {
const temps = state.findIndex((sub) => sub.id === id)
setState(prevState => ({
...prevState,
[prevState[temps].selected]: !prevState[temps].selected
}))
Use a .map() to iterate everything. You shouldn't change values within the array without recreating the tree down to the changed object. See below example.
Below you will:
map over every element
Spread their current values
Overwrite the selected depending on if the id is the newly "selected" id
I also supplied an additional function to update using the index of the item in the array instead of by id since your title is "how to update object using index in react state?", although your question also mentions changing by id. I would say go by id if you have the choice, array indices can change.
const App = () => {
const [state, setState] = React.useState([
{ id: 1, name: "John" , selected: true},
{ id: 2, name: "Jim" , selected: false },
{ id: 3, name: "James" , selected: false }
]);
// This takes the ID of the item
const handleOnClick = (id) => {
const newState = state.map(item => {
return {
...item,
selected: item.id === id
};
});
setState(newState);
};
// This takes the index of the item in the array
const handleOnClickByIndex = (indexToSelect) => {
const newState = state.map((item, idx) => {
return {
...item,
selected: indexToSelect === idx
};
});
setState(newState);
};
return (
<div>
{state.map((item, mapIndex) => (
<div key={item.id}>
<div>
<span>{item.name} is selected: {item.selected ? "yes" : "no"}</span>
{/* This will update by ID*/}
<button onClick={() => handleOnClick(item.id)}>Select using ID</button>
{/* This will update by the index of the array (supplied by map) */}
<button onClick={() => handleOnClickByIndex(mapIndex)}>Select using index</button>
</div>
</div>
))}
</div>
);
};
ReactDOM.render(
<App/>,
document.getElementById("app")
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="app"></div>
I have a group of 3 checkboxes and the main checkbox for checking those 3 checkboxes.
When I select all 3 checkboxes I want for main checkbox to become checked.
When I check those 3 checkboxes nothing happens but when I then uncheck one of those trees the main checkbox becomes checked.
Can someone explain to me what actually is happening behind the scenes and help me somehow to solve this mystery of React state? Thanks!
Here is a code snnipet:
state = {
data: [
{ checked: false, id: 1 },
{ checked: false, id: 2 },
{ checked: false, id: 3 }
],
main: false,
}
onCheckboxChange = id => {
const data = [...this.state.data];
data.forEach(item => {
if (item.id === id) {
item.checked = !item.checked;
}
})
const everyCheckBoxIsTrue = checkbox.every(item => item === true);
this.setState({ data: data, main: everyCheckBoxIsTrue });
}
onMainCheckBoxChange = () => {
let data = [...this.state.data];
data.forEach(item => {
!this.state.main ? item.checked = true : item.checked = false
})
this.setState({
this.state.main: !this.state.main,
this.state.data: data,
});
}
render () {
const checkbox = this.state.data.map(item => (
<input
type="checkbox"
checked={item.checked}
onChange={() => this.onCheckboxChange(item.id)}
/>
))
}
return (
<input type="checkbox" name="main" checked={this.state.main} onChange={this.onMainCheckBoxChange} />
{checkbox}
)
I can't make a working code snippet based on the code you provided, one of the issues was:
const everyCheckBoxIsTrue = checkbox.every(item => item === true);
where checkbox is not defined.
However, I think you confused about using the old state vs the new state, it'd be simpler to differentiate if you name it clearly, e.g.:
eventHandler() {
const { data } = this.state; // old state
const newData = data.map(each => ...); // new object, soon-to-be new state
this.setState({ data }); // update state
}
Here's a working example for your reference:
class App extends React.Component {
state = {
data: [
{ checked: false, id: 1 },
{ checked: false, id: 2 },
{ checked: false, id: 3 }
],
main: false,
}
onCheckboxChange(id) {
const { data } = this.state;
const newData = data.map(each => {
if (each.id === id) {
// Toggle the previous checked value
return Object.assign({}, each, { checked: !each.checked });
}
return each;
});
this.setState({
data: newData,
// Check if every checked box is checked
main: newData.every(item => item.checked === true),
});
}
onMainCheckBoxChange() {
const { main, data } = this.state;
// Toggle the previous main value
const newValue = !main;
this.setState({
data: data.map(each => Object.assign({}, each, { checked: newValue })),
main: newValue,
});
}
render () {
const { data, main } = this.state;
return (
<div>
<label>Main</label>
<input
type="checkbox"
name="main"
// TODO this should be automatically checked instead of assigning to the state
checked={main}
onChange={() => this.onMainCheckBoxChange()}
/>
{
data.map(item => (
<div>
<label>{item.id}</label>
<input
type="checkbox"
checked={item.checked}
onChange={() => this.onCheckboxChange(item.id)}
/>
</div>
))
}
</div>
);
}
}
ReactDOM.render(
<App />
, document.querySelector('#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>
Side note: You might want to consider not to use the main state
You shouldn't be storing state.main to determine whether every checkbox is checked.
You are already storing state that determines if all checkboxes are checked, because all checkboxes must be checked if every object in state.data has checked: true.
You can simply render the main checkbox like this:
<input
type="checkbox"
name="main"
checked={this.state.data.every(v => v.checked)}
onChange={this.onMainCheckBoxChange}
/>;
The line this.state.data.every(v => v.checked) will return true if all of the checkboxes are checked.
And when the main checkbox is toggled, the function can look like this:
onMainCheckBoxChange = () => {
this.setState(prev => {
// If all are checked, then we want to uncheck all checkboxes
if (this.state.data.every(v => v.checked)) {
return {
data: prev.data.map(v => ({ ...v, checked: false })),
};
}
// Else some checkboxes must be unchecked, so we check them all
return {
data: prev.data.map(v => ({ ...v, checked: true })),
};
});
};
It is good practice to only store state that you NEED to store. Any state that can be calculated from other state (for example, "are all checkboxes checked?") should be calculated inside the render function. See here where it says:
What Shouldn’t Go in State? ... Computed data: Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation within render(). For example, if you have an array of list items in state and you want to render the count as a string, simply render this.state.listItems.length + ' list items' in your render() method rather than storing it on state.
I am trying to keep track of which boxes are checked in my local state(you can check multiple boxes). I want to be able to check and uncheck the boxes and keep track of the ids of the boxes that are checked. I will do something with the values later. This is what I have so far:
import React, { Component } from 'react'
import './App.css'
import CheckBox from './CheckBox'
class App extends Component {
constructor(props) {
super(props)
this.state = {
fruits: [
{id: 1, value: "banana", isChecked: false},
{id: 2, value: "apple", isChecked: false},
{id: 3, value: "mango", isChecked: false},
{id: 4, value: "grape", isChecked: false}
],
fruitIds: []
}
}
handleCheckChildElement = (e) => {
const index = this.state.fruits.findIndex((fruit) => fruit.value === e.target.value),
fruits = [...this.state.fruits],
checkedOrNot = e.target.checked === true ? true : false;
fruits[index] = {id: fruits[index].id, value: fruits[index].value, isChecked: checkedOrNot};
this.setState({fruits});
this.updateCheckedIds(e);
}
updateCheckedIds = (e) => {
const fruitIds = [...this.state.fruitIds],
updatedFruitIds= fruitIds.concat(e.target.id);
this.setState({updatedFruitIds});
}
render() {
const { fruits } = this.state;
if (!fruits) return;
const fruitOptions = fruits.map((fruit, index) => {
return (
<CheckBox key={index}
handleCheckChildElement={this.handleCheckChildElement}
isChecked={fruit.isChecked}
id={fruit.id}
value={fruit.value}
/>
);
})
return (
<div className="App">
<h1>Choose one or more fruits</h1>
<ul>
{ fruitOptions }
</ul>
</div>
);
}
}
export default App
So basically I am able to check and uncheck the boxes, but I cannot seem to update and store the fruitIds. Here is my checkbox component also:
import React from 'react'
export const CheckBox = props => {
return (
<li>
<input key={props.id}
onChange={props.handleCheckChildElement}
type="checkbox"
id={props.id}
checked={props.isChecked}
value={props.value}
/>
{props.value}
</li>
)
}
export default CheckBox
Also if you have a cleaner ways to do this than the way I am doing it, I would love to see it.
This is what if I were to approach it I will do. I will create a one dimensional array that holds the id's of the fruits when A fruit if clicked(checked) I will add it id to the array and when its clicked the second time I check if the array already has the id I remove it. then the presence of id in the array will mean the fruit is checked otherwise its not checked So I will do something like below
this.state={
fruitsIds: []
}
handleCheckChildElement=(id) => {
//the logic here is to remove the id if its already exist else add it. and set it back to state
const fruitsIds = this.state.fruitsIds;
this.setState({fruitsIds: fruitsIds.contains(id) ? fruitsIds.filter(i => i != id) : [...fruitsIds, id] })
}
then I render the checkboxes like
<CheckBox key={index}
handleCheckChildElement={this.handleCheckChildElement}
isChecked = { this.state.fruitsIds.contains(fruit.id)}
id={fruit.id}
/>
This is because you can always use the id to get all the other properties of the fruit so there is absolutely no need storing them again.
then the checkbox component should be as follows
export const CheckBox = props => {
return (
<li>
<input key={props.id}
onChange={() => props.handleCheckChildElement(props.id)}
type="checkbox"
id={props.id}
checked={props.isChecked}
value={props.value}
/>
{props.value}
</li>
)
}
The reason you are not getting your ids updated because:
You are trying to concat a non array element to an array.
concat is used for joining two or more arrays.
updatedFruitIds = fruitIds.concat(e.target.id);
You are not updating your actual fruitIds state field. I dont know why you are using "updatedFruitIds" this variable but due to above error it will always result into a single element array.
this.setState({ updatedFruitIds });
updateCheckedIds = e => {
const fruitIds = [...this.state.fruitIds],
updatedFruitIds = fruitIds.concat([e.target.id]);
this.setState({ fruitIds: updatedFruitIds });
};
OR
updateCheckedIds = e => {
const fruitIds = [...this.state.fruitIds, e.target.id],
this.setState({ fruitIds });
};
im new to react, trying to make an todolist website, i have the add and delete and displaying functionality done, just trying to add an search function, but i cant seem to get it working, where as it doesn't filter properly.
i basically want to be able to filter the values on the todos.title with the search value. such as if i enter an value of "ta" it should show the todo item of "take out the trash" or any item that matches with that string.
when i try to search, it gives random outputs of items from the filtered, i am wondering if my filtering is wrong or if i am not like displaying it correctly.
ive tried to pass the value into todo.js and display it there but didn't seem that was a viable way as it it should stay within App.js.
class App extends Component {
state = {
todos: [
{
id: uuid.v4(),
title: "take out the trash",
completed: false
},
{
id: uuid.v4(),
title: "Dinner with wife",
completed: true
},
{
id: uuid.v4(),
title: "Meeting with Boss",
completed: false
}
],
filtered: []
};
// checking complete on the state
markComplete = id => {
this.setState({
todos: this.state.filtered.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
};
//delete the item
delTodo = id => {
this.setState({
filtered: [...this.state.filtered.filter(filtered => filtered.id !== id)]
});
};
//Add item to the list
addTodo = title => {
const newTodo = {
id: uuid.v4(),
title,
comepleted: false
};
this.setState({ filtered: [...this.state.filtered, newTodo] });
};
// my attempt to do search filter on the value recieved from the search field (search):
search = (search) => {
let currentTodos = [];
let newList = [];
if (search !== "") {
currentTodos = this.state.todos;
newList = currentTodos.filter( todo => {
const lc = todo.title.toLowerCase();
const filter = search.toLowerCase();
return lc.includes(filter);
});
} else {
newList = this.state.todos;
}
this.setState({
filtered: newList
});
console.log(search);
};
componentDidMount() {
this.setState({
filtered: this.state.todos
});
}
componentWillReceiveProps(nextProps) {
this.setState({
filtered: nextProps.todos
});
}
render() {
return (
<div className="App">
<div className="container">
<Header search={this.search} />
<AddTodo addTodo={this.addTodo} />
<Todos
todos={this.state.filtered}
markComplete={this.markComplete}
delTodo={this.delTodo}
/>
</div>
</div>
);
}
}
export default App;
search value comes from the header where the value is passed through as a props. i've checked that and it works fine.
Todos.js
class Todos extends Component {
state = {
searchResults: null
}
render() {
return (
this.props.todos.map((todo) => {
return <TodoItem key={todo.id} todo = {todo}
markComplete={this.props.markComplete}
delTodo={this.props.delTodo}
/>
})
);
}
}
TodoItem.js is just the component that displays the item.
I not sure if this is enough to understand the issue 100%, i can add more if needed.
Thank you
Not sure what is wrong with your script. Looks to me it works fine when I am trying to reconstruct by using most of your logic. Please check working demo here: https://codesandbox.io/s/q9jy17p47j
Just my guess, it could be there is something wrong with your <TodoItem/> component which makes it not rendered correctly. Maybe you could try to use a primitive element such as <li> instead custom element like <TodoItem/>. The problem could be your logic of markComplete() things ( if it is doing hiding element works ).
Please let me know if I am missing something. Thanks.