So the problem I am currently facing is this. I have a Cart logic located in the CartContext. Everything works except the total number of prices it is displaying NAN. Here is the link to the CodeSandbox for a better understanding https://codesandbox.io/s/frosty-sound-5y7pg?file=/src/CartItem.js:1486-1494
import React from "react";
function getCartFromLocalStorage() {
return localStorage.getItem("cart")
? JSON.parse(localStorage.getItem("cart"))
: [];
}
const CartContext = React.createContext();
function CartProvider({ children }) {
const [cart, setCart] = React.useState(getCartFromLocalStorage());
const [total, setTotal] = React.useState(0);
const [cartItems, setCartItems] = React.useState(0);
React.useEffect(() => {
localStorage.setItem("cart", JSON.stringify(cart));
let newTotal = cart.reduce((total, cartItem) => {
return (total += cartItem.amount * cartItem.price);
}, 0);
newTotal = parseFloat(newTotal.toFixed(2));
setTotal(newTotal);
// cart items
let newCartItems = cart.reduce((total, cartItem) => {
return (total += cartItem.amount);
}, 0);
setCartItems(newCartItems);
}, [cart]);
// global functions
const removeItem = id => {
setCart([...cart].filter(item => item.id !== id));
};
const increaseAmount = id => {
const newCart = [...cart].map(item => {
return item.id === id
? { ...item, amount: item.amount + 1 }
: { ...item };
});
setCart(newCart);
};
const decreaseAmount = (id, amount) => {
if (amount === 1) {
removeItem(id);
return;
} else {
const newCart = [...cart].map(item => {
return item.id === id
? { ...item, amount: item.amount - 1 }
: { ...item };
});
setCart(newCart);
}
};
const addToCart = book => {
const { id, image, by, bookName,RegularPrice } = book;
const item = [...cart].find(item => item.id === id);
if (item) {
increaseAmount(id);
return;
} else {
const newItem = { id, image, by, bookName, RegularPrice, amount: 1 };
const newCart = [...cart, newItem];
setCart(newCart);
}
};
const clearCart = () => {
setCart([]);
};
return (
<CartContext.Provider
value={{
cart,
cartItems,
total,
removeItem,
increaseAmount,
decreaseAmount,
addToCart,
clearCart
}}
>
{children}
</CartContext.Provider>
);
}
export { CartContext, CartProvider };
Your CodeSandbox link works fine for me. Total is not shown as NaN. The only problem: change the initial amount from 0 to 1 (CartContext.js, line 48):
cart.concat({
amount: 1,
/* ... */
})
Related
I have a problem with the localStorage in my application. When I add items to a list of "favorites" they are stored without any problem in the localStorage, they can even be deleted by clicking them again.
But when I refresh the page, my application doesn't read that these items are in the favorites list and therefore doesn't mark them. Also, when I add a new item to the favorites list it causes it to delete everything from localStorage and start over.
Here's a gif of the localStorage view
Here's the code:
import React, { useState, useEffect } from 'react';
import SearchBar from '../../SearchBar/SearchBar.js';
import FiltersBox from '../FiltersBox/FiltersBox.js';
import { getItems } from '../../../Database/Database.js';
import './ItemsContainer.css';
function ItemsContainer() {
const [items, setItems] = useState([]);
const [search, setSearch] = useState('');
const [favoriteItems, setFavoriteItems] = useState([]);
let localItems = localStorage.getItem('Favorite Items');
const [sortPrice, setSortPrice] = useState('');
const [filterCategory, setFilterCategory] = useState('');
const addItemToFavorites = item => {
let existentItem = favoriteItems.find(favItem => favItem.id === item.id);
if (existentItem) {
let filterTheExistentItem = favoriteItems.filter(
favItem => item.title !== favItem.title
);
setFavoriteItems(filterTheExistentItem);
let stringItems = JSON.stringify(filterTheExistentItem);
localStorage.setItem('Favorite Items', stringItems);
} else {
setFavoriteItems([...favoriteItems, item]);
let stringItems = JSON.stringify([...favoriteItems, item]);
localStorage.setItem('Favorite Items', stringItems);
}
};
const filteredItemsList = () => {
let newItemList = [];
newItemList = items.filter(item => {
if (filterCategory !== '' && filterCategory !== 'none') {
return item.category === filterCategory;
} else {
return item;
}
});
if (sortPrice === 'ascending') {
return newItemList.sort((a, b) => (a.price > b.price ? 1 : -1));
} else if (sortPrice === 'descending') {
return newItemList.sort((a, b) => (b.price > a.price ? 1 : -1));
} else {
return newItemList;
}
};
function onSortSelected(sortValue) {
setSortPrice(sortValue);
}
function onCategorySelected(categoryValue) {
setFilterCategory(categoryValue);
}
useEffect(() => {
getItems().then(res => setItems(res));
}, []);
useEffect(() => {
let xd = JSON.parse(localItems);
console.log(xd);
}, [localItems]);
return (
<div>
<SearchBar setSearch={setSearch} />
<FiltersBox
items={items}
setItems={setItems}
onSortSelected={onSortSelected}
onCategorySelected={onCategorySelected}
/>
<div>
{filteredItemsList()
.filter(item =>
search.toLowerCase() === ''
? item
: item.title.toLowerCase().includes(search)
)
.map(item => (
<div key={item.id}>
<div>{item.title}</div>
<button
className={favoriteItems.includes(item) ? 'si' : 'no'}
onClick={() => addItemToFavorites(item)}>
Add to favorites
</button>
</div>
))}
</div>
</div>
);
}
export default ItemsContainer;
And here I leave a GIF with a continuous console.log of the localStorage:
I tried everyrhing, and I don't know what is happening.
You're retrieving your items in localItems and... you do nothing with this variable. You should initialize your state favoritesItems with your local storage
const getItemsFromLocalStorage = () => {
const items = localStorage.getItem('Favorite Items');
return items ? JSON.parse(items) : [];
}
const [favoriteItems, setFavoriteItems] = useState(getItemsFromLocalStorage())
This is where the culprit is:
const [favoriteItems, setFavoriteItems] = useState([]);
let localItems = localStorage.getItem('Favorite Items');
You load localStorage into localItems, but you expect it to be in favoriteItems, where you have never assigned it. You would need to specify the item of localStorage as the initial state, like:
let localItems = localStorage.getItem('Favorite Items');
const [favoriteItems, setFavoriteItems] = useState(localItems ? localItems : []);
I have 4 products which I would like to do toggle handler for. User can choose only one of them. Every product will have pernament ID at the shopping cart.
If I click for one of the product, 3 of them should not be in the current cart - later if I change mind and click on another product, current one should be removed from the cart. I can recognize them by ID. I have a problem to make toggle handler. Can someone help me how funcion should looks for something like that? As later, depend on the choosen product, I wanna show diffrent price of other product and if change the basic ones, everything will change in the cart.
That's my functions in cart file for update items, add item and remove item. It's for all items in store, not only these 4 basic products. I would like to rewrite addToCartHandler for checking at the same time if there are already other ID prodcuts and if yes, remove them.
const updateCartHandler = async (item) => {
const existItem = cartItems.find((x) => x._id === product._id);
const quantity = existItem ? existItem.quantity - 1 : 1;
// const { data } = await axios.get(`/api/products/${item._id}`);
if (data.countInStock < quantity) {
window.alert("Sorry. Product is out of stock");
return;
}
ctxDispatch({
type: "CART_ADD_ITEM",
payload: { ...item, quantity },
});
};
const addToCartHandler = async (item) => {
const existItem = cartItems.find((x) => x._id === product._id);
const quantity = existItem ? existItem.quantity + 1 : 1;
// const { data } = await axios.get(`/api/products/${item._id}`);
if (data.countInStock < quantity) {
window.alert("Sorry. Product is out of stock");
return;
}
ctxDispatch({
type: "CART_ADD_ITEM",
payload: { ...item, quantity },
});
};
const removeItemHandler = (item) => {
ctxDispatch({ type: "CART_REMOVE_ITEM", payload: item });
};
Store:
import { createContext, useReducer } from "react";
export const Store = createContext();
const initialState = {
cart: {
cartItems: localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [],
},
};
function reducer(state, action) {
switch (action.type) {
case "CART_ADD_ITEM":
// add to cart
const newItem = action.payload;
const existItem = state.cart.cartItems.find(
(item) => item._id === newItem._id
);
const cartItems = existItem
? state.cart.cartItems.map((item) =>
item._id === existItem._id ? newItem : item
)
: [...state.cart.cartItems, newItem];
localStorage.setItem("cartItems", JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
case "CART_REMOVE_ITEM": {
const cartItems = state.cart.cartItems.filter(
(item) => item._id !== action.payload._id
);
localStorage.setItem("cartItems", JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
}
default:
return state;
}
}
export function StoreProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch };
return <Store.Provider value={value}>{props.children}</Store.Provider>;
}
thank you for helping
In image it is saying It is expected to have ";", can someone explain ?
import React, {useReducer} from 'react';
import CartContext from './cart-context';
const defaultCartState = {
items : [],
totalAmount : 0
};
const cartReducer = (state, action) = {
if (action.type === 'ADD') {
const updatedItems = state.items.concat(item);
const updatedTotalAmount = state.totalAmount + action.item.price * action.item.amount;
return {
items : updatedItems,
totalAmount : updatedTotalAmount
};
}
return defaultCartState;
}
const CartProvider = (props) => {
const [cartState, dispatchCartState] = useReducer(cartReducer, defaultCartState);
const adder = (item) =>{
dispatchCartState({type : 'ADD', item : item})
};
const remover = (id) => {
dispatchCartState({type : 'REMOVE', id : id})
};
const cartContext = {
item: [],
totalAmount : 0,
addItem : adder,
removeItem : remover
}
return (
<CartContext.Provider value={cartContext}>
{props.children}
</CartContext.Provider>
);
}
export default CartProvider;
I don't know what code is saying. Picture has been attached.
You are Using the Arrow Function in a Wrong way
Update your cartReducer function like this
const cartReducer = (state, action) => {
if (action.type === 'ADD') {
const updatedItems = state.items.concat(item);
const updatedTotalAmount = state.totalAmount + action.item.price * action.item.amount;
return {
items : updatedItems,
totalAmount : updatedTotalAmount
};
}
I need to convert my Class Components code to Functional Hooks Components. I have the Class Components logic below that works. However for me to implement Context Api I need to convert it to hooks.
I get storeList undefined when I console log it and this error...
TypeError: undefined is not an object (evaluating 'storeList'.map).
Is there a way I can make introduce a state inside a UseEffect? Thank you in advance
const { data, status } = useQuery('stores', fetchStores)
const [isSelected, setIsSelected] = useState(false)
const storeList = data
useEffect(() => {
let array = storeList.map((item, index) => {
isSelected = false
return { ...item }
})
setIsSelected({ ...isSelected, array: { ...storeList.array } })
selectHandler()
}, [])
const selectHandler = (ind) => {
let array = storeList.map((item, index) => {
if (ind == index) {
item.isSelected = !item.isSelected
}
return { ...item }
})
setIsSelected({ ...isSelected, array: { ...storeList.array } })
}
Here is the same code as Class component and it works perfectly
async componentDidMount() {
let array = this.state.storeList.map((item, index) => {
this.isSelected = false
return { ...item }
})
this.setState({ storeList: array })
}
selectionHandler = (ind) => {
const { storeList } = this.state
let array = storeList.map((item, index) => {
if (ind == index) {
item.isSelected = !item.isSelected
}
return { ...item }
})
this.setState({ storeList: array })
}
Try This:
const { data, status } = useQuery('stores', fetchStores)
const [isSelected, setIsSelected] = useState(false)
const storeList = data
useEffect(() => {
let array = storeList.map((item, index) => {
isSelected = false
return { ...item }
})
setIsSelected(state => ({ ...state, array: { ...storeList.array } }));
selectHandler()
}, [])
const selectHandler = (ind) => {
let array = storeList.map((item, index) => {
if (ind == index) {
item.isSelected = !item.isSelected
}
return { ...item }
})
setIsSelected(state => ({ ...state, array: { ...storeList.array } }));
}
Reference: https://stackoverflow.com/a/63522873/10994570
This worked!! I hope it can help someone else. Thanks
const { data, status } = useQuery('stores', fetchStores)
const [isSelected, setIsSelected] = useState(false)
const storeList = data
useEffect(() => {
handlerControl()
}, [])
const handlerControl = async () => {
try {
let array = await storeList.map((item, index) => {
isSelected = false
return { ...item }
})
setIsSelected(array)
} catch (e) {
console.log(e)
}
}
const selectHandler = async (ind) => {
//alert('pressed')
try {
let array = await storeList.map((item, index) => {
if (ind == index) {
item.isSelected = !item.isSelected
}
return { ...item }
})
setIsSelected(array)
console.log(index)
} catch (e) {
console.log(e)
}
}
I am trying to migrate my class based react component to react-hooks. The purpose of the component is to fetch stories from HackerNews API and after each 5000 milliseconds to do a polling by hitting the API again for new data.
The problem I am facing is in using the custom hooks below usePrevious() to compare my previous state with current state and only after the comparison to execute some other function inside useEffect()
I am most probably missing some basic implementation here of the custom hooks or of useEffect()
And I am following this official guide
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Here's the code for my class based component and this is perfectly working.
And below is my hooks based component
The problem is with this line
const fromPrevStoriesIds = usePrevious(prevStoriesIds);
The variable fromPrevStoriesIds is giving me good value inside return(), but inside useEffect() its undefined.
import React, { Component, useState, useEffect, useRef } from "react";
import axios from "axios";
import MUIDataTable from "mui-datatables";
import "./Dashboard.css";
import NewItemAddedConfirmSnackbar from "./NewItemAddedConfirmSnackbar";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
const isEqual = require("lodash.isequal");
const differenceWith = require("lodash.differencewith");
const omit = require("lodash.omit");
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const getEachStoryGivenId = (id, index) => {
return new Promise((resolve, reject) => {
axios
.get(`https://hacker-news.firebaseio.com/v0/item/${id}.json`)
.then(res => {
let story = res.data;
let result = omit(story, ["descendants", "time", "id", "type"]);
if (
result &&
Object.entries(result).length !== 0 &&
result.constructor === Object
) {
resolve(result);
} else {
reject(new Error("No data received"));
}
});
});
};
const Dashboard = () => {
const [prevStoriesIds, setPrevStoriesIds] = useState([]);
const [fetchedData, setFetchedData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [tableState, setTableState] = useState({});
const [
openNewItemAddedConfirmSnackbar,
setOpenNewItemAddedConfirmSnackbar
] = useState(false);
const [noOfNewStoryAfterPolling, setNoOfNewStoryAfterPolling] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const onChangeRowsPerPage = rowsPerPage => {
setRowsPerPage(rowsPerPage);
};
const closeNewItemConfirmSnackbar = () => {
setOpenNewItemAddedConfirmSnackbar(false);
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
setPrevStoriesIds(storyIds.data.slice(0, 2));
getAllNewStory(storyIds);
});
};
const getAllNewStory = storyIds => {
setIsLoading(true);
let topStories = storyIds.data.slice(0, 2).map(getEachStoryGivenId);
let results = Promise.all(topStories);
results
.then(res => {
setFetchedData(res);
setIsLoading(false);
})
.catch(err => {
console.log(err);
});
};
const fromPrevStoriesIds = usePrevious(prevStoriesIds);
useEffect(() => {
const fetchData = () => {
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
// console.log("STORY IDs FETCHED ", storyIds.data.slice(0, 2));
setPrevStoriesIds(storyIds.data.slice(0, 2));
getAllNewStory(storyIds);
});
};
fetchData();
const doPolling = () => {
var timer = setInterval(() => {
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
console.log(
"fromPrevStoriesIds INSIDE doPolling() ",
fromPrevStoriesIds
);
if (
fromPrevStoriesIds !== undefined &&
!isEqual(fromPrevStoriesIds.sort(), storyIds.data.slice(0, 2).sort())
) {
setPrevStoriesIds(storyIds.data.slice(0, 2));
setNoOfNewStoryAfterPolling(
differenceWith(
prevStoriesIds.sort(),
storyIds.data.slice(0, 2).sort(),
isEqual
).length
);
getAllNewStory(storyIds);
setOpenNewItemAddedConfirmSnackbar(true);
}
});
}, 5000);
};
doPolling();
// return () => {
// console.log("cleaning up");
// clearInterval(timer);
// };
}, [rowsPerPage, noOfNewStoryAfterPolling]);
let renderedStoriesOnPage = [];
const getDataToRender = (() => {
renderedStoriesOnPage = fetchedData.map(i => {
return Object.values(i);
});
return renderedStoriesOnPage;
})();
const columnsOptions = [
{
name: "Author",
sortDirection: tableState
? tableState.columns && tableState.columns[0].sortDirection
: null
},
{
name: "score",
sortDirection: tableState
? tableState.columns && tableState.columns[1].sortDirection
: null
},
{
name: "title",
sortDirection: tableState
? tableState.columns && tableState.columns[2].sortDirection
: null
},
{
name: "URL",
options: {
filter: false,
customBodyRender: (value, tableMeta, updateValue) => {
// console.log("TABLE META IS ", JSON.stringify(tableMeta));
return (
<a target="_blank" href={value}>
{value}
</a>
);
}
}
}
];
const options = {
filter: true,
selectableRows: false,
filterType: "dropdown",
responsive: "stacked",
selectableRows: "multiple",
rowsPerPage: tableState ? tableState.rowsPerPage : 10,
onChangeRowsPerPage: onChangeRowsPerPage,
activeColumn: tableState ? tableState.activeColumn : 0,
onTableChange: (action, tableState) => {
// console.log("taBLE STATE IS ", JSON.stringify(tableState));
setTableState(tableState);
}
};
return (
<React.Fragment>
{console.log("fromPrevStoriesIds INSIDE RETURN --- ", fromPrevStoriesIds)}
<div
style={{
marginLeft: "15px",
marginTop: "80px",
display: "flex",
flexDirection: "row"
}}
>
<h4 style={{ width: "400px", paddingRight: "15px" }}>
Hacker News top 2
</h4>
</div>
<div>
{isLoading ? (
<div className="interactions">
<div className="lds-ring">
<div />
<div />
<div />
<div />
</div>
</div>
) : fetchedData.length !== 0 && renderedStoriesOnPage.length !== 0 ? (
<MUIDataTable
title={"Hacker News API top 2 result"}
data={renderedStoriesOnPage}
columns={columnsOptions}
options={options}
/>
) : null}
<NewItemAddedConfirmSnackbar
openNewItemAddedConfirmSnackbar={openNewItemAddedConfirmSnackbar}
closeNewItemConfirmSnackbar={closeNewItemConfirmSnackbar}
noOfNewStoryAfterPolling={noOfNewStoryAfterPolling}
/>
</div>
</React.Fragment>
);
};
export default Dashboard;
Instead of returning ref.current from usePrevious return, ref since ref.current will be mutated at its reference and you will be able to receive the updated value within useEffect otherwise it will receive the value from its closure
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref;
}
Code:
const fromPrevStoriesIds = usePrevious(prevStoriesIds);
useEffect(() => {
const fetchData = () => {
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
// console.log("STORY IDs FETCHED ", storyIds.data.slice(0, 2));
setPrevStoriesIds(storyIds.data.slice(0, 2));
getAllNewStory(storyIds);
});
};
fetchData();
const doPolling = () => {
var timer = setInterval(() => {
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
console.log(
"fromPrevStoriesIds INSIDE doPolling() ",
fromPrevStoriesIds.current
);
if (
fromPrevStoriesIds.current !== undefined &&
!isEqual(fromPrevStoriesIds.current.sort(), storyIds.data.slice(0, 2).sort())
) {
setPrevStoriesIds(storyIds.data.slice(0, 2));
setNoOfNewStoryAfterPolling(
differenceWith(
prevStoriesIds.sort(),
storyIds.data.slice(0, 2).sort(),
isEqual
).length
);
getAllNewStory(storyIds);
setOpenNewItemAddedConfirmSnackbar(true);
}
});
}, 5000);
};
doPolling();
// return () => {
// console.log("cleaning up");
// clearInterval(timer);
// };
}, [rowsPerPage, noOfNewStoryAfterPolling]);