I am learning react and came across a tutorial where a delete button is added in the JSX and a function has been defined to execute the event. However, I am confused what does the code below do.
const deleteList=this.state.list.filter(item=>item.objectID!==id);
In particular I am confused what does the below logic mean:
(item=>item.objectID!==id);
this.state.list.filter(item=>item.objectID!==id);
This part loops over all items in list, and returns a new array containing only items that match the condition item.objectID!==id
This is a common syntax to delete one element of a list.
See documentation of filter method.
It is similar to
function(item) {
return item.objectID!==id
}
Basically filter out all elements where id is not equal to item.objectID
For Better understanding I am explaining it with an example.
see the below code:-
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
list: [
{ id: '1', age: 42 },
{ id: '2', age: 33 },
{ id: '3', age: 68 },
],
};
}
onRemoveItem = id => {
this.setState(state => {
const list = state.list.filter(item => item.objectID !== id);
return {
list,
};
});
};
render() {
return (
<div>
<ul>
{this.state.list.map(item => (
<li key={item.objectID}>
The person is {item.age} years old.
<button
type="button"
onClick={() => this.onRemoveItem(item.objectID)}
>
Remove
</button>
</li>
))}
</ul>
</div>
);
}
}
export default App;
Above code we have React state array of objects (i.e. objectid and age).
while defining onRemoveItem methods id is the parameter.
when onRemoveItem methods is called on the button click event an item.objectID is passed as parameter.
onRemoveItem = id => {
this.setState(state => {
const list = state.list.filter(item => item.objectID !== id);
Here we filter the item from the React state array whose objectid is id and id is nothing but the objectid which is passed as a parameter on the onRemoveItem method calling.
Related
I have created a basic single page app, on initial page there is some dummy data and on click of each item I direct user to individual details page of that item. I wanted to implement comment and delete comment functionality which I successfully did but now when I comment or delete the comment it doesn't only happen at that individual page but in every other page too. Please see the sandbox example for better clarify.
https://codesandbox.io/s/objective-feistel-g62g0?file=/src/components/ProductDetails.js
So once you add some comments in individual page, go back and then click to another products, apparently you will see that the comments you've done in other pages are also available there. What do you think causing this problem ?
The same state being reused by all the different pages.
Try to load dynamically load reducers for each page/router differently to use distinct state values.
You can start from here
Redux modules and code splitting
I found my own logical solution. You probably might find a better solution but this works pretty well too. I thought of passing another property in the object with the params I get from url and then filter the comments by their url params. So that I could do filtering based on the url parameters and display the comments only made on that specific page.
So ProductDetails.js page should be looking like this:
import React, { useState, useEffect } from 'react';
import { Input, Button } from 'semantic-ui-react'
import { connect } from 'react-redux';
const ProductDetails = (props) => {
const [commentObject, setCommentObject] = useState({
text: "",
date: "",
id: ""
});
const clickHandler = () => {
if (!commentObject.text.trim()) {
return
}
props.addNewComment(commentObject)
setCommentObject({
...commentObject,
text: ""
})
console.log(commentObject.id);
}
useEffect(() => {
}, []);
return (
<div>
{props.posts ? props.posts.text : null}
{props.comments.filter(comment => {
return comment.postId === props.match.params.slug
}).map(({ text, id }) => {
return (<div key={id}>
<p>{text}</p>
<Button onClick={() => props.deleteComment(id)} >Delete comment</Button></div>)
})}
<Input value={commentObject.text}
onChange={comment => setCommentObject({ text: comment.target.value, date: new Date(), id: Date.now(), postId: props.match.params.slug })}
/>
<Button onClick={clickHandler} >Add comment</Button>
</div>
);
}
const mapStateToProps = (state, ownProps) => {
let slug = ownProps.match.params.slug;
return {
...state,
posts: state.posts.find(post => post.slug === slug),
}
}
const mapDispatchToProps = (dispatch) => {
return {
addNewComment: (object) => { dispatch({ type: "ADD_COMMENT", payload: { comment: { text: object.text, date: object.date, id: object.id, postId: object.postId } } }) },
deleteComment: (id) => { dispatch({ type: "DELETE_COMMENT", id: id }) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);
I have a component:
export default class Shop extends PureComponent {
state = {
search: "",
filters: [],
items: json
};
onFilterChange = ( event ) => {
const checkboxes = [...event.currentTarget.closest(".filter").getElementsByTagName("input")]
const filters = [];
checkboxes.map(checkbox => {
if (checkbox.checked) {
filters.push(checkbox.name);
}
});
this.setState({ filters }, this.filtredInput);
}
filtredInput() {
let items = json
if (this.state.filters.length !== 0) {
items = items.filter(element => this.state.filters.every(key => element[key]));
}
if (this.state.search.length !== 0) {
items = items.filter(word =>
word.name.toLocaleLowerCase().indexOf(this.state.search.toLocaleLowerCase()) !== -1
)
}
this.setState( {items} )
}
onSearchChange = ( {currentTarget} ) => {
const search = currentTarget.value
this.setState({ search }, this.filtredInput() )
}
render() {
return (
<div>
<div className="navigation">
<Filter
onFilterChange={this.onFilterChange}
/>
<Search
onSearchChange={this.onSearchChange}
/>
</div>
<Filtered
items={this.state.items}
updateShoppingBasket={this.updateShoppingBasket}
/>
</div>
)
}
}
Help to organize the logic so that both the search and the filter work simultaneously. Individually, everything works fine. But in the current version, the search works as if with a delay (apparently, the code works before the state is set), but I'm not sure that there are no other errors. How to organize the logic of the filter + search correctly in React?
You can make this much easier on yourself by thinking about the data differently. If you have it as a requirement that you always store the latest filtered data, then this won't work. I have included a custom code example here (including components that the example depends on): https://codesandbox.io/s/hardcore-cohen-sg263?file=/src/App.js
I like to think about the three pieces of data that we need to store to perform our operation.
The search term
The list of filters
The list of items we want to filter and search within
We can download the list of items from an API if we need to, and this way we can ensure we never lose data by filtering and replacing.
export default class App extends React.Component {
/*
there are three things we store
1. the current search term
2. the current list of filters
3. the entire list of data we want to search and filter through
(this can start as an empty array and then be filled with data from an API)
*/
state = {
term: "",
filters: [],
list: [
{
color: "red",
name: "car"
},
{
color: "blue",
name: "plane"
},
{
color: "red",
name: "boat"
}
]
};
/* This handles toggling filters, when the filter is clicked it will check
the array and add it if it isn't there.
*/
toggleFilter = filter => {
if (this.state.filters.includes(filter)) {
this.setState({
filters: this.state.filters.filter(item => item !== filter)
});
} else {
this.setState({
filters: [...this.state.filters, filter]
});
}
};
updateTerm = term => {
this.setState({
term
});
};
/* selector function to filter a list of items */
applyFilters = list => {
if (this.state.filters.length === 0) {
return list;
}
return list.filter(item => this.state.filters.includes(item.color));
};
/* search function to filter for the search term */
applySearch = list => {
if (this.state.term === "") {
return list;
}
return list.filter(item => item.name.startsWith(this.state.term));
};
render() {
/* we can filter the list and then search through the filtered list */
const filteredItems = this.applyFilters(this.state.list);
const searchedItems = this.applySearch(filteredItems);
/* we pass the filtered items to the list component */
return (
<>
<Filters onChange={this.toggleFilter} />
<Search term={this.state.term} onChange={this.updateTerm} />
<List items={searchedItems} />
</>
);
}
}
Hope this helps build a different mental model for React. I intentionally avoided making the Filters a controlled component, but only to show you the filter in the render function. Always open to discussion. Let me know how it goes.
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.
Ok, so I'm so frustrated finding the right solution so I'm posting the problem here. Giving an answer would help me a lot, coz I'm stuck!
the state tree looks like this
this.state = {
itemList : [{
_id : 1234,
description : 'This the description',
amount : 100
}, {
_id : 1234,
description : 'This the description',
amount : 100
}],
}
The problems are :
can not update any specific key in the Object of the array according
to the _id
The previous state should remain intact
answered March 25 2018
This is how you would use setState and prevstate to update a certain attribute of an object in your data structure.
this.setState(prevState => ({
itemList: prevState.itemList.map(
obj => (obj._id === 1234 ? Object.assign(obj, { description: "New Description" }) : obj)
)
}));
answered Dec 12 2019 (REACT HOOKS)
import React, { useState } from 'react';
const App = () => {
const [data, setData] = useState([
{
username: '141451',
password: 'password',
favoriteFood: 'pizza',
},
{
username: '15151',
password: '91jf7jn38f8jn3',
favoriteFood: 'beans'
}
]);
return (
<div>
{data.map(user => {
return (
<div onClick={() => {
setData([...data].map(object => {
if(object.username === user.username) {
return {
...object,
favoriteFood: 'Potatos',
someNewRandomAttribute: 'X'
}
}
else return object;
}))
}}>
{JSON.stringify(user) + '\n'}
</div>
)
})}
</div>
)
}
to update state constructed like this you will have to find index of element you want to update, copy the array and change found index.
it's easier and more readable if you keep list of records as object, with id as a key and record as a value.
The only way to do this will be to copy itemList, modify it, and set the state to it.
update() {
let itemList = this.state.itemList.slice();
//update it
this.setState({ itemList });
}
Best way to update data into an array of objects
onChange={ (e) => this.setState({formData: { ...this.state.formData, 'plan_id': e.target.value}})}