Add To Cart functionality not working - Context API - React - javascript

I am following this tutorial here and followed it exactly, and I have even gone over github repo and code is the same. However when I click my button to add the product to the cart the state does not change. In react dev tools only state that changes is showHideCart changes from false to true - so it seems to only recognise that state change?
I want to be able to add my product to the basket once I click the button - can anyone see where the I have gone wrong? there are no errors in the console and the basket array just does not change it does not even say undefined which was what I thought would of been the case for no product showing up in basket.
Here is a link to a code sandbox
and below is code files where I believe the issues would be:
CartState.js
import { useReducer } from 'react'
import { CartContext } from './CartContext'
import {CartReducer} from './CartReducer'
import { SHOW_HIDE_CART, ADD_TO_CART, REMOVE_ITEM } from '../Types'
export const CartState = ({children}) => {
const initialState ={
showCart: false,
cartItems: [],
};
const [state, dispatch] = useReducer(CartReducer, initialState);
const addToCart = (item) => {
dispatch({type: ADD_TO_CART, payload: item})
};
const showHideCart = () => {
dispatch({type: SHOW_HIDE_CART})
};
const removeItem = (id) => {
dispatch({ type: REMOVE_ITEM, payload: id });
};
return (
<CartContext.Provider
value={{
showCart: state.showCart,
cartItems: state.cartItems,
addToCart,
showHideCart,
removeItem,
}}>
{children}
</CartContext.Provider>
)
};
CartReducer.js
import {ADD_TO_CART, REMOVE_ITEM, SHOW_HIDE_CART } from '../Types'
export const CartReducer = (state, action) => {
switch (action.type) {
case SHOW_HIDE_CART: {
return{
...state,
showCart: !state.showCart
}
}
case ADD_TO_CART: {
return {
...state,
cartItems: [...state.cartItems, action.payload],
}
}
case REMOVE_ITEM: {
return {
...state,
cartItems: state.cartItems.filter(item => item.id !== action.payload)
}
}
default:
return state
}
}
Product.js
import React, { useContext } from 'react'
import { QuantityButtonDiv } from './QuantityButtonDiv'
import {BasketItem} from './BasketItem'
import { CartContext } from '../context/cart/CartContext'
export const Product = ({product}) => {
const {addToCart } = useContext(CartContext)
return (
<div>
<div className="image-div">
<img style={{height: "100%", width: "100%"}} src={product.image}/>
</div>
<div className="details-div">
<h1>{product.title}</h1>
<span>
{product.description}
</span>
<span className="price">
£ {product.price}
</span>
<div className="stock-div">
{product.stock} in stock
</div>
<QuantityButtonDiv/>
<button onClick={() => addToCart(product)} className="button">
Add To Basket
</button>
</div>
</div>
)
}
ProductDetailsPAge.js
import React, { useContext } from 'react';
import '../styles/productDetailsPage.scss';
import img from '../assets/image.png'
import { QuantityButtonDiv } from '../components/QuantityButtonDiv';
import { Product } from '../components/Product';
import { CartContext } from '../context/cart/CartContext';
export const ProductDetailsPage = () => {
const products = [
{
id: 1,
title: "Waxed Cotton Hooded Jacket",
image: require("../assets/image.png"),
description: "The Drumming jacket in orange is finished with a water-repellent dry wax treatment that creates a love-worn look. It's made in the United Kingdom using organic cotton ripstop with a drawstring hood, underarm eyelets and buttoned flap front pockets. Shoulder epaulettes add a utilitarian twist, while a fly-fronted zip and snap-button closure keeps the overall look streamlined. Attach one of the collection's padded liners to the internal tab on cooler days.",
price: 650,
stock: 10,
}
]
return (
<div className="product-details-page">
{
products.map((product) => (
<Product
key={product.id}
product={product}
// title={product.title}
// image={product.image}
// description={item.description}
// price={item.price}
// stock={item.stock}
/>
))
}
</div>
)
}

There is currently an error on basketpage route
You are passing an object through CartContext:
<CartContext.Provider
value={{
showCart: state.showCart,
cartItems: state.cartItems,
addToCart,
showHideCart,
removeItem,
}}>
in BasketPage you are destructuring an array:
const [cart, setCart] = useContext(CartContext);//Typeerror here
Also, you aren't passing neither cart or setCart through that Context value object.
You need to first solve this error and further see if it works alright.

Issue was with showCart and showHideCart - once I got rid of these the cart was working.

Related

Why is my RemoveItem function not working? - Context API - React

I am building a basket ecommerce app and my add to cart function is working fine, however when I use my remove item function it does not work, in fact it just adds the same item to the cart again but then the total displays as NaN.
I am just using .filter to return a new array without the items that I want removed by using its id, so not sure how this is happening.
If you want to replicate what I am talking about in code sandbox just click the add to cart button - then click top right basket icon to go to basket page - then click the minus button to remove the item.
code sandbox here
code below:
CartReducer.js
import {ADD_TO_CART, REMOVE_ITEM} from '../Types'
export const CartReducer = (state, action) => {
switch (action.type) {
case ADD_TO_CART: {
return {
...state,
cartItems: [...state.cartItems, action.payload],
}
}
case REMOVE_ITEM: {
return {
...state,
cartItems: state.cartItems.filter((item) => item.id !== action.payload.id),
}
}
default:
return state
}
}
CartState.js
import { useReducer } from 'react'
import { CartContext } from './CartContext'
import {CartReducer} from './CartReducer'
import { SHOW_HIDE_CART, ADD_TO_CART, REMOVE_ITEM } from '../Types'
import {products} from '../../pages/ProductDetailsPage'
export const CartState = ({children}) => {
const initialState ={
// showCart: false,
products: products,
cartItems: [],
};
const [state, dispatch] = useReducer(CartReducer, initialState);
const addToCart = (item) => {
dispatch({type: ADD_TO_CART, payload: item})
};
const removeItem = (id) => {
dispatch({ type: REMOVE_ITEM, payload: id });
};
return (
<CartContext.Provider
value={{
products: state.products,
cartItems: state.cartItems,
addToCart,
removeItem,
}}>
{children}
</CartContext.Provider>
)
};
BasketItem.js
import React, { useContext } from 'react'
import image from '../assets/image.png'
// import { QuantityButtonDiv } from '../components/QuantityButtonDiv'
import plusButtonImage from '../assets/vector+.png'
import subtractButtonImage from '../assets/vector.png'
import { CartContext } from '../context/cart/CartContext'
export const BasketItem = ({item}) => {
const { cartItems, removeItem } = useContext(CartContext);
return (
<div className="basket-item">
<div className="title-div">
<span>
{item.title}
</span>
</div>
<div className="image-div">
<img style={{height: "100%", width: "100%"}} src={image}/>
</div>
<div className="price-div">
<span>
£{item.price}
</span>
</div>
<div className="basket-quantity-div">
<button onClick={() => removeItem(item.id)} className="subtract-btn">
<img src={subtractButtonImage}/>
</button>
<span className="quantity-value">
{cartItems.length}
</span>
<button className="add-btn">
<img src={plusButtonImage}/>
</button>
</div>
<div className="total-div">
£{cartItems.reduce((amount, item) => item.price + amount, 0)}
</div>
</div>
)
}
Are you double checked removeItem function?
In the dispatch you are sending { payload: id } and then the reducer get the value from payload.id. If you want to manage the reducer in this way you have to send in the payload an object { id: id } or shorthanded { id }

How to update quantity when item is added to cart rather than add same item twice - Context API - Reaxt

I only have one item for my basket app, when I add it multiple times it adds multiple different basketItems all with same id and with same quantity so for example 3 items all of quantity 3. What I want however is for there just to be one item with a quantity of 3.
I also want to be able to remove the item from cart by quantity - so one at a time. however currently when I click to remove from cart it removes it entirely from cart even if quantity in cart is more than 1.
How do I do this?
code sandbox [here] (https://codesandbox.io/s/silly-jepsen-ix3dd?file=/src/pages/ProductDetailsPage.js&resolutionWidth=584&resolutionHeight=696)
code below:
CartReducer.js
import {ADD_TO_CART, CHANGE_CART_QUANTITY, DECREASE, REMOVE_FROM_CART} from '../Types'
export const CartReducer = (state, action) => {
switch (action.type) {
case ADD_TO_CART: {
return {
...state,
cart: [...state.cart, { ...action.payload, qty : 1}]
};
}
case REMOVE_FROM_CART: {
return {
...state,
cart: state.cart.filter((c) => c.id !== action.payload.id,)
};
}
default:
return state
}
}
CartContext.js
import { createContext, useContext, useReducer } from "react";
import { CartReducer } from "./CartReducer";
import { products } from "../../pages/ProductDetailsPage";
const Cart = createContext();
const Context = ({ children }) => {
const [state, dispatch] = useReducer(CartReducer, {
products: products,
cart: [],
});
// const [productState, productDispatch] = useReducer(productReducer, {
// byStock: false,
// byFastDelivery: false,
// byRating: 0,
// searchQuery: "",
// });
// console.log(productState);
return (
<Cart.Provider value={{ state, dispatch }}>
{children}
</Cart.Provider>
);
};
export const CartState = () => {
return useContext(Cart);
};
export default Context;
BasketItem.js
import React, { useContext } from 'react'
import image from '../../assets/image.png'
// import { QuantityButtonDiv } from '../components/QuantityButtonDiv'
import plusButtonImage from '../../assets/vector+.png'
import subtractButtonImage from '../../assets/vector.png'
import { CartState } from '../../context/cart/CartContext'
import { ADD_TO_CART, DECREASE, REMOVE_FROM_CART } from '../../context/Types'
import CustomizedSelect from '../SelectInput'
export const BasketItem = ({item}) => {
// const { cartItems, removeItem } = useContext(CartContext);
const {
state: { cart },
dispatch,
} = CartState();
return (
<div className="basket-item">
<div className="title-div">
<span>
{item.title}
</span>
</div>
<div className="image-div">
<img style={{height: "100%", width: "100%"}} src={image}/>
</div>
<div className="price-div">
<span>
£{item.price}
</span>
</div>
<div className="basket-quantity-div">
<button onClick={() => dispatch({
type: REMOVE_FROM_CART,
payload: item,
})} className="subtract-btn">
<img src={subtractButtonImage}/>
</button>
<span className="quantity-value">
{cart.length}
</span>
<button onClick={() => dispatch({
type: ADD_TO_CART,
payload: item,
})} className="add-btn">
<img src={plusButtonImage}/>
</button>
</div>
<div className="total-div">
£{cart.reduce((amount, item) => item.price + amount, 0)}
</div>
</div>
)
}
Product.js
import React, { useContext, useState } from 'react'
import image from '../../assets/image.png'
import { QuantityButtonDiv } from '../QuantityButtonDiv'
import {BasketItem} from '../basketItem/BasketItem'
import { CartContext, CartState } from '../../context/cart/CartContext'
import { ADD_TO_CART, REMOVE_FROM_CART } from '../../context/Types'
export const Product = ({product}) => {
// const {addToCart, cartItems, removeItem } = useContext(CartContext)
const { state: {cart}, dispatch } = CartState();
const [stockCount, setStockCount] = useState(10)
return (
<div>
<div className="image-div">
<img style={{height: "100%", width: "100%"}} src={image}/>
</div>
<div className="details-div">
<h1>{product.title}</h1>
<span>
{product.description}
</span>
<span className="price">
£ {product.price}
</span>
<div className="stock-div">
{stockCount} in stock
</div>
<QuantityButtonDiv/>
{cart.some((p) => p.id === product.id) ? (
//checking to see if item is in cart if so remove from cart button appears
<button onClick={() => dispatch({
type: REMOVE_FROM_CART,
payload: product,
})} className="remove-button">
Remove From Cart
</button>
) : (
<></>
)}
<button onClick={() => {dispatch({
type: ADD_TO_CART,
payload: product,
}); setStockCount(stockCount-1)}} disable={stockCount <= 0} className="add-to-cart-button">
{stockCount === 0 ? "Out Of Stock" : "Add To Cart"}
</button>
</div>
</div>
)
}
before adding new product to cart, you need to check if the product is already in the cart. You need to check it in ADD_TO_CART case.
in REMOVE_FROM_CART case, check if the quantity is more than 1. If its more than 1, don't remove it.

TypeError: addToCart is not a function - React/Redux

I am following this tutorial on youtube to implement redux for my ecommerce project. I have followed exactly how the instructor does it however when trying to add a product to the cart I get this error "TypeError: addToCart is not a function".
The only difference between my setup and the tutorial is that I am passing data into my card to display products using props whereas the tutorial renders the product info using redux.
I have looked at many posts online about this error but none of them seem to apply to me as all the potential workarounds I have tried do not works so far.
All relevant code is below.
Card.js
import React, {useState} from 'react';
import 'antd/dist/antd.css';
import { Card, Avatar, Button, Modal } from 'antd';
import { EditOutlined, EllipsisOutlined, PlusCircleTwoTone, SettingOutlined } from '#ant-design/icons';
import {connect} from 'react-redux';
import {addToCart} from '../Redux/Shopping/ShoppingActions'
const { Meta } = Card;
function Cardo(props, {addToCart}) {
//Setting variables up to use for useState so to manage state of modal
//Default state is false so not to be visible
const [isModalVisible, setIsModalVisible] = useState(false);
const showModal = () => {
setIsModalVisible(true);
};
const handleOk = () => {
setIsModalVisible(false);
};
const handleCancel = () => {
setIsModalVisible(false);
};
//^^^All the const's above will be called below within the card or modal to manage the state of the modal
return (
<div className="card">
<Card
style={{ width: "340px", textAlign: 'center' }}
cover={<img className="card-cover" src={props.image}/>}
actions={[
// <SettingOutlined key="setting" />,
// <EditOutlined onClick={showModal} key="edit" />,
<EllipsisOutlined onClick={showModal} key="ellipsis" />,
]}
>
<Meta
avatar={<Button className="card-button" onClick={() => addToCart(props.id)} type="primary" shape="circle"><PlusCircleTwoTone /></Button>}
title={props.header}
description={props.price}
/>
</Card>
<Modal title={props.header} visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
<p>{props.description}</p>
</Modal>
</div>
)
}
const mapDispatchToProps = (dispatch) => {
return{
addToCart: (id) => dispatch(addToCart(id)),
}
}
export default connect(null, mapDispatchToProps)(Cardo)
ShoppingActions.js
import * as actionTypes from './ShoppingTypes';
export const addToCart = (itemID) => {
return{
type: actionTypes.ADD_TO_CART,
payload: {
id: itemID
},
};
};
export const removeFromCart = (itemID) => {
return{
type: actionTypes.REMOVE_FROM_CART,
payload: {
id: itemID
},
};
};
export const adjutQty = (itemID, value) => {
return{
type: actionTypes.ADJUST_QTY,
payload: {
id: itemID,
qty: value,
},
};
};
export const loadCurrentItem = (item) => {
return{
type: actionTypes.LOAD_CURRENT_ITEM,
payload: item,
};
};
ShoppingReducer.js
import * as actionTypes from './ShoppingTypes';
import data from '../../Data/MenuData';
const INITIAL_STATE = {
products: data,//(id, title, description, price, img)
cart: [], //(id, title, description, price, img, qty)
currentItem: null,
}
//reducer is just function that takes in state and action - action is part that gets dispatched which contains a type
const shopReducer = (state = INITIAL_STATE, action) => {
switch(action.type){
case actionTypes.ADD_TO_CART:
//get items data from products array
const item = state.products.find((prod) => prod.id === action.payload.id);
//we need to check if item is in cart already
const inCart = state.cart.find((item) => item.id === action.payload.id ? true : false);
return{
//we spread the state first so not to lose current or all the products
...state,
//inCart we check if it is in cart and that return true - if so map through cart and find that id
cart: inCart ? state.cart.map((item) =>
item.id === action.payload.id
//Then spread all of data inside and change quantity if needed
? {...item, qty: item.quantity + 1} : item
) //if not in cart then spread the array and add the item and quantity to state of cart
: [...state.cart, { ...item, qty: 1}],
};
case actionTypes.REMOVE_FROM_CART:
return{
...state,
//this filters through array and deletes item we want to remove
cart: state.cart.filter(item => item.id !== action.payload.id)
};
case actionTypes.ADJUST_QTY:
return{
...state,
//if i find id in cart I want to recreate object by spreading current item and setting qty set to original qty - else return item
cart: state.cart.map((item) => item.id === action.payload.id ? {...item, qty: action.payload.qty} : item)
};
case actionTypes.LOAD_CURRENT_ITEM:
return{
...state,
currentItem: action.payload,
};
default:
return state;
}
}
export default shopReducer;
function Cardo(props, {addToCart}) {
here lies the error addToCart is a property of props, so it should look like this
function Cardo(props) {
const {addToCart} = props
This is not how you take props
function Cardo(props, {addToCart})
If you want to make all props you just simply make
function Cardo(props)
and then use props.addToCart
but if you want to do not use props.addToCart you can make it:
function Cardo(props: { addToCart })
so now all items that you pass into {} like { addToCart, anotherProp, thirdProp }
will be from props
you can also use this way:
function Cardo(props)
and under it:
const { addToCart, anotherProp, thirdProp } = props;
and then just use normal addToCart

How to filter products in React

Good day,
i am quite a newbie at react and i am making my first ecommerce website. My question is: How do i filter my products by size? I can't really think of the logic. Thank you in advance for your answers. I have tried to use Redux also, but had no succes it gave me the following error: ×
TypeError: Cannot read property 'items' of undefined
code product Cards:
import React,{useState,useEffect} from 'react'
import {Link} from 'react-router-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import {Library} from '#fortawesome/fontawesome-svg-core'
import {faShoppingBasket} from '#fortawesome/free-solid-svg-icons'
import {useDispatch, useSelector} from 'react-redux'
import { listProduct, Filterproducts } from '../../actions/productActions'
function Product(props){
//default value is an array, because we've got data in an array
const [qty,setQty] = useState(1)
const productList = useSelector(state=>state.productList)
const{products,loading,error}=productList
const dispatch = useDispatch()
// handlefilter
useEffect(()=> {
dispatch(listProduct())
}, [])
// handle cart adding
const handleAddToCart = ()=> {
props.history.push('/cart/' + props.match.params.id + "?qty" +qty)
}
return(
// Check the loading before rendering products
loading? <div><h1 className="load">loading...</h1></div> :
error?<div>{error}</div>:
<ul className="products">
{products.map(product=> (
<li key={product.id} className="product">
<Link to={"/product/" + product.id}><div className="img" style={{background: `url(${product.img})`, backgroundSize: 'cover'}}></div></Link>
{/* LOOK OUT FOR TYPOS IN ROUTIING dont put':' after /, this only applies
when routing because the ": " implies for a parameter
In this case you can directly access product.id */}
<Link to={"/product/" + product.id}><h1>{product.name}</h1></Link>
<p> <small>€</small>{product.price}</p>
<div>size: {product.size}</div>
{product.qty > 0 ? <div><button onClick={handleAddToCart}>Add to cart</button> <div>{product.qty} left</div></div> : <div>out of stock</div> }
</li>
)
)}
</ul>
)
}
export default Product
code ProductActions:
import Axios from 'axios'
import {
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
FILTER_PRODUCTS_BY_PRICE,
FILTER_PRODUCTS_BY_SIZE} from '../constants/productConstants'
const listProduct = () => async(dispatch)=> {
try{
dispatch({type:PRODUCT_LIST_REQUEST})
fetch('http://localhost:5000/')
.then(res=> res.json())
.then(data=> dispatch({type: PRODUCT_LIST_SUCCESS, payload:data}) )
}
catch(error){
dispatch({type: PRODUCT_LIST_FAIL, payload:error.message})
}
}
// DETAILSPRODUCT
//we need to have a server in order to display the products
//it should get another link which contains the id of the product
const detailsProduct = (productId) => async(dispatch) => {
try{
dispatch({type: PRODUCT_DETAILS_REQUEST, payload: productId});
fetch('http://localhost:5000/' + productId)
.then(res=> res.json())
.then(data=> dispatch({type: PRODUCT_DETAILS_SUCCESS, payload:data}) )
}
catch(error){
dispatch({type: PRODUCT_DETAILS_FAIL, payload: error.message})
}
}
// Filter products
const Filterproducts = (products,size) => (dispatch) => {
return dispatch({
type:FILTER_PRODUCTS_BY_SIZE,
payload: {
size:size,
items:size === '' ? products : products.filter(a=> a.indexOf(size.toUpperCase()) >= 0)
}
})
}
export { listProduct, detailsProduct, Filterproducts }
code productReducers:
//two params are being accepted in the reducer func
//* state
import {
PRODUCT_DETAILS_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
FILTER_PRODUCTS_BY_SIZE,
FILTER_PRODUCTS_BY_PRICE } from "../constants/productConstants";
//*action
function producListReducer(state = {products: [], filteredItems: [], size: ''}, action){
switch (action.type){
// case is like the if statement
//getting product
case PRODUCT_LIST_REQUEST:
return{loading: true};
// when products are loaded
case PRODUCT_LIST_SUCCESS:
return{loading:false, products: action.payload};
//when err occurs
case PRODUCT_LIST_FAIL:
return{loading: false, error: action.payload};
case FILTER_PRODUCTS_BY_SIZE:
return{...state, filteredItems: action.payload.items, size: action.payload.size}
default:
return state
}
}
function productDetailsReducer(state = {product: {}}, action){
switch (action.type){
// case is like the if statement
//getting product
case PRODUCT_DETAILS_REQUEST:
return{loading: true};
// when products are loaded
case PRODUCT_DETAILS_SUCCESS:
return{loading:false, product: action.payload};
//when err occurs
case PRODUCT_DETAILS_FAIL:
return{loading: false, error: action.payload};
default:
return state
}
}
export { producListReducer,productDetailsReducer }
The error message means that you are trying to access the key "items" of an inexisting object. The only you are doing this in the posted code is in the productReducers, so I recomment you to check the function "Filterproducts" where you return the payload.
Got the filter function working, but i dont know how to apply it.
The Searchbar Component:
import React from 'react'
// Function Searchbar
//Looks for products
function Search(props){
return(
<input
type="search"
className="search"
placeholder={props.placeholder}
onChange={props.handleInput}/>
)
}
export default Search
The product Component:
return(
// Check the loading before rendering products
loading? loading... :
error?{error}:
{products.map(product=> (
<li key={product.id} className="product">
<Link to={"/product/" + product.id}><div className="img" style={{background: `url(${product.img})`, backgroundSize: 'cover'}}></div></Link>
{/* LOOK OUT FOR TYPOS IN ROUTIING dont put':' after /, this only applies
when routing because the ": " implies for a parameter
In this case you can directly access product.id */}
<Link to={"/product/" + product.id}><h1>{product.name}</h1></Link>
<p> <small>€</small>{product.price}</p>
<div>size: {product.size}</div>
{product.qty > 0 ? <div><button onClick={handleAddToCart}>Add to cart</button> <div>{product.qty} left</div></div> : <div>out of stock</div> }
</li>
)
)}
</ul>
)
The Searchbar Functions in Store.js:
import React,{Component, useState} from 'react'
import Product from '../components/product'
import Nav from '../components/nav'
import Footer from '../components/footer'
import Searchbar from '../components/searchbar'
import Filter from '../components/filter'
class Store extends Component{
constructor(){
super()
this.state = {
products: [],
SearchProduct: ''
}
}
componentDidMount(){
fetch('http://localhost:5000/')
.then(res=> res.json())
.then(data=>this.setState({products:data}) )
}
render(){
let filteredProducts = this.state.products.filter((product)=> {
return product.name.toLowerCase().includes(this.state.SearchProduct.toLowerCase())
})
return (
<div>
<Nav/>
<div className="store">
<div className="title">
<h1>Store</h1>
</div>
<aside>
<Searchbar
placeholder="Search"
handleInput={(e)=> {
this.setState({SearchProduct: e.target.value})
}
}
/>
<ul>
<h2>Categories</h2>
<li>Women</li>
<li>Men</li>
<li>Clothing</li>
<li>Equipment</li>
</ul>
</aside>
<Filter/>
<Product filter={filteredProducts}/>
</div>
<Footer/>
</div>
)
}
}
export default Store

How to delete a specific element from an array in the redux store

I am new to to redux and react. Still doing simple tutorials. I managed to create 2 simple components; one that outputs on the screen (as a list) whatever is in the array in the redux store, and the other component contains a button and a textfield which basically adds to that array in the store.
I would like to add a feature that will enable me to delete a specific entry in the list depending on what the user clicked on. I am thinking of creating a <button> next to each <li> tag that gets rendered as it loops through the array, and these buttons will correspond to the respective list elements. But I'm not sure how to do that.
I've tried creating a button when each <li> tag gets created but I was getting an error on the console stating that each element in a list needs a unique ID. I then decided to create another array in my store called buttons which will contain a unique id as well as the id of the list but it got out of hand. I think I might be overcomplicating this. This is what I have at the moment:
Components:
List.jsx (responsible for outputting the list)
import React from 'react'
import { connect } from "react-redux";
const ListComp = ({ lists }) => (
<div>
<ul>
{console.log(lists)}
{lists.map( element => (
<li key={element.id}>
{element.titleToBeAddedToList}
</li>
))}
</ul>
</div>
)
const mapStateToProps = state => {
return {
lists: state.lists
};
}
const List = connect(mapStateToProps)(ListComp)
export default List;
SubmitButton.jsx (responsible for outputting the button and textfield)
import React from 'react'
import { connect } from "react-redux";
import uuidv1 from "uuid";
import { addList } from "../actions/index";
import { addButton } from "../actions/index"
function mapDispatchToProps(dispatch){
return {
addlist: article => dispatch(addList(article)),
addbutton: idOfButton => dispatch(addButton(idOfButton))
};
}
class Submit extends React.Component{
constructor(){
super();
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.id]: event.target.value });
}
handleSubmit(event) {
event.preventDefault();
const {titleToBeAddedToList} = this.state;
const id = uuidv1();
const button_id = uuidv1();
//Dispatching the action:
this.props.addlist({ titleToBeAddedToList, id });
this.props.addbutton({id, button_id});
//Once we've dispatched an action, we want to clear the state:
this.setState({ titleToBeAddedToList: "" });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="titleToBeAddedToList"
onChange={this.handleChange}
/>
</div>
<button type="submit" className="btn btn-success btn-lg">
SAVE
</button>
</form>
);
}
}
const SubmitButton = connect(null, mapDispatchToProps)(Submit)
export default SubmitButton;
Reducers:
const initialState = {
lists: [],
buttons: []
};
function rootReducer (state = initialState, action) {
if(action.type === "ADD_LIST" ){
return Object.assign({}, state, {
lists: state.lists.concat(action.payload)
});
} else if(action.type === "ADD_BUTTON"){
return Object.assign({}, state, {
buttons: state.lists.concat(action.payload)
});
} else if(action.type === "DELETE_FROM_LIST"){
//.....//
}
return state;
}
export default rootReducer;
Action:
export function addList(payload) {
return { type: "ADD_LIST", payload }
};
export function addButton(payload){
return {type: "ADD_BUTTON", payload }
}
export function deleteList(payload){
return { type: "DELETE_FROM_LIST", payload }
}
Store:
import { createStore } from "redux";
import rootReducer from "../reducers/index";
const store = createStore(rootReducer);
export default store;
You can use Math.random() as an unique key identifier, if the button is click it will call action deleteItem with the ID, action is bound to reducer pass on the ID, you can then use the ID to indentify elements and remove it in the list.
import React from 'react'
import { connect } from "react-redux";
import { deleteItem } from './actions';
const ListComp = ({ lists }) => (
<div>
<ul>
{console.log(lists)}
{lists.map( element => (
<li key={Math.random()} key={element.id}>
{element.titleToBeAddedToList}
<button onClick={() => deleteItem(element.id)}>X</button>
</li>
))}
</ul>
</div>
)
const mapStateToProps = state => {
return {
lists: state.lists
};
}
const List = connect(mapStateToProps, {deleteItem})(ListComp) // Make it available to component as props
export default List;
Action:
export function deleteElement(id) {
return function(dispatch) {
return dispatch({type: "DELETE_FROM_LIST", payload: id})
}
}
Reducer:
case 'DELETE_FROM_LIST': {
const id = action.payload;
return {
...state,
list: state.list.filter(item => item.id !== id)
}
}
else if (action.type === "DELETE_FROM_LIST") {
return Object.assign({}, state, {
buttons: state.lists.filter(item => (item.id !==action.payload))
});
}
you can use filter() for delete.
This is a minimal working react-redux example containing all the pieces to delete an item from an array in redux store.
// reducer.js
const reducer = (state, action) => {
switch (action.type) {
case 'DELETE':
return state.filter(item => (
item.id !== action.payload.id
))
default: return state;
}
}
// Item.js
const Item = ({id, onClick, label}) => (
<li>
{label}
<button onClick={ () => onClick(id) }>
delete
</button>
</li>
)
// ListContainer.js
const mapStateToProps = state => ({ items: state })
const ListContainer = ReactRedux.connect(mapStateToProps)(class extends React.Component {
handleDelete = id => {
const { dispatch } = this.props;
dispatch({ type: 'DELETE', payload: { id } })
}
render() {
const { items } = this.props;
return items.map(({id, label}) => (
<Item
label={label}
id={id}
onClick={this.handleDelete}
/>
))
}
})
// Main.js
const initialState = [
{ id: 1, label: 'item 1' },
{ id: 2, label: 'item 2' },
{ id: 3, label: 'item 3' },
{ id: 4, label: 'item 4' }
]
const store = Redux.createStore(reducer, initialState);
class App extends React.Component {
render(){
return (
<ReactRedux.Provider store={store}>
<ListContainer />
</ReactRedux.Provider>
);
}
}
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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.1/react-redux.js"></script>
<div id="root"></div>

Categories