React, Redux Deleting an item on click deletes all items - reactjs

I need fresh eyes on this. As I am slowly learning React and Redux i have run into a roadblock again.
/actions/items.js
export const DELETE_ITEM = "DELETE_ITEM"
export function deleteItem(id) {
return {
type: DELETE_ITEM,
id
}
}
/components/Item.jsx
export default class Item extends React.Component {
renderDelete = () => {
return <button onClick={this.props.onDelete}>x</button>
};
renderItem = () => {
const onDelete = this.props.onDelete
return (
<div onClick={this.edit}>
<span>{this.props.text}</span> {onDelete ? this.renderDelete() : null}
</div>
)
}
/components/Items.jsx
export default class Items extends React.Component {
handleOnDelete = (id) => {
this.props.dispatch(actions.deleteItem(id))
}
render() {
const {items, onEdit, onDelete } = this.props
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
id={item.id}
text={item.text}
onEdit={this.handleOnEdit}
onDelete={this.handleOnDelete.bind(null, item.id)}
/>
</li>
)}</ul>
);
}
}
export default connect(
state => ({
items: state.items
})
)(Items)
/reducers/items.js
case types.DELETE_ITEM:
const filteredItems = state.filter((item) => {
item.id !== action.id
});
return filteredItems
I'm not sure why clicking on x button to delete an item deletes all of them. Thanks in advance for the help

You do not return value in filter in your reducers.
Your should add return:
const filteredItems = state.filter((item) => {
return item.id !== action.id;
});
Or use short version, without brackets:
const filteredItems = state.filter((item) => item.id !== action.id);

Related

Why the list doesn't re-render after updating the store?

I started learning mobx and got stuck. Why when I change listItems, List doesn't re-render?
I have store:
export const listStore = () => {
return makeObservable(
{
listItems: [],
addItem(text) {
this.listItems.push(text);
}
},
{
listItems: observable,
addItem: action.bound
}
);
};
Component that adds text from input to store:
const store = listStore();
export const ListForm = observer(() => {
const [value, setValue] = useState();
return (
<>
<input type="text" onChange={e => setValue(e.target.value)} />
<button onClick={() => store.addItem(value)}>Add note</button>
</>
);
});
And I have a list component:
const store = listStore();
export const List = () => {
return (
<React.Fragment>
<ul>
<Observer>
{() => store.listItems.map(item => {
return <li key={item}>{item}</li>;
}
</Observer>
</ul>
<ListForm />
</React.Fragment>
);
};
I don't understand what's wrong. Looks like the list doesn't watch the store changing
codesandbox: https://codesandbox.io/s/ancient-firefly-lkh3e?file=/src/ListForm.jsx
You create 2 different instances of the store, they don't share data between. Just create one singleton instance, like that:
import { makeObservable, observable, action } from 'mobx';
const createListStore = () => {
return makeObservable(
{
listItems: [],
addItem(text) {
this.listItems.push(text);
}
},
{
listItems: observable,
addItem: action.bound
}
);
};
export const store = createListStore();
Working example

React and state

I would like your take on a specific implementation. I have a react app (no redux), the app has a shopping cart. The shopping cart is defined in the state in the App component and it is passed and used further down the tree in several components. E.g. I have a component called ShoppingCart, it displays the shopping cart, plus it has actions to add/remove/clear the cart.
My problem is updating the shopping cart state after performing an action on the shopping cart. E.g. when I call a function to clear the shopping cart, the state should be updated in the App component thus updating my component which is further down the tree. How would one implement these action functions (without redux)?
Code:
const App = () => {
const [cart, setCart] = useState({ lines: [], total: 0 });
return <ShoppingCart cart={cart} />;
}
const ShoppingCart = ({ cart }) => {
const onAddOne = l => {
// not sure how to update cart and update state
}
const onRemoveOne = l => {
// not sure how to update cart and update state
}
return (
<table>
{
cart.lines.map(l => <tr><td>{l.name}</td><td><button onClick={() => onAddOne(l)}>+</button><button onClick={() => onRemoveOne(l)}>-</button></td></tr>)
}
</table>
);
}
Thanks in advance for any tip.
Here you can use the useContext hook.
The idea is similar to redux.
So, what you can do is, first create a StateProvider, like in the example
import React, { createContext, useReducer, useContext } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
Similarly, create a Reducer for that, you can add more reducers, the example shown is to ADD ITEMS IN BASKET and REMOVE ITEMs FROM BASKET
export const initialState = {
basket: [],
user: null,
};
export const getBasketTotal = (basket) =>
basket?.reduce((amount, item) => item.price + amount, 0);
function reducer(state, action) {
switch (action.type) {
case "ADD_TO_BASKET":
return { ...state, basket: [...state.basket, action.item] };
case "REMOVE_ITEM":
let newBasket = [...state.basket];
const index = state.basket.findIndex(
(basketItem) => basketItem.id === action.id
);
if (index >= 0) {
newBasket.splice(index, 1);
} else {
console.warn("Cant do this");
}
return { ...state, basket: newBasket };
default:
return state;
}
}
export default reducer;
Go to your index.js file and wrap your file like this
<StateProvider initialState={initialState} reducer={reducer}>
<App />
</StateProvider>
And voila, while adding items to the basket use following code
const addtobasket = () => {
dispatch({
type: "ADD_TO_BASKET",
item: {
id: id,
title: title,
price: price,
rating: rating,
color: color,
},
});
};
I found a solution, however, I am not sure it is the correct way to do things:
const App = () => {
const onUpdateCart = (cart) => {
setCart({ ...cart });
}
const [cart, setCart] = useState({ lines: [], total: 0, onUpdateCart });
return <ShoppingCart cart={cart} />;
}
const ShoppingCart = ({ cart }) => {
const onRemoveLine = l => {
cart.lines = cart.lines.filter(l2 => l2 !== l);
cart.onUpdateCart(cart);
}
const onAddOne = l => {
l.amount++;
cart.onUpdateCart(cart);
}
const onRemoveOne = l => {
l.amount--;
cart.onUpdateCart(cart);
}
return (
<table>
{
cart.lines.map(l => (
<tr>
<td>{l.name}</td>
<td>
<button onClick={() => onAddOne(l)}>+</button>
<button onClick={() => onRemoveOne(l)}>-</button>
<button onClick={() => onRemoveLine(l)}>x</button>
</td>
</tr>)
)
}
</table>
);
};
The straight forward way to implement this is to pass down props to the child component that when called update the state.
Notice how all state business logic is in a central place .e.g in App component. This allows ShoppingCart to be a much simpler.
const App = () => {
const [cart, setCart] = useState({ lines: [], total: 0 });
const updateLineAmount = (lineIdx, amount) => {
// update the amount on a specific line index
setCart((state) => ({
...state,
lines: state.lines.map((line, idx) => {
if (idx !== lineIdx) {
return line;
}
return {
...line,
amount: line.amount + amount,
};
}),
}));
};
const onAddOne = (lineIdx) => {
updateLineAmount(lineIdx, 1);
};
const onRemoveOne = (lineIdx) => {
updateLineAmount(lineIdx, -1);
};
return (
<ShoppingCart cart={cart} onAddOne={onAddOne} onRemoveOne={onRemoveOne} />
);
};
const ShoppingCart = ({ cart, onAddOne, onRemoveOne }) => {
return (
<table>
{cart.lines.map((line, idx) => (
<tr key={idx}>
<td>{line.name}</td>
<td>
<button onClick={() => onAddOne(idx)}>+</button>
<button onClick={() => onRemoveOne(idx)}>-</button>
</td>
</tr>
))}
</table>
);
};

How to store inCart value in localStorage?

In my React ECommerce project, I have created Add to cart icon, when clicked disables, the icon is replaced with 'In Cart' text showing that the product is available in cart,but, the problem is when the browser is refreshed the 'In Cart' text disappears and cart icon is back. How to store it in localStorage so that the value remains even when refreshed. Following is the code for reference.
ProductList.js
<ProductConsumer>
{value => {
return value.products.map((product, key) => {
return <Product key={product.id} product={product} />;
});
}}
</ProductConsumer>
Product.js
export default function Product(props) {
// Taken from ProductList.js File
const {id, title, img, price, inCart} = props.product;
<ProductConsumer>
{(value) => (
<button className="cart-btn" disabled={inCart?true:false}
onClick={() => {value.addToCart(id)}}>
{ inCart ? (
<p className="text-capitalize mb-0" disabled>
{" "}
In Cart</p>
) : (
<i className="fas fa-shopping-cart"/>
)}
</button>
)}
</ProductConsumer>
}
context.js (addToCart(id) is defined)
const ProductContext = React.createContext();
class ProductProvider extends Component {
addToCart = (id) => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
// How to store this value 'product.inCart' in...
// ...localStorage and make it true until the product is
// removed
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return {
products: tempProducts,
cart: [...this.state.cart, product]
};
},
() => {
this.addTotal();
localStorage.setItem('myCart', JSON.stringify(this.state.cart));
});
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
As seen above when the product is in cart, cart icon gets disabled, I want to make inCart be true (even when the browser is refreshed) until and unless the product is removed from cart. Watch out for sandbox link: https://codesandbox.io/s/mobile-store-tdgwm
Above File ProductList.js is added and ProductConsumer is defined from context.js
In your setProducts function in context.js
setProducts = () => {
let tempProducts = [];
let activeProducts = JSON.parse(localStorage.getItem("myCart"));
storeProducts.forEach(item => {
let singleItem = { ...item };
if(activeProducts){
activeProducts.forEach(i => {
if (singleItem.id === i.id) {
singleItem = i;
}
});
}
tempProducts = [...tempProducts, singleItem];
});
this.setState(() => {
return { products: tempProducts };
});
};
Working codeSandbox - https://codesandbox.io/s/mobile-store-325x9

react component is no updateing the view after deleting an item

this the onDelete function which supposed to update the state and the item should disappear from the list once the delete button is pressed
onDelete = async (id) => {
const messages = await Api.deleteMessage(id);
const messageId = id;
const filterdMessages =this.state.messages.filter((message) => {
return message.id !== messageId
});
this.setState({
messages: [...filterdMessages]
});
}
and this what is being rendered
render() {
const {
messages
} = this.state;
const $messages = messages.map((message) => <MessageItem onDelete={this.onDelete} key={message.id} {...message} />);
console.log( $messages);
return (
<section className="messages">
<ul>
{$messages}
</ul>
</section>
)
}
this the messageItem
export default ({ id, body, onResolve, onDelete, license_plate }) => {
const onResolveClick = (event) => {
event.preventDefault();
onResolve(id);
};
const onDeleteClick = (event) => {
event.preventDefault();
onDelete(id);
};
return (
<li className="MessageItem">
<span> <b>{license_plate} </b>{body}</span>
<button onClick={onDeleteClick}>Delete</button>
</li>
)
}
You are using the key as key={message._id} but filtering by message.id.
// v Always undefined, the expression always false.
message.id !== messageId
const filterdMessages = this.state.messages.filter(message => {
// v message.id
return message._id !== messageId;
});
Should be onDelete={() => this.onDelete(message._id)}
There are 2 problems in your code
1) You are not passing the ID in argument
2) You are using _id instead of id for key prop
_onDelete = async (id) => {
await Api.deleteMessage(id);
const { messages } = this.state;
const filterdMessages = messages.filter((message) => message.id !== messageId);
this.setState({ messages: [...filterdMessages]});
}
render() {
const { messages } = this.state;
return (
<section className="messages">
<ul>
{
messages.map(message => (
<MessageItem
onDelete={() => this._onDelete(message.id)}
key={message.id}
{...message}
/>
))
}
</ul>
</section>
)
}

React-Redux: Cannot read property 'map' of undefined when deleting an item

I have an error after clicking the delete button saying:
Cannot read property 'map' of undefined.
I'm new in React Redux JS.
Please see my code below of my component reducers and actions:
Post.js
class Post extends Component {
constructor(){
super();
this.deletePost = this.deletePost.bind(this);
}
deletePost(postId){
this.props.deletePost(postId);
}
render(){
const postItems = this.props.posts.map(post => (
<div key={post.id} className="row">
<div className="container">
<h3>{post.title}</h3>
<p>{post.body}</p>
<button
onClick={() =>this.deletePost(post.id)}
className="btn btn-danger">
Delete
</button>
</div>
</div>
))
const divStyle = {
padding: '15px',
}
return (
<div style={divStyle}>
<PostForm />
<hr/>
{postItems}
</div>
)
}
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
})
export default connect(mapStateToProps, { fetchPosts, deletePost })(Post);
PostAction.js (Here is my delete action. I am using jsonplaceholder API post.)
export const deletePost = (postId) => dispatch => {
fetch('https://jsonplaceholder.typicode.com/posts/'+postId, {
method: 'DELETE',
})
.then(dispatch({
type: DELETE_POST,
payload: postId
}));
}
PostReducer.js (This is my reducer.)
case DELETE_POST:{
const newState = Object.assign([], state);`enter code here`
const filteredItems = newState.items.filter(items => {
return items.id != action.payload;
});
return filteredItems;
}
case DELETE_POST:{
const { items } = state;
const filteredItems = items.filter(items => {
return items.id != action.payload;
});
return {
...state,
items: [ ...filteredItems ]
};
}
Yes just replace
return filteredItems; to return { items: filteredItems }
But please can you check my code if it's correct. Thanks

Resources