I am working on building an expense tracker using react. I want to save the entered inputs using localStorage. I have tried the below way but seems getItem() is not working as expected. The input is gone after refresh. Below is the code snippet
import React,{useState,useEffect} from "react";
import Expenses from "./components/Expenses";
import NewExpense from "./components/NewExpense/NewExpense";
const dummyExpenses = [
{
id: "e1",
title: "Toilet Paper",
amount: 94.12,
date: new Date(2020, 7, 14),
},
{ id: "e2", title: "New TV", amount: 799.49, date: new Date(2021, 2, 12) },
{
id: "e3",
title: "Car Insurance",
amount: 294.67,
date: new Date(2021, 2, 28),
},
{
id: "e4",
title: "New Desk (Wooden)",
amount: 450,
date: new Date(2021, 5, 12),
},
{
id: "e5",
title: "internet",
amount: 400,
date: new Date(2021, 3, 12),
},
];
//Rendering items using localstorage.getItem
const local = localStorage.getItem('expenses')
? JSON.parse(localStorage.getItem('expenses'))
: dummyExpenses
const App = ()=>{
const [expenses,setExpenses] = useState(dummyExpenses,local);
const addExpenseHandler = expense =>{
setExpenses(prevExpenses=>{
return[expense,...prevExpenses];
});
//console.log('In App.js');
console.log(expense);
};
//saving items using localStorage
useEffect(() => {
localStorage.setItem('expenses', JSON.stringify(expenses))
}, [expenses])
return (
<div>
<NewExpense onAddExpense = {addExpenseHandler}/>
<Expenses items = {expenses}/>
</div>
);
}
export default App;
I am using getMonth() to filter the expenses month wise, and on just using local in useState is rendering the below error
'TypeError: expense.date.getMonth is not a function'
Expense component
import React, { useState } from "react";
import "./Expenses.css";
import ExpensesFilter from "./ExpenseFilter";
import ExpenseList from "./ExpenseList";
function Expenses(props) {
const [filteredMonth, setFilteredMonth] = useState(" ");
const FilterChangeHandler = (selectedMonth) => {
//console.log(selectedYear);
setFilteredMonth(selectedMonth);
};
const FilteredExpenses = props.items.filter((expense) => {
return expense.date.getMonth().toString() === filteredMonth;
});
return (
<div className="expenses">
<ExpensesFilter
selected={filteredMonth}
onChangeFilter={FilterChangeHandler}
/>
<ExpenseList items = {FilteredExpenses} />
</div>
);
}
export default Expenses;
Related
There is a json file
{
"items": [
{ "id": "0", "imageUrl": "https://dodopizza.azureedge.net/static/Img/Products/f035c7f46c0844069722f2bb3ee9f113_584x584.jpeg", "title": "Пепперони Фреш с перцем", "types": [0, 1], "sizes": [26, 30, 40], "price": 803, "category": 0, "rating": 4 },
]
}
Pizza is loaded from the file, and all data is output to react
http://joxi.ru/krDaNEVSGRlpJm
Tell me please, how in redux toolkit to make the price of the product increase depending on the selected parameter sizes. That is, if the value 26 is selected, then you need to increase the amount by 100 rubles, if the size is 30, then by 200 rubles.
I tried to do it with various crutches, but I don't have enough knowledge
Here is the code where I get the pizzas
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
pizzas: [],
itemsCount: 0,
isLoading: true,
};
export const getItemsSlice = createSlice({
name: "items",
initialState,
reducers: {
setItems(state, action) {
state.pizzas = action.payload;
},
setItemsCount(state, action) {
state.itemsCount = action.payload;
},
setIsLoading(state, action) {
state.isLoading = action.payload;
},
},
});
export const { setItems, setItemsCount, setIsLoading } = getItemsSlice.actions;
export default getItemsSlice.reducer;
React output code
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { addPizzaInCart } from "../../../redux/slices/CartSlice";
import { typeName } from "../../../redux/slices/GetItemsSlice";
import styles from "./PizzaBlock.module.scss";
function PizzaBlock({ id, imageUrl, title, price, types, sizes }) {
const [activeType, setActiveType] = React.useState(0);
const [activeSize, setActiveSize] = React.useState(0);
const dispatch = useDispatch();
const itemInCart = useSelector((state) => state.cart.pizzasInCart.find((obj) => obj.id === id && obj.type === typeName[activeType] && obj.size === sizes[activeSize]));
const addedCount = itemInCart ? itemInCart.count : 0;
const pizza = {
id,
imageUrl,
title,
price,
type: typeName[activeType],
size: sizes[activeSize],
};
const onClickAddPizza = () => {
dispatch(addPizzaInCart(pizza));
};
const onChangeSize = (i) => {
setActiveSize(i);
};
return (
<div className={styles.item}>
<img className={styles.item__image} src={imageUrl} alt="Pizza" />
<Link to={`/product/${pizza.id}`} className={styles.item__title}>
{title}
</Link>
<div className={styles.item__selector}>
<ul>
{types.map((type, i) => (
<li className={activeType === i ? styles.active : ""} onClick={() => setActiveType(type)} key={i}>
{typeName[type]}
</li>
))}
</ul>
<ul>
{sizes.map((size, i) => (
<li className={activeSize === i ? styles.active : ""} onClick={() => onChangeSize(i)} key={i}>
{size} см.
</li>
))}
</ul>
</div>
<div className={styles.item__bottom}>
<div className={styles.item__price}>от {Math.trunc(pizza.price * (pizza.size / 100 + 1))} ₽</div>
<div className={styles.item__buttons}>
<button className={`${styles.button} ${styles.button_outline} ${styles.button_add}`} onClick={onClickAddPizza}>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8 4.8H7.2V1.2C7.2 0.5373 6.6627 0 6 0C5.3373 0 4.8 0.5373 4.8 1.2V4.8H1.2C0.5373 4.8 0 5.3373 0 6C0 6.6627 0.5373 7.2 1.2 7.2H4.8V10.8C4.8 11.4627 5.3373 12 6 12C6.6627 12 7.2 11.4627 7.2 10.8V7.2H10.8C11.4627 7.2 12 6.6627 12 6C12 5.3373 11.4627 4.8 10.8 4.8Z" fill="white" />
</svg>
<span>Добавить</span>
{addedCount > 0 && <i>{addedCount}</i>}
</button>
</div>
</div>
</div>
);
}
export default PizzaBlock;
There are items with sizes 26, 30 and 40. When I change the active size, that is, I press the button 26, 30 or 40, I need the price for pizza to automatically increase by 0, 100 and 200 rubles, depending on the size of the pizza. I tried using state to pass size parameters and already substitute the required amount in redux, but in this case problems appeared, the value of this state was applied to all pizzas at once, but only to the current one
Here Cart logic
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
totalPricePizzasInCart: 0,
totalCountPizzasInCart: 0,
pizzasInCart: [],
};
export const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
addPizzaInCart(state, action) {
const findItem = state.pizzasInCart.find((obj) => {
return obj.id === action.payload.id && obj.type === action.payload.type && obj.size === action.payload.size;
});
if (findItem) {
findItem.count++;
} else {
state.pizzasInCart.push({
...action.payload,
count: 1,
});
}
state.totalPricePizzasInCart = state.pizzasInCart.reduce((sum, obj) => {
return Math.trunc(obj.price * (obj.size / 100 + 1)) * obj.count + sum;
}, 0);
state.totalCountPizzasInCart = state.pizzasInCart.reduce((count, obj) => {
return obj.count + count;
}, 0);
},
minusPizzaInCart(state, action) {
const findItem = state.pizzasInCart.find((obj) => {
return obj.id === action.payload.id && obj.type === action.payload.type && obj.size === action.payload.size;
});
if (findItem && findItem.count > 0) {
findItem.count--;
state.totalPricePizzasInCart = state.totalPricePizzasInCart - Math.trunc(findItem.price * (findItem.size / 100 + 1));
}
state.totalCountPizzasInCart = state.pizzasInCart.reduce((count, obj) => {
return obj.count + count;
}, 0);
},
removePizzaInCart(state, action) {
state.pizzasInCart = state.pizzasInCart.filter((obj) => {
return obj.id !== action.payload.id || obj.type !== action.payload.type || obj.size !== action.payload.size;
});
state.totalPricePizzasInCart = state.pizzasInCart.reduce((sum, obj) => {
return Math.trunc(obj.price * (obj.size / 100 + 1)) * obj.count + sum;
}, 0);
state.totalCountPizzasInCart = state.pizzasInCart.reduce((count, obj) => {
return obj.count + count;
}, 0);
},
clearPizzasInCart(state) {
state.pizzasInCart = [];
state.totalPricePizzasInCart = 0;
state.totalCountPizzasInCart = 0;
},
},
});
export const { addPizzaInCart, removePizzaInCart, minusPizzaInCart, clearPizzasInCart } = cartSlice.actions;
export default cartSlice.reducer;
github github.com/antonboec1994/reactPizza.git
Store :
import { configureStore, createSlice } from "#reduxjs/toolkit";
const initialState = {
basePrice: 100,
total: 0,
itemsCount: 0
};
export const getItemsSlice = createSlice({
name: "items",
initialState,
reducers: {
addToCart(state, action) {
console.log(action.payload);
state.itemsCount = action.payload;
state.total = state.itemsCount * state.basePrice;
}
}
});
export const { addToCart } = getItemsSlice.actions;
export const store = configureStore({
reducer: {
pizza: getItemsSlice.reducer
}
});
export default getItemsSlice.reducer;
Component :
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { addToCart } from "../store/__counterStore__";
const PizzaComponent = () => {
const dispatch = useDispatch();
const storeData = useSelector((state) => state.pizza);
console.log(storeData);
const [itemCount, setItemCount] = React.useState(0);
const handleAdd = () => {
setItemCount(itemCount + 1);
dispatch(addToCart(itemCount));
};
return (
<div>
<button onClick={handleAdd}>Add 1 Pitzzza</button>
<h2>Cart Price</h2>
<div>{storeData.total}</div>
</div>
);
};
export default PizzaComponent;
I just updated price for one type of pitza, what you can do is you can updated price according to type of different pitzas, by setting dispatch different kind of payload of you can put locgic in your reducer.
I tried to make it simple, since purpose is to understand the folw and logic.
EDIT:
CODESANDBOX
I'm using Vuejs 2 and vue router to create this website.
It has a cart saved in session.storage.
I would like to delete the cart (session.storage) when the restaurant page change.
I've tried to save the slug of the restaurant and compare it to the current slug:
setSlug() {
sessionStorage.setItem("slug", this.$route.params.slug);
},
checkSlug() {
if (sessionStorage.getItem("slug") != this.$route.params.slug) {
sessionStorage.clear();
}
},
But it doesn't work.
How can I achieve this?
Thank you.
TheRestaurant.vue
export default {
name: "TheRestaurant",
data() {
return {
restaurant: {},
cart: {},
quantity: 1,
partialTotal: 0,
total: 0,
};
},
methods: {
//get the restaurant and the dishes with axios call and set the data
getRestaurant() {
axios
.get("/api/restaurants/" + this.$route.params.slug)
.then((response) => {
this.restaurant = response.data;
})
.catch((error) => {
console.log(error);
});
},
showDetails(id) {
let modal = document.getElementById("modal-" + id);
modal.classList.replace("d-none", "d-flex");
},
hideDetails(id) {
let modal = document.getElementById("modal-" + id);
modal.classList.replace("d-flex", "d-none");
},
addToCart(dish) {
if (sessionStorage.getItem("cart") == null) {
sessionStorage.setItem("cart", JSON.stringify([]));
}
let cart = JSON.parse(sessionStorage.getItem("cart"));
let index = cart.findIndex((item) => item.id == dish.id);
if (index == -1) {
dish.quantity = 1;
cart.push(dish);
} else {
cart[index].quantity++;
}
sessionStorage.setItem("cart", JSON.stringify(cart));
this.cart = JSON.parse(sessionStorage.getItem("cart"));
this.partialTotal = round(
this.cart.reduce(
(acc, dish) => acc + dish.price * dish.quantity,
0
),
2
);
sessionStorage.setItem(
"partialTotal",
JSON.stringify(this.partialTotal)
);
this.total = this.partialTotal + this.restaurant.delivery_price;
sessionStorage.setItem("total", JSON.stringify(this.total));
},
removeOneFromCart(dish) {
let cart = JSON.parse(sessionStorage.getItem("cart"));
let index = cart.findIndex((item) => item.id == dish.id);
if (index !== -1) {
cart[index].quantity--;
if (cart[index].quantity == 0) {
cart.splice(index, 1);
}
}
sessionStorage.setItem("cart", JSON.stringify(cart));
this.cart = JSON.parse(sessionStorage.getItem("cart"));
this.partialTotal = round(
this.cart.reduce(
(acc, dish) => acc + dish.price * dish.quantity,
0
),
2
);
sessionStorage.setItem(
"partialTotal",
JSON.stringify(this.partialTotal)
);
this.total = this.partialTotal + this.restaurant.delivery_price;
sessionStorage.setItem("total", JSON.stringify(this.total));
},
removeAllFromCart(dish) {
let cart = JSON.parse(sessionStorage.getItem("cart"));
let index = cart.findIndex((item) => item.id == dish.id);
if (index !== -1) {
cart.splice(index, 1);
}
sessionStorage.setItem("cart", JSON.stringify(cart));
this.cart = JSON.parse(sessionStorage.getItem("cart"));
this.partialTotal = round(
this.cart.reduce(
(acc, dish) => acc + dish.price * dish.quantity,
0
),
2
);
sessionStorage.setItem(
"partialTotal",
JSON.stringify(this.partialTotal)
);
this.total = this.partialTotal + this.restaurant.delivery_price;
sessionStorage.setItem("total", JSON.stringify(this.total));
},
},
mounted() {
this.getRestaurant();
this.cart = JSON.parse(sessionStorage.getItem("cart"));
this.partialTotal = JSON.parse(sessionStorage.getItem("partialTotal"));
this.total = JSON.parse(sessionStorage.getItem("total"));
},
};
router.js
import Vue from "vue";
import VueRouter from "vue-router";
import Restaurant from "./pages/TheRestaurant.vue";
import Home from "./pages/TheMain.vue";
import Cart from "./pages/TheCart.vue";
import Search from "./pages/AdvancedSearch.vue";
//put all the different pages below
Vue.use(VueRouter);
/**
* #type {import("vue-router").RouteConfig[]}
*/
const routes = [
{
path: "/",
component: Home,
name: "home.index",
meta: {
title: "Deliveboo Homepage",
},
},
{
path: "/cart",
component: Cart,
name: "cart.index",
meta: {
title: "Deliveboo Cart",
},
},
{
path: "/search",
component: Search,
name: "search.index",
meta: {
title: "Deliveboo Search Restaurants",
},
},
{
path: "/:slug",
component: Restaurant,
name: "restaurant.index",
meta: {
title: "Deliveboo Restaurant",
},
},
];
const router = new VueRouter({
//it must contain an array of routes
routes,
mode: "history",
});
export default router;
If I understood your hierarchy and logic of components correctly,
in the mounted hook of Home and Search components you can reset your session.storage.
How to detect click on an axis label with chart.js
In the example bellow, I can only detect click on the graph itself
https://stackblitz.com/edit/ng2-charts-bar-template-qchyz6
You will need to implement a custom plugin that can listen to all the events of the canvas:
const findLabel = (labels, evt) => {
let found = false;
let res = null;
labels.forEach(l => {
l.labels.forEach((label, index) => {
if (evt.x > label.x && evt.x < label.x2 && evt.y > label.y && evt.y < label.y2) {
res = {
label: label.label,
index
};
found = true;
}
});
});
return [found, res];
};
const getLabelHitboxes = (scales) => (Object.values(scales).map((s) => ({
scaleId: s.id,
labels: s._labelItems.map((e, i) => ({
x: e.translation[0] - s._labelSizes.widths[i],
x2: e.translation[0] + s._labelSizes.widths[i] / 2,
y: e.translation[1] - s._labelSizes.heights[i] / 2,
y2: e.translation[1] + s._labelSizes.heights[i] / 2,
label: e.label,
index: i
}))
})));
const plugin = {
id: 'customHover',
afterEvent: (chart, event, opts) => {
const evt = event.event;
if (evt.type !== 'click') {
return;
}
const [found, labelInfo] = findLabel(getLabelHitboxes(chart.scales), evt);
if (found) {
console.log(labelInfo);
}
}
}
Chart.register(plugin);
const options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'pink'
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderColor: 'orange'
}
]
},
options: {}
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js"></script>
</body>
Adpatation for Angular with ng2-charts (chart-js v3.7.1)
Just use Chart.register
i.e. put the following functiion into component ngOnInit()
RegisterPlugin() {
Chart.register(
{
id: 'yAxisCustomClick',
afterEvent: (chart: Chart<'bar'>, event: {
event: ChartEvent;
replay: boolean;
changed?: boolean | undefined;
cancelable: false;
inChartArea: boolean
}) => {
const evt = event.event;
if (evt.type === 'click' && evt.x! < Object.values(chart.scales).filter(s => s.id === 'x')[0].getBasePixel()) {
const labelIndex = Object.values(chart.scales).filter(s => s.id === 'y')[0].getValueForPixel(evt.y!);
const label = Object.values(chart.scales).filter(s => s.id === 'y')[0].getTicks()[labelIndex!]?.label;
if (label) {
console.log('Do the stuff for', label)
}
}
}
}
);
}
Example in stackblitz udpated
https://stackblitz.com/edit/ng2-charts-bar-template-qchyz6
I'm trying to set pagination on page with dynamic input setting of the pageSize
using redux-form library, I set pageSize using selectors and mapping them into props,
but in order to set pagination I'm in need of that pageSize value in my other reducer (
not form reducer ), and I cant get how to access it
// Reducer
import { FETCH, SHOW_GRID, SHOW_LIST, SET_PAGINATION } from "./types";
import { data } from "../data";
const initialData = {
fetchedData: data,
listSelected: true,
pageSizeOptions: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
};
export default function (state = initialData, action) {
switch (action.type) {
case SHOW_GRID:
return {
...state,
listSelected: false,
};
case SHOW_LIST:
return {`enter code here`
...state,
listSelected: true,
};
case SET_PAGINATION:
console.log(??)
default:
return state;
}
}
// actions
import { FETCH, SHOW_GRID, SHOW_LIST, SET_PAGINATION } from "./types";
export function fetch() {
return {
type: FETCH,
};
}
export function showGrid() {
return {
type: SHOW_GRID,
};
}
export function showList() {
return {
type: SHOW_LIST,
};
}
export function setPagination() {
return {
type: SET_PAGINATION,
};
}
// The component itself
import React, { Component } from "react";
import { reduxForm, Field, formValueSelector } from "redux-form";
import { connect } from "react-redux";
import { getFormValues } from "redux-form";
import { setPagination } from "../store/actions";
class PageSize extends Component {
render() {
const pageSizeOptions = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
return (
<Field
component="select"
name="pageSize"
defaultValue={4}
onChange={setPagination}
>
{pageSizeOptions.map((val) =>
val == this.props.pageSize ? (
<option name={val} value={val} selected>
{val}
</option>
) : (
<option value={val}>{val}</option>
)
)}
</Field>
);
}
}
export const ReduxFormComponent = reduxForm({
form: "pageSize",
})(PageSize);
const mapStateToProps = (state) => {
const selector = formValueSelector("pageSize");
const selectedPageSize = selector(state, "pageSize");
return {
pageSize: selectedPageSize,
};
};
const mapDispatchToProps = {
setPagination,
};
const component = connect(
mapStateToProps,
mapDispatchToProps
)(ReduxFormComponent);
export default connect((state) => ({
values: getFormValues("pageSize")(state),
}))(component);
I'm trying to recreate RxMarbles for RxJS 5, but I'm having feedback problems when I change the collection's data (specifically the length of the data source).
I added console.logs for debugging
Note for those who are familiar with RxMarbles, I renamed "Diagram" to "Timeline".
import { svg } from '#cycle/dom';
import isolate from '#cycle/isolate';
import { Observable } from 'rxjs';
import { apply, flip, map, max, merge, path, prop, sortBy, zip } from 'ramda';
import { Collection } from '../collection';
import { Marble } from './marble';
import { EndMarker } from './end-marker';
function sortMarbleDoms$(marbles$) {
const doms$ = Collection.pluck(marbles$, prop('DOM'));
const dataList$ = Collection.pluck(marbles$, prop('data'));
return Observable.combineLatest(doms$, dataList$, zip)
.map(sortBy(path([1, 'time'])))
.map(map(prop(0)));
}
function OriginalTimeline({ DOM, marbles: marblesState$, end: end$ }) {
const marblesProps$ = end$.map(({ time }) => ({
minTime: 0,
maxTime: time,
}));
const endMarkerProps$ = marblesState$.map(marbles => ({
minTime: marbles.map(prop('time')).reduce(max, 0),
maxTime: 100,
}));
const marblesSources = { DOM, props: marblesProps$ };
const endMarkerSources = {
DOM,
props: endMarkerProps$,
time: end$.pluck('time'),
};
const marbles$ = Collection.gather(
Marble, marblesSources, marblesState$
.do(a=>console.log('marblesState', a)), '_itemId');
const marbleDOMs$ = sortMarbleDoms$(marbles$);
const endMarker = EndMarker(endMarkerSources);
const vtree$ = Observable.combineLatest(marbleDOMs$, endMarker.DOM)
.map(([marbleDOMs, endMarkerDOM]) =>
svg({
attrs: { viewBox: '0 0 100 10' },
style: { width: 500, height: 50, overflow: 'visible' },
}, [
svg.line({
attrs: { x1: 0, x2: 100, y1: 5, y2: 5 },
style: { stroke: 'black', strokeWidth: 0.4 },
}),
endMarkerDOM,
...marbleDOMs,
])
);
const marbleData$ = Collection.pluck(marbles$, prop('data'))
.withLatestFrom(marblesState$, zip)
.map(map(apply(flip(merge))))
const data$ = Observable.combineLatest(marbleData$, endMarker.time)
.map(([marbles, endMarkerTime]) => ({
marbles,
end: { time: endMarkerTime },
}))
.debounceTime(1);
return { DOM: vtree$, data: data$.do(a=>console.log('tdata', a)) };
}
export function Timeline(sources) {
return isolate(OriginalTimeline)(sources);
}
The basic structure of the app is that all necessary data is fed into a global sink to a dummy driver that just takes the data and re-emits it as is (so in theory, all outputs should be new inputs).
Because of this, the problem might be in other parts of my code so I'm happy to post a codepen/plunkr of the code if it helps. This is indeed working sometimes, but not all the time.
Here's the console outputs (abridged)
store Object {route: "merge", inputs: undefined}
timeline.js:39 marblesState [Object, Object, Object, Object]
timeline.js:69 tdata Object {marbles: Array[3], end: Object}
sandbox.js:48 data [Object, Object]
app.js:26 store Object {route: "merge", inputs: Array[2]}
Notice the marblesState has 4 objects, but the tdata returns marbles with an array of 3 objects. For some reason, the Collection is only returning 3 items.
Any help is appreciated. Thanks!
I have no idea why this makes sense but moving up the debounceTime(1) made it work
const marbleData$ = Collection.pluck(marbles$, prop('data'))
.debounceTime(1)
.withLatestFrom(marblesState$, zip)
.map(map(apply(flip(merge))))
const data$ = Observable.combineLatest(marbleData$, endMarker.time)
.map(([marbles, endMarkerTime]) => ({
marbles,
end: { time: endMarkerTime },
}));
The Collection.pluck was sending once for each piece of new and old data.