I have a filter options, which shows checkbox. So when click on each checkbox the value should be added to array if not exists and remove the value from array if already exists and the state should be updated. I have tried using below code and it is not working.
const [showFilter, setFilter] = useState([]);
useEffect(() => {
dispatch(fetchproducts(slug, sort, pageInitial+1, showFilter));
console.log(showFilter);
}, [showFilter]);
function filterClick (id, title) {
const index = showFilter.indexOf(id);
if (index > -1)
setFilter(showFilter.splice(index, 1));
else
setFilter(showFilter.concat(id));
}
return (
<ul style={{display: showMe.includes(index) ? "block" : "none"}}>
{item.items.map((single, index1) => (
<li key={index1}>
<label><input type="checkbox" name="checkbox" onClick={(e) => filterClick(e.target.value, item.title)} value={single.items_id}/> {single.items_value}</label>
</li>
))}
</ul>
)
In the above code, array insertion is working, but the splice is not working and the state is not updating.
How to alter my code to get the expected result.
You use useEffect. The useEffect's callback will be triggered when one of dependency is changed.
splice function changes array in place (ie mutates the array). In this case your array variable (showFilter) is not changed, therefore useEffect's callback will not be triggered.
Try using filter function instead:
setFilter(showFilter.filter(el=> el !== id));
Splice modifies the original array which is not considered a good practice in React. Please use slice or`filter.
Using slice your code would look like:
setFilter(showFilter.slice(index, index + 1))
Related
So I am new to javascript and I tried making a todo list. This works well with adding elements. The issue is when I am removing some item, the first one gets removed too, why is it so? I know I am missing a small thing and this may be really basic but I am not able to find out what that is.
const App1 = () => {
const [item, updatedItem]=useState('');
const [Items, setItems]=useState([]);
function inputEvent(event) {
updatedItem(event.target.value);
}
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return[
...prev,
item
]
});
updatedItem('');
}
let key=0;
return(<>
<div className='back'>
<div className='list'>
<header>ToDo List</header>
<form onSubmit={addItem}>
<input type='text' placeholder='Add an item' value={item} onChange={inputEvent}/>
<button type='submit'>+</button>
</form>
<div className='items'>
<ol>
{Items.map((val) => <li><button id={key++} onClick={(event) => {
setItems((Items) => {
return Items.filter((val, index) => {
if(index!==Number(event.target.id)){
return index;
}
}
);
});
key=0;
}}>x</button>{val}</li>)}
</ol>
</div>
</div>
</div>
</>);
You return the index in your filter, expecting this to always be true, yet 0 (the index of the first element) is a falsy value.
Try this instead:
return Items.filter((_val, index) => index !== Number(event.target.id));
Some unrelated code-quality notes:
In React, you should always set a key prop on each element when looping through them, rather than id.
map has a second argument, index, which it passes into the callback --- you don't have to keep track of this yourself with e.g. key++ etc.
If you use map's index parameter, then you can pass that directly into your filter rather than using Number(event.target.id), which is not very idiomatic in React.
If you don't use an argument of a callback, it's a good idea to prefix it with a _ (like I've done with _val here), to make it explicit that you're not using it.
Your filter callback should return a flag. index is a number. When treated as a flag, 0 is false (more on MDN). Instead:
return Items.filter((val, index) => index !== Number(event.target.id));
However, your code is returning an array of li elements without setting key on them (see: keys), which React needs in order to manage that list properly (you should be seeing a warning about it in devtools if you're using the development version of the libs, which is best in development). You can't use the mechanism you're using now for keys when doing that, it will not work reliably (see this article linked by the React documentation). Instead, assign each Todo item a unique ID when you create it that doesn't change, and use that as the key (and as the value to look for when removing the item):
// Outside the component:
let lastId = 0;
// Inside the component:
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return [
...prev,
{text: item, id: ++lastId}
];
});
updatedItem("");
};
// Add a remove function:
const removeItem = ({currentTarget}) => {
const id = +currentTarget.getAttribute("data-id"); // Get the ID, convert string to number
setItems(items => items.filter(item => item.id !== id));
};
// When rendering:
{Items.map((item) => <li key={item.id}><button data-id={item.id} onClick={removeItem}>x</button>{item.text}</li>)}
In some cases it may be useful to use useCallback to memoize removeItem to avoid unnecessary rendering, but often that's overkill.
I'm updating an object within react's state which I use to display a list. The state updates correctly, however the display breaks.
This is the section of the code from inside my render function which produces the list.
this.state.shoppingItems ? this.state.shoppingItems.currentShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
Here is the code that produces the previous items list:
this.state.shoppingItems ? this.state.shoppingItems.previousShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
This is the method which removes the item from the current list and adds it to the previous list, where the issue occurs.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
let shoppingItems = this.state.shoppingItems;
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
Here is the list before I remove the item.
Here is the list after I remove the middle item:
Finally here is the console.log output which shows that the items have updated properly but the display hasn't updated:
I'm entirely new to react coming from angular so I have no idea if this is the correct way to do this or if there is a better way. But could somebody help me figure out why the display isn't updating?
The issue seemed to be the key on the item in the map. I replaced the index with the item's id from the database as below and now it renders properly.
return <ItemSummary key={task._id} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={task} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
Similar answer here:
Change to React State doesn't update display in functional component
The issue is the update for shoppingItems. You save a reference to the current state object, mutate it, and store it back in state. Spreading this.state.shoppingItems into a new object first will create a new object reference for react to pick up the change of.
React uses shallow object comparison of previous state and prop values to next state and prop values to compute what needs to be rerendered to the DOM and screen.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
const shoppingItems = {...this.state.shoppingItems};
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
I had a similar issue with my application in which I had to delete comments made.
<textarea disabled key={note._id} className="form-control">{note.note}</textarea>
But the issue got resolved when I added the Key attribute to the list item.
when I click on the button to sort the data about countries after the website is loaded then the data displays correclty. But when I want to sort it again by different value, the data doesn't update and the state also doesn't update, however, the function works well, as I can see that the results in the console are sorted correctly. What can be the reason for this weird behaviour? Thank you for your time.
function AllCountries({ countries }) {
const [sortedCountries, setSortedCountries] = useState([]);
useEffect(() => {
console.log(sortedCountries)
}, [sortedCountries])
const sortResults = (val) => {
let sortedResults = countries.sort((a, b) => {
if (val === "deaths") {
return b.TotalDeaths - a.TotalDeaths;
} else if (val === "cases") {
return b.TotalConfirmed - a.TotalConfirmed;
} else if (val === "recovered") {
return b.TotalRecovered - a.TotalRecovered;
}
});
console.log(sortedResults);
setSortedCountries(sortedResults);
};
return (
<div className="all-container">
<p>Sort countries by the highest number of:</p>
<button onClick={() => sortResults("deaths")}>Deaths</button>
<button onClick={() => sortResults("cases")}>Cases</button>
<button onClick={() => sortResults("recovered")}>Recoveries</button>
<ul className="all-countries">
{sortedCountries.map((country, key) => {
return <li key={key}>{country.Country}</li>;
})}
</ul>
</div>
);
}
export default AllCountries;
Array.sort() method doesn't return new reference, it returns the same reference. So I assume in sortedCountries state is stored the same props.countries reference, that is why second button click doesn't set the state (it is the same reference), I can give you simple solution just change this line setSortedCountries(sortedResults) with this setSortedCountries([...sortedResults]) in this case copy of the sortedResults will be passed to setSortedCountries (new reference).
let sortedResults = countries.sort
this line is sorting the countries props and setting sortedResults to that pointer
Let us assume that pointer is held at mem |0001| for simplicity
After your first click, the function is fired, the prop is sorted, and sortedResults is set via setSortedCountries (set state).
This fires off the render that you desire, because the previous state was undefined, and now the state is pointing to |0001|
When your function runs again, the sort function fires off, does its work on |0001| and returns -- you guessed it -- |0001| but with your new sorted array.
When you go to set the state a second time, there wasn't actually any state changed because the previous country is |0001| and the country you want to change to is |0001|
So what can we do my good sir?
First I need you to think about the problem for a second, think about how you could solve this and try to apply some changes.
What you can try to do is copy the countries props to a new array pointer with the same values, and then sort it, and then set sortedCountries state to that list.
Does that make sense?
By the way, this is for similar reasons why if you try to setState directly on an new object state, the new object state will be exactly equal to the one you set. There isn't any real magic going on here. React does not automagically merge your previous object state and your new one, unless you tell it explicitly to do so.
In some sense you have told react to check differences between two states, an old country and a new country state (behind the scenes with the Vdom), but to react, those two states have no difference. And since the two object states have no difference, there will be no actual DOM changes.
You setting a pointer twice will therefore produce no actual changes.
You must therefore set state to an actual new pointer with new values, so the react virtual dom can compare the two states (the previous countries list) and the new countries list.
Hope that helps
-
It's a bit problem in React and another solution is by using the useReducer instead. such as below:
function reducer(state, action) {
return [...state, ...action];
}
function AllCountries({ countries }) {
const [sortedCountries, setSortedCountries] = useReducer(reducer, []);
useEffect(() => {
console.log(sortedCountries)
}, [sortedCountries])
const sortResults = (e, val) => {
e.preventDefault();
let sortedResults = countries.sort((a, b) => {
if (val === "deaths") {
return b.TotalDeaths - a.TotalDeaths;
} else if (val === "cases") {
return b.TotalConfirmed - a.TotalConfirmed;
} else if (val === "recovered") {
return b.TotalRecovered - a.TotalRecovered;
}
});
console.log(sortedResults);
setSortedCountries(sortedResults);
};
return (
<div className="all-container">
<p>Sort countries by the highest number of:</p>
<button onClick={(e) => sortResults(e, "deaths")}>Deaths</button>
<button onClick={(e) => sortResults(e, "cases")}>Cases</button>
<button onClick={(e) => sortResults(e, "recovered")}>Recoveries</button>
<ul className="all-countries">
{sortedCountries.map((country, key) => {
return <li key={key}>{country.Country}</li>;
})}
</ul>
</div>
);
}
export default AllCountries;
I have built a simple ToDo App. Rending the tasks from the form input is working fine, but I am unable to delete the tasks when clicked on Delete button.
export class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
task:'',
items:[]
}
}
onChangeHandler=(e)=>{
this.setState({
[e.target.name]: e.target.value
})
}
addItem=(e)=>{
e.preventDefault()
if (this.state.task!==""){
this.setState({
items:[...this.state.items,this.state.task],
task:''
})
}
}
removeItem=(index)=>{
const remainingItems = this.state.items.filter(j => {
return j !== index
})
this.setState({
items: remainingItems
})
};
render() {
return (
<div>
<form>
<input type='text' name="task"onChange={this.onChangeHandler} value={this.state.task} placeholder='Enter Task'/>
<button type='submit' onClick={this.addItem}>Add Task</button>
</form>
<Lists items={this.state.items}
delete={this.removeItem}/>
</div>
)
}
}
export class Lists extends Component {
removeItems=(index)=>{
this.props.delete(index)
}
render() {
return (
<div>
{this.props.items.map((item,index)=>
<li className="Lists" key={index}>{item}
<button type='button' onClick={this.removeItems(index)}>Remove</button>
</li>)}
</div>
)
}
}
Do you even happen to have any items to delete here or the list comes up empty? Delete function itself looks fine but you have couple of problems here.
Don't use index as a key. In case you're reordering or deleting (which you are doing) an array of items, you can run into a lot of issues. Here's a good article: https://medium.com/#vraa/why-using-an-index-as-key-in-react-is-probably-a-bad-idea-7543de68b17c
Probably the error is with this since you're deleting key which, since it's an iterator, is reassigned to another element when array repopulates itself. Change iterator to some other unique identifier for each element.
You're calling removeItems method as soon as it's set. If you have invoked methods (with ()) inside return of render, it will be executed immediately on each refresh. That's why I'm asking do you have anything to delete at all since, if delete function is okay written, this would probably delete all items as soon as they are added.
Best method would be to use dataset. To each element you can add dataset like this:
data-item-id={some-id} and you can fetch it inside your method from the fired event like this const clickedId = event.currentTarget.dataset.someId. Note that dataset in the element must be written like-this, and it's rewritten automatically when fetching it into camelCase (likeThis). Then you can use this index to target the element you want inside the array and delete it.
Note that the iterator issue still applies, and you need a different unique identifier.
Let me know if you need further explanation.
You can delete the current item using splice method.
removeItem = index=> {
let newArray = this.state.items;
newArray.splice(index, 1);
this.setState({
items: newArray
});
};
It would be better to use onClick for removing item like this :
<button type='button' onClick={()=>this.removeItems(index)}>Remove</button>
Hope this helps.
I prefer to pass item that i would like to remove, index can be decieving becouse it changes.
Find index by unique key, i use item.id as unique key.
removeItem = item => {
const items = this.state.items;
// if using lodash i use findIndex
const index = _.findIndex(items, i => i.id === item.id)
// if plain js
const index = items.findIndex(i => i.id === item.id)
items.splice(index, 1);
this.setState({
items
});
};
I hope you can help.
I can't remember where I got the the snippet of code in the deleteHandler function. It deletes the relevant listdata item from the JSON array and re-renders as expected. I just don't understand what it's doing. Is it specific React syntax? Is it rudimentary stuff that I am oblivious to?
I know the state.listdata.splice(id, 1); line gets the current JSON object, but what does the arrow function do? What is being returned? I'm quite baffled by it.
Any help is much appreciated.
var AppFront = React.createClass({
getInitialState:function(){
return{
listdata: [
{"id":1,"name":"Push Repo","description":"Job No 8790","priority":"Important"},
{"id":2,"name":"Second Note","description":"Job No 823790","priority":"Important"}
]
}
},
deleteHandler: function(e,id){
this.setState(state => {
state.listdata.splice(id, 1);
return {listdata: state.listdata};
});
},
render: function(){
var listDataDOM = this.state.listdata.map((item,index) => {return (<li key={item.id}>
{item.name}
<button onClick={()=>this.deleteHandler(item.id)}>delete</button>
</li>)});
return(
<div>
<h1>To-do List</h1>
<ul>
{listDataDOM}
</ul>
</div>
);
}
});
ReactDOM.render(<AppFront />,document.getElementById("container"));
<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>
1) About setState
setState function in React looks something like that :
setState(partialState, callback)
Where partialState may be : object , function or null.
In your particular case you use function, which returns an object of state variables.
setState(function(state){ return {some:data} })
and with arrow func (es6) , the same will look like
setState(state=> { return {some:data} })
in yout particular case arrow func used just for short
2) About splice
In handler, you use JS func splice() to remove element from state's array;
But it is bad practice, because it mutates the state of component.And It will cause bugs, problems and unpredictable behavior. You shouldn't mutate your state!
To avoid that you can copy your array through slice(), because slice returns new array.
var newArray = state.listdata.slice()
newArray.splice(index, 1);
3) About deleteHandler and data structure
deleteHandler doesnt work properly, and works only for first position.And if your data will look like that:
listdata: [
{"id":52,"name":"Push Repo","description":"Job No 8790","priority":"Important"},
{"id":11,"name":"Second Note","description":"Job No 823790","priority":"Important"}
]
It will not work at all
For proper result , you should change deleteHandler to this:
deleteHandler: function(e,id){
//find index of element
var index = this.state.listdata.findIndex(e=>e.id==id);
//copy array
var newAray = this.state.listdata.slice();
//delete element by index
newAray.splice(index, 1);
this.setState({listdata: newAray});
},
and button
<button onClick={e=>this.deleteHandler(e,item.id)}>delete</button>
> JSBIN example
or you can delete by index
deleteHandler: function(e,index){
//copy array
var newAray = this.state.listdata.slice();
//delete element by index
newAray.splice(index, 1);
this.setState({listdata: newAray});
},
<button onClick={e=>this.deleteHandler(e,index)}>delete</button>
> JSBIN example
In your AppFront component you have a state
{
listdata: [
{"id":1,"name":"Push Repo","description":"Job No 8790","priority":"Important"},
{"id":2,"name":"Second Note","description":"Job No 823790","priority":"Important"}
]
}
It represents initial data in your component. Every time you change state, your component gets rerendered.
You can change state by calling component's setState method
In deleteHandler
deleteHandler: function(e,id){
this.setState(state => {
// state.listdata - array of initial values,
state.listdata.splice(id, 1);
return {listdata: state.listdata}; // returns a new state
});
}
state.listdata.splice(id, 1) // removes an element with index == id from the array. You should not confuse listdata item.id and item index. In order for your code to work correctly you need to pass index in you deleteHandler.
<button onClick={()=>this.deleteHandler(index)}>delete</button>
Another thing is that you call deleteHandler only with one argument - item index so in your definition it should be
deleteHandler: function(index){
this.setState(state => {
// state.listdata - array of initial values,
state.listdata.splice(index, 1);
return {listdata: state.listdata}; // returns a new state
});
}
In your render method you iterate through this.state.listdata and return React.DOM nodes for each.
When you update component's state it gets rerendered and you see that item was deleted.
This code is written in es2015 so if it's new to you, it's better to start from reading something about new syntaxis.
state.listdata.splice(id, 1) deletes 1 element with the index equal to id from listdata array. For example if id equals to 0, then, after applying state.listdata.splice(id, 1), state.listdata will become:
listdata: [
{"id":2,"name":"Second Note","description":"Job No 823790","priority":"Important"}
]
And exactly this array will be returned by this arrow functions.
Keeping in mind, that splice method receives index as first argument, but you pass id property there, most probably you should change this code:
<button onClick={()=>this.deleteHandler(item.id)}>delete</button>
To:
<button onClick={()=>this.deleteHandler(index)}>delete</button>