Child component re-renders before local storage is cleared - javascript

I have a small restaurant app which renders a menu and lets users place an order. The parent component receives an array of menu items and renders a child component for each item. The quantity of each item is initialized at the child component ('menu-item') and is also saved in local storage so if a user refreshes the page the order persists. Once the order is submitted, local storage is cleared by the parent component but this change is not picked up by the child. As a result, users see the order quantity even after the order has been submitted. I would like the order quantity to be reset to 0 after the order is submitted. Below is the condensed codebase.
//Parent
const TotalStore = {
total: 0,
reset_timer: setTimeout(() => {}, 0),
compute() {
let newTotal = 0;
let types = new Map<string, boolean>();
TotalStore.items.forEach((value: TotalStoreMenuItem) => {
newTotal += value.price * value.count;
types.set(value.type, true);
});
if (TotalStore.total !== newTotal) {
clearTimeout(TotalStore.reset_timer);
TotalStore.reset_timer = setTimeout(() => {
TotalStore.total = newTotal;
TotalStore.onChange(newTotal);
}, 50);
}
},
update(id: number, price: number, count: number, type: string) {
TotalStore.items.set(id, {
id,
price,
count,
type,
});
TotalStore.compute();
},
reset() {
TotalStore.items.clear();
localStorage.clear();
TotalStore.total = 0;
},
onChange(t: number) {},
items: new Map<number, TotalStoreMenuItem>(),
};
const itemChanged = (
id: number,
price: number,
count: number,
type: string
) => {
TotalStore.update(id, price, count, type);
};
useEffect(() => {
apiFetch("menu").then((json) => setMenu(json.menu));
}, []);
async function handleSubmit(e: any) {
e.preventDefault();
const selectedItems = getSelectedItems(TotalStore);
apiFetch("order", "post", { selectedItems })
.then((json) => {
alert("Order has been submitted");
TotalStore.reset();
}
return (
<div>
{menu.length > 0 ? (
<>
<div className="menu">
<div className="menu-title">Food Menu</div>
<form id="menu-form" onSubmit={handleSubmit} autoComplete="off">
<Menu onChange={itemChanged} props={menu} />
<button type="submit" disabled={!orderPlaced(total)}>
Place Order
</button>
</form>
</div>
<div className="order-total">
<h2>
Total: $<span>{total.toFixed(2)}</span>
</h2>
</div>
</>
) : (
<>Loading Menu</>
)}
</div>
);
//Menu
export default function Menu({ onChange, props }: MenuProps) {
return (
<div>
{props.map((food: any, index: number) => {
return (
<MenuItem
key={index}
onChange={onChange}
type={food.type}
item={food}
/>
);
})}
</div>
);
}
//Menu-Item
const FoodItemLocalStore = {
setCount(id: Number, count: Number) {
localStorage.setItem(`menu_item_id_${id}`, String(count));
},
getCount(id: Number) {
return parseInt(localStorage.getItem(`menu_item_id_${id}`) || "0");
},
};
export default function MenuItem({ onChange, item, type }: MenuItemProps) {
const [data, setData] = useState({
count: FoodItemLocalStore.getCount(item.id),
});
const menuItemCountChange = (e: any) => {
data.count = parseInt(e.target.value);
FoodItemLocalStore.setCount(item.id, data.count);
setData({ ...data });
};
onChange(item.id, item.price, data.count, type);
return (
<div>
<article className="menu-item" data-item-type={type}>
<h3 className="item-name">{item.name}</h3>
<input
type="number"
className="menu-item-count"
min="0"
value={data.count}
onChange={menuItemCountChange}
/>
<strong className="item-price">${item.price.toFixed(2)}</strong>
</article>
</div>
);
}

Related

How to make an onClick event on a button work the same way as the onClick event defined for other Button in react

I just started learning react and came across this problem where I has two buttons(Add button in menu-item and add button in cart) each defined in different components. I defined the functionality and added logic through props for Add button in menu-item which when user clicks updates the quantity , cartCount and adds item details and quantity to the cart. Now, when I click on Add button in cart, I want a similar functionalty like it should update quantity in cart, quantity in menu-item and cart-count. I know there should be some way without repeating the entire logic again. I know this is a long one. Thanks in Advance for Answering!!!
App.js
import react, { useState, useEffect } from "react";
import Header from "./components/Header";
import LandingPage from "./components/LandingPage";
import MenuItems from "./components/menuItems";
import Cart from "./components/Cart";
import ItemContext from "./store/item-context";
function App() {
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(() => {
return items.reduce((acc, eachItem) => {
return eachItem.quantity + acc;
}, 0)
})
}, [items])
const [cartBool, setCartBool] = useState(false);
function AddedItem(item) {
const foundIndex = items.findIndex(eachItem => {
return eachItem.title === item.title;
})
if (foundIndex !== -1) {
setItems(prev => {
prev[foundIndex].quantity = item.quantity;
return [...prev];
})
}
else {
setItems(prev => {
return [...prev, item]
})
}
}
function handleCartClick() {
setCartBool(true);
}
function handleCloseClick() {
setCartBool(false);
}
return (
<react.Fragment>
<ItemContext.Provider value={{
items: items
}}>
{cartBool &&
<Cart onCloseClick={handleCloseClick} />}
<div className="parent-container">
<Header cartCount={total} onCartClick={handleCartClick} />
<LandingPage />
<MenuItems onAddItem={AddedItem} />
</div>
</ItemContext.Provider>
</react.Fragment>
);
}
export default App;
Menu-items.js
import react from "react";
import MenuItem from "./menuItem";
import MenuContent from "./menuContent";
function MenuItems(props) {
function handleItems(item){
props.onAddItem(item);
}
return (
<div className="menu">
{MenuContent.map(eachItem =>{
return <MenuItem title={eachItem.title} description={eachItem.description} price={eachItem.price} key={eachItem.key} onAdd={handleItems}/>
})}
</div>
);
}
export default MenuItems;
Menu-item.js
import react , { useState } from "react";
function MenuItem(props) {
const [item, setItem] = useState({
title: "",
quantity: 0,
price: ""
});
function handleClick(){
setItem(prev =>{
return {
title: props.title,
quantity: prev.quantity + 1,
price: props.price
}
})
}
function handleSubmit(event){
event.preventDefault();
props.onAdd(item);
}
return (
<div className="menu-item">
<div className="menu-content">
<h3>{props.title}</h3>
<p>{props.description}</p>
<h4>{props.price}</h4>
</div>
<form onSubmit={handleSubmit} className="add-items">
<label htmlFor="Amount">Amount</label>
<input onChange={() => {}} type="number" name="Amount" value={item.quantity}/>
<button onClick={handleClick} type="submit" className="btn btn-lg">Add</button>
</form>
</div>
);
}
export default MenuItem;`
Cart.js
import react, { useContext } from "react";
import CartItem from "./cartItem";
import ItemContext from "../store/item-context";
function Cart(props) {
const ctx = useContext(ItemContext);
function handleCloseClick(){
props.onCloseClick();
}
return (
<div className="cart-modal">
<div className="card">
{ctx.items.map((eachItem, index) =>{
return <CartItem title={eachItem.title} price={eachItem.price} quantity={eachItem.quantity} key={index} onAdd={props.onAddItem} onRemove={props.RemoveItem}/>
})}
<footer>
<button className="btn btn-lg" onClick={handleCloseClick}>Close</button>
<button className="btn btn-lg">Order</button>
</footer>
</div>
</div>
);
}export default Cart;
cartItem.js
import react, { useState } from "react";
function CartItem(props) {
const [item, setItem] = useState({
title: props.title,
price: props.price,
quantity: props.quantity
})
function handlePlusClick(){
setItem(prev =>{
prev.quantity = prev.quantity + 1
return prev
})
props.onAdd(item);
}
function handleMinusClick(){
var updatedQuantity;
setItem(prev =>{
prev.quantity = prev.quantity -1
updatedQuantity = prev.quantity
return prev;
})
if(updatedQuantity > 0){
props.onAdd(item);
}
else{
props.onRemove(item);
}
}
return (
<div className="cart-item">
<div className="cart-content">
<h1>{props.title}</h1>
<p>{props.price}
<span> X {props.quantity}</span>
</p>
</div>
<div className="button-controls">
<button onClick={handleMinusClick}>-</button>
<button onClick={handlePlusClick}>+</button>
</div>
</div>
);
}export default CartItem;
I tried creating new item object when user clicked on + button in cartItem and sent it to AddedItem function in App.js and it is working(even though it is not the best practice out there)!!! But it is also updating the item.quantity in menuItem component too. it is working as expected... But I have no idea why it is going back and updating the menuItem quantity as well. is it because of useContext and I wrapped it around all the components I'm rendering??
Updates in Response to OP 2/18
Your example is still a bit hard to follow and reproduce since we can't see MenuContent and the use of useContext is confusing.
But it sounds like both your menu and the cart are using the same items state or at least something along those lines is happening.
Your code demonstrates a handle on state management but I think you need to take a step back and think about what parts of your app should be stateful and what strategies are needed. You don't need useContext but I suppose it's an opportunity to illustrate the differences and advantages.
State Management Overview
For now I'll assume your menu items are a list of items that aren't really changing. You cart will need some state since you need to track the items along with their quantity and use this information to calculate cart totals.
Where do we need to update or access our cart state?
MenuItem - Our menu item has an Add button that should update the cart state with the new quantity. We don't need the cart items here, but we do need to handle the logic to update our cart.
Cart - Our cart needs to access the cart state to a) show the cart items and b) to increment or decrement the quantity of specific items (+ and -).
You can do this with prop drilling using the same strategies used in your code so far (that you've shared) OR you can use useContext.
To demonstrate the difference, below is a more complete solution with useContext. All state management logic for the cart is bundled into our cart context and our provider lets parts of our app access this without relying so much on props.
Example/Demo Full Solution with useContext (Click to View)
https://codesandbox.io/s/update-cart-example-use-context-4glul7
import "./styles.css";
import React, { useState, createContext, useContext, useReducer } from "react";
const CartContext = createContext();
const initialCartState = { cartItems: [], totalCost: 0, totalQuantity: 0 };
const actions = {
INCREMENT_ITEM: "INCREMENT_ITEM",
DECREMENT_ITEM: "DECREMENT_ITEM",
UPDATE_QUANTITY: "UPDATE_QUANTITY"
};
const reducer = (state, action) => {
const existingCartItem = state.cartItems.findIndex((item) => {
return item.id === action.itemToUpdate.id;
});
switch (action.type) {
case actions.INCREMENT_ITEM:
return {
cartItems: state.cartItems.map((item) =>
item.id === action.itemToUpdate.id
? {
...item,
quantity: item.quantity + 1
}
: item
),
totalQuantity: state.totalQuantity + 1,
totalCost: state.totalCost + action.itemToUpdate.price
};
case actions.DECREMENT_ITEM:
return {
cartItems: state.cartItems.map((item) =>
item.id === action.itemToUpdate.id
? {
...item,
quantity: item.quantity - 1
}
: item
),
totalQuantity: state.totalQuantity - 1,
totalCost: state.totalCost - action.itemToUpdate.price
};
case actions.UPDATE_QUANTITY:
return {
cartItems:
existingCartItem !== -1
? state.cartItems.map((item) =>
item.id === action.itemToUpdate.id
? {
...item,
quantity: item.quantity + action.itemToUpdate.quantity
}
: item
)
: [...state.cartItems, action.itemToUpdate],
totalQuantity: state.totalQuantity + action.itemToUpdate.quantity,
totalCost:
state.totalCost +
action.itemToUpdate.quantity * action.itemToUpdate.price
};
default:
return state;
}
};
const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialCartState);
const value = {
cartItems: state.cartItems,
totalQuantity: state.totalQuantity,
totalCost: state.totalCost,
incrementItem: (itemToUpdate) => {
dispatch({ type: actions.INCREMENT_ITEM, itemToUpdate });
},
decrementItem: (itemToUpdate) => {
dispatch({ type: actions.DECREMENT_ITEM, itemToUpdate });
},
updateQuantity: (itemToUpdate) => {
dispatch({ type: actions.UPDATE_QUANTITY, itemToUpdate });
}
};
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
};
export default function App() {
return (
<CartProvider>
<MenuItems />
<Cart />
</CartProvider>
);
}
const menuItems = [
{ title: "item 1", description: "description 1", price: 10, id: "1" },
{ title: "item 2", description: "description 2", price: 20, id: "2" },
{ title: "item 3", description: "description 3", price: 30, id: "3" }
];
function MenuItems(props) {
return (
<div className="menu">
{menuItems.map((item) => {
return (
<MenuItem
title={item.title}
description={item.description}
price={item.price}
key={item.id}
// added this as prop
id={item.id}
/>
);
})}
</div>
);
}
function MenuItem(props) {
const { updateQuantity } = useContext(CartContext);
const [item, setItem] = useState({
title: props.title,
quantity: 0,
price: props.price,
// included a unique item id here
id: props.id
});
// Don't need this anymore...
// function handleClick(e) {
// ...
// }
// update quantity as we type by getting as state...
function changeQuantity(e) {
e.preventDefault();
setItem((prev) => {
return {
...prev,
quantity: Number(e.target.value)
};
});
}
function handleSubmit(e, item) {
e.preventDefault();
updateQuantity(item);
}
return (
<div className="menu-item">
<div className="menu-content">
<h3>{props.title}</h3>
<p>{props.description}</p>
<h4>Price: ${props.price}</h4>
</div>
<form onSubmit={(e) => handleSubmit(e, item)} className="add-items">
<label htmlFor="Amount">Amount</label>
<input
onChange={changeQuantity}
type="number"
name="Amount"
value={item.quantity}
/>
{/* No need for onClick on button, onSubmit already handles it */}
<button type="submit" className="btn btn-lg">
Add
</button>
</form>
</div>
);
}
function Cart() {
const {
cartItems,
totalQuantity,
totalCost,
incrementItem,
decrementItem
} = useContext(CartContext);
return (
<div>
<h2>Cart</h2>
<h3>Items:</h3>
{cartItems.length > 0 &&
cartItems.map(
(item) =>
item.quantity > 0 && (
<div key={item.id}>
{item.title}
<br />
<button onClick={() => decrementItem(item)}> - </button>{" "}
{item.quantity}{" "}
<button onClick={() => incrementItem(item)}> + </button>
</div>
)
)}
<h3>Total Items: {totalQuantity}</h3>
<h3>Total Cost: {`$${Number(totalCost).toFixed(2)}`}</h3>
</div>
);
}
Original Response
It sounds like you wanted the cart to update whenever Add was clicked in MenuItem.
Fixing use of onClick and onSubmit
This was part of your issue. In MenuItem you used a form and had onClick on your form submit button. Since your button has type="submit" it will fire submit event along with onSubmit handler. We can simply use onSubmit as our handler here and remove the onClick from the button.
I simplified MenuItem to update and read quantity value from state. Then when adding the item we simply pass the item (since it already has the up-to-date quantity).
Your logic was basically there. I gave each product an id to simplify keeping track with all the prop drilling versus using title or key as it was just a bit easier for me to wrap my head around. Hopefully the changes and comments make sense.
Example/Demo (Click to view)
https://codesandbox.io/s/update-cart-example-veic1h
import "./styles.css";
import React, { useState, createContext, useContext, useEffect } from "react";
const CartContext = createContext();
export default function App() {
const [cartItems, setCartItems] = useState([]);
const [totalQuantity, setTotalQuantity] = useState(0);
const [totalCost, setTotalCost] = useState(0);
useEffect(() => {
setTotalQuantity(() => {
return cartItems.reduce((acc, item) => {
return item.quantity + acc;
}, 0);
});
setTotalCost(() => {
return cartItems.reduce((acc, item) => {
return item.quantity * item.price + acc;
}, 0);
});
}, [cartItems]);
function addItemToCart(newItem) {
const existingCartItem = cartItems.findIndex((item) => {
return item.id === newItem.id;
});
setCartItems((prevItems) => {
return existingCartItem !== -1
? prevItems.map((prevItem) =>
prevItem.id === newItem.id
? {
...prevItem,
quantity: prevItem.quantity + newItem.quantity
}
: prevItem
)
: [...prevItems, newItem];
});
// the above is similar to what you have below,
// but good practice not to mutate state directly
// in case of incrementing item already found in cart...
// if (foundIndex !== -1) {
// setCartItems((prev) => {
// prev[foundIndex].quantity = item.quantity;
// return [...prev];
// });
// } else {
// setCartItems((prev) => {
// return [...prev, item];
// });
// }
}
return (
<CartContext.Provider value={{ cartItems, totalQuantity, totalCost }}>
<div className="parent-container">
<MenuItems onAddItem={addItemToCart} />
<Cart />
</div>
</CartContext.Provider>
);
}
const menuItems = [
{ title: "item 1", description: "description 1", price: 10, id: "1" },
{ title: "item 2", description: "description 2", price: 20, id: "2" },
{ title: "item 3", description: "description 3", price: 30, id: "3" }
];
function MenuItems(props) {
function handleItems(item) {
props.onAddItem(item);
}
return (
<div className="menu">
{menuItems.map((item) => {
return (
<MenuItem
title={item.title}
description={item.description}
price={item.price}
key={item.id}
// added this as prop
id={item.id}
onAdd={handleItems}
/>
);
})}
</div>
);
}
function MenuItem(props) {
const [item, setItem] = useState({
title: props.title,
quantity: 0,
price: props.price,
// included a unique item id here
id: props.id
});
// Don't need this anymore...
// function handleClick(e) {
// ...
// }
// update quantity as we type by getting as state...
function changeQuantity(e) {
e.preventDefault();
setItem((prev) => {
return {
...prev,
quantity: Number(e.target.value)
};
});
}
function handleSubmit(event) {
event.preventDefault();
props.onAdd(item);
}
return (
<div className="menu-item">
<div className="menu-content">
<h3>{props.title}</h3>
<p>{props.description}</p>
<h4>Price: ${props.price}</h4>
</div>
<form onSubmit={handleSubmit} className="add-items">
<label htmlFor="Amount">Amount</label>
<input
onChange={changeQuantity}
type="number"
name="Amount"
value={item.quantity}
/>
{/* No need for onClick on button, onSubmit already handles it */}
<button type="submit" className="btn btn-lg">
Add
</button>
</form>
</div>
);
}
function Cart() {
const cart = useContext(CartContext);
const { cartItems, totalQuantity, totalCost } = cart;
return (
<div>
<h2>Cart</h2>
<h3>Items:</h3>
{cartItems.length > 0 &&
cartItems.map(
(item) =>
item.quantity > 0 && (
<div key={item.id}>
{item.title} - quantity: {item.quantity}
</div>
)
)}
<h3>Total Items: {totalQuantity}</h3>
<h3>Total Cost: {`$${Number(totalCost).toFixed(2)}`}</h3>
</div>
);
}

Component data was gone after re rendering, even though Component was react.memo already

I have two components.
First is called: BucketTabs
Second is called:BucketForms
To have a better idea. Below pictures illustrate it.
When I switching tab, different form will be showed below.
Q: Whenever I switch from one tab to other tab, and then switch back, the content in the previous BucketForms will be gone. But, gone data are supposed to be stored into a state of that BucketForms.
In fact, I've memo the BucketForms already, so I've expected the content(data) would not be gone.
What's the problem and how could I prevent the data to be gone after switching tab.
My BucketTabs:
import { BucketForms } from '~components/BucketForms/BuckForms'
export const BucketTabs: React.FC = () => {
const items = useMemo<ContentTabsItem[]>((): ContentTabsItem[] => {
return [
{
title: '1',
renderContent: () => <BucketForms key="1" bucketCategory="1" />,
},
{
title: '2',
renderContent: () => <BucketForms key="2" bucketCategory="2" />,
},
]
}, [])
return (
<div className="row">
<div className="col">
<ContentTabs items={tabs} kind="tabs" />
</div>
</div>
)
}
BucketForms
function PropsAreEqual(prev, next) {
const result = prev.bucketCategory === next.bucketCategory;
return result;
}
interface IData {
portfolioValue?: number
}
export const BucketForms: React.FC<IProps> = React.memo(props => {
const { bucketCategory } = props
const [data, setData] = useState<IData>({
})
const view = ({
portfolioValue,
}: IData) => {
return (
<>
<div className="row portfolio">
<FormNumericInput
key="input-portfolio-value"
name="portfolioValue"
required
value={portfolioValue}
/>
</div>
</>
)
}
return (
<Form
onChange={e => {
setData({ ...data, ...e, })
}}
>
{view(data)}
</Form>
)
}, PropsAreEqual)

Reseting a store object to a default value in SolidJS

How would you completely reset a value in a store in SolidJS
I have something akin to:
interface Item { id: number, price: number, quantity: number }
​interface State { items: Array<Item> }
export const ItemsContext = createContext()
export const ContextProvider = (props: any) => {
const [state, setState] = createStore({items: []})
const incrementItemQuantity = ({id}: Item) => {
const index = state.items.findIndex(i => i.id === id)
if(index !== -1) {
const item = state.items[index]
setState("items", index, {quantity: item.quantity + 1})
}
}
const clearItems = () => {
setState(produce(s => s.items = []))
}
const addItem = (item: Item) => {
setState(produce(s => s.items.push(item))
}
const value = [state, { addItem, clearItems, incrementItemQuantity} ]
return (
<ItemsContext.Provider value={value} >
{ props.children }
<ItemsContext.Provider/>
)
}
Adding an item and incrementing its quantity works as expected.
When I:
Add an item.
Increment its quantity
Clear the items
I expect the state to be blank. However, If I add an item with the same properties as the first to the list, it is displayed with the old values.
I can't figure out why. What am I not doing right ?
You are not using the store API correctly. For example, the item path gives you the item, you should get the item first, than update it through a setter:
setState("items", index, {quantity: item.quantity + 1});
Here is how you can do it correctly:
// Here path gives us the item
setState("items", index, item => ({...item, quantity: item.quantity + 1}));
// Here path gives us the property
setState('items', index, 'quantity', q => q + 1);
Here is how you can do it. I did not expose store but items. It is up to you.
// #refresh reload
import { createContext, JSX, useContext } from "solid-js";
import { createStore, produce } from 'solid-js/store';
import { render } from "solid-js/web";
interface Item { id: number, price: number, quantity: number }
interface Store {
items: () => Array<Item>;
add?: (item: Item) => void;
increment?: (index: number) => void;
clear?: () => void;
};
export const CartContext = createContext<Store>();
export const CartProvider = (props: { children: JSX.Element }) => {
const [store, setStore] = createStore({ items: [{ id: 0, price: 10, quantity: 1 }] })
const items = () => store.items;
const add = (item: Item) => setStore('items', items => [...items, item]);
const increment = (index: number) => setStore('items', index, 'quantity', q => q + 1);
const clear = () => setStore('items', []);
return (
<CartContext.Provider value={{ items, add, increment, clear }}>
{props.children}
</CartContext.Provider>
);
}
const Child = () => {
const { items, add, increment, clear } = useContext(CartContext);
return (
<div>
<ul>
{items().map((item, index) => (
<li>{JSON.stringify(item)} <button onclick={() => increment(index)}>inc</button></li>)
)}
</ul>
<div>
<button onClick={() => add({ id: items().length, price: 10, quantity: 1 })}>Add Item</button>
{` `}
<button onClick={() => clear()}>Clear Items</button>
</div>
</div>
)
};
const App = () => {
return (
<CartProvider>
<Child />
</CartProvider>
);
}
render(App, document.querySelector("#app"));
You are not using the store correctly. Check this live example here
import { render } from "solid-js/web";
import { createContext, useContext, For } from "solid-js";
import { createStore } from "solid-js/store";
export const CounterContext = createContext([{ items: [] }, {}]);
export function CounterProvider(props) {
const [state, setState] = createStore({ items: props.items || []});
const store = [
state,
{
add: (val) => setState("items", (c) => [...c, val]),
clear: () => setState("items", () => []),
},
];
return (
<CounterContext.Provider value={store}>
{props.children}
</CounterContext.Provider>
);
}
const Counter = () => {
const [state, { add,clear }] = useContext(CounterContext);
return <>
<For each={state.items}>
{(i) => (<h1>{i}</h1>)}
</For>
<button onClick={() => add(state.items.length + 1)}>Add </button>
<button onClick={clear}>Clear </button>
</>
};
const App = () => (
<CounterProvider>
<Counter />
</CounterProvider>
);
render(() => <App />, document.getElementById("app")!);

React TypeScript MultiSelector using checkbox

i am trying to create a search form using React typescript props event.I have acheived half of it but now stuck on an checkbox multiSelector where i have no idea how we can implement it.i have googled a lot but got nothing in return.
here is my code.
I am using common typescript props event onChange for setting all the values inside my search Api Object.
can anyone help me out with code or docs how we can acheive multiSelector checkbox for React Typescript props event.
1.here is my search for structure=>
enter code here
let columns = useMemo(
() => [
{
Header: "Name", accessor: "username",
Cell: (props: any) => {
if (authoritiesList.includes("USR-U")) {
let path = "/users/" + props.value;
return createClickableLink(path, props.value);
} else {
return <>
<span>{props.row.original.username}</span>
</>
}
},
FilterOptions: {
FilterInput:
<input type="text" placeholder="Username..." />,
overrideFilterLabel: "Username",
overrideFilterAccessor: "username"
}
},
{
Header: "Role(s)", accessor: "roles", disableSortBy: true,
Cell: (props: any) => {
return <>
{props.row.original.roles.map((role: any) => {
return (
<div>
<span>{role}</span><br/>
</div>)
})}
</>
},
FilterOptions: {
FilterSelect:
roleData.items.map((curRole:any)=>{
return (
<input type="checkbox value=
{curRole.name} />
)
})} ,
overrideFilterLabel: "Roles",
overrideFilterAccessor: "roles"
}
},
},
], [customerData,roleData]
)
enter code here
const selector = (state: any) => state.users;
return (
<div className="m-0 p-0 ">
<section id="content-wrapper">
<div className="row">
<div className="col-lg-12 ml-auto">
<Breadcrumb crumbs={crumbs}/>
<div className="table_data mt-2">
{createSearchButton()}
{authoritiesList.includes("USR-C") && createAddButton("/users/create", "Add User")}
<DataTable columns={columns}
fetchAction={userActions.getAllData as Dispatch<Action>}
exportAction={userActions.exportData as Dispatch<Action>}
selector={selector}/>
</div>
</div>
</div>
</section>
</div>
);
}
I want to handle multi selected checkbox event for this form in
Typescript. all forms input tags are working currently but multiselected checkbox is not working for brining output to the query object.
here is my typescript code.
for (let column of tableColumns) {
if (!column.FilterOptions) {
column.FilterOptions = {};
}
if (column.FilterOptions?.FilterSelect) {
column.FilterOptions.FilterSelect.props.onKeyPress = (event: KeyboardEvent) => {
event.key === 'Enter' && setApplyFilter(true);
}
column.FilterOptions.FilterSelect.props.onChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
updateFilterQuerySelect(column, filterQuery, setFilterQuery, event);
}
}
if (column.FilterOptions?.FilterInput) {
column.FilterOptions.FilterInput.props.onKeyPress = (event: KeyboardEvent) => {
event.key === 'Enter' && setApplyFilter(true);
}
column.FilterOptions.FilterInput.props.onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
updateFilterQuery(column, filterQuery, setFilterQuery, event);
}
}
}
here is function updateFilterQuery
const updateFilterQuery = (column: DataTableColumn, filterQuery: any, setFilterQuery: Function, event: React.ChangeEvent) => {
let tempQuery: any = {...filterQuery};
let key: string = column.FilterOptions?.overrideFilterAccessor || column.accessor;
let value: any = event.target.value;
if (event.target.value == "on" && event.target.checked != undefined) {
value = event.target.checked;
}
if (event.target.value == undefined) {
delete tempQuery[key];
} else {
key === 'phone' ? tempQuery[key] = getUnformattedPhoneNumber(value)
:
tempQuery[key] = value;
}
setFilterQuery(tempQuery);
}
It is a search form and similary it is working same as for other forms as well th eonly part missing in this form is now multiselector which is not working.
You have to separate selection state into a custom hook. A state is an array of selected items.
CodeSandbox
hooks.ts
import React, { useState } from "react";
export const useMultiselect = (initialValue: string[]) => {
const [selected, setSelected] = useState<string[]>(initialValue);
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
const index = selected.indexOf(value);
if (index > -1) {
setSelected([...selected.slice(0, index), ...selected.slice(index + 1)]);
} else {
setSelected([...selected, ...[value]]);
}
};
const isSelected = (value: string) => {
return selected.includes(value);
};
return { selected, isSelected, onChange };
};
App.tsx
import { useMultiselect } from "./hooks";
const data = ["Apple", "Orange", "Banana", "Pear", "Peach"];
export default function App() {
const { selected, isSelected, onChange } = useMultiselect([]);
return (
<div>
<div>Select your favorite fruites!</div>
<ul style={{ listStyleType: "none" }}>
{data.map((value) => (
<li key={value}>
<input
id={value}
type="checkbox"
value={value}
checked={isSelected(value)}
onChange={onChange}
/>
<label htmlFor={value}>{value}</label>
</li>
))}
</ul>
<div>Selected: {selected.join()}</div>
</div>
);
}

Update localStorage Value on submit in Typescript and React

I want to update values within an Array of Objects saved to my localStorage.
The Objects are already in my localStorage. I want to update the progress.
The key is skills and the value is the Array.
I have a form with two sliders on my page which tracks the time(progress). On Submit I want to update the progress.
I think I have a major misunderstanding of how this works because I don't get it to work.
[
{category: "crafting"
description: "Prepare yourself for cold times"
imageSrc: "https://images.unsplash.com/photo-1621490153925-439fbe544722?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1100&q=80"
isDone: false
progress: 500
title: "Knitting"},
{category: "mental"
description: "Take control over your mind"
imageSrc: "https://images.unsplash.com/photo-1554244933-d876deb6b2ff?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1100&q=80"
isDone: false
progress: 500
title: "Meditation"}]
This is my handleSubmit function which updates the Value. Its controlled by a form with 2 Sliders (mins) (hrs)
function handleSubmit(event: React.FormEvent) {
event.preventDefault();
const hrs = parseInt(hours);
const mins = parseInt(minutes);
const addedHours = value + hrs + mins * 0.015;
setValue(addedHours);
}
Here is a screenshot of my localStorage:
localStorage
Here is the whole code of the page:
export default function DetailPage(): JSX.Element {
const [hours, setHours] = useState('0');
const [minutes, setMinutes] = useState('0');
const [value, setValue] = useState(0);
const history = useHistory();
const { skills } = useLocalStorageSkills();
const { title }: { title: string } = useParams();
const filteredSkills = skills.filter(
(skills) => skills.title === String(title)
);
function handleSubmit(event: React.FormEvent) {
event.preventDefault();
const hrs = parseInt(hours);
const mins = parseInt(minutes);
const addedHours = value + hrs + mins * 0.015;
setValue(addedHours);
}
const ranking =
value === 0
? '0'
: value < 99
? '1'
: value > 100 && value < 199
? '2'
: value > 200 && value < 299
? '3'
: value > 300 && value < 399
? '4'
: '5';
const ranktrack = JSON.stringify(ranking);
localStorage.setItem('ranking', ranking);
const ranktrackparsed = JSON.parse(ranktrack);
localStorage.getItem('ranking');
return (
<div className={styles.container}>
{filteredSkills.map((skills) => (
<Header
{...skills}
key={skills.title}
title={skills.title}
type="detail"
imageSrc={skills.imageSrc}
onClick={() => {
history.push('/');
}}
/>
))}
<main className={styles.main}>
{filteredSkills.map((skills) => (
<ProgressTrack
value={(skills.progress - value).toFixed(1)}
rank={ranktrackparsed}
/>
))}
<form className={styles.form} onSubmit={handleSubmit}>
<Rangeslider
size="hours"
value={hours}
min={'0'}
max={'24'}
onChange={(event) => setHours(event.target.value)}
/>
<Rangeslider
size="minutes"
value={minutes}
min={'0'}
max={'59'}
onChange={(event) => setMinutes(event.target.value)}
/>
<ActionButton
children={'Submit'}
type={'submit'}
style="primary"
></ActionButton>
</form>
{filteredSkills.map((skills) => (
<ProgressBar
percentageVal={value}
textVal={value.toFixed(1)}
minValue={1}
maxValue={500}
children={skills.description}
/>
))}
</main>
<Navigation activeLink={'add'} />
</div>
);
}
Here I have a custom hook, to use localStorage:
import useLocalStorage from './useLocalStorage';
import type { Skill } from '../../types';
export default function useLocalStorageSkills(): {
skills: Skill[];
addSkill: (skills: Skill) => void;
removeSkill: (newSkill: Skill) => void;
editSkill: (oldSkill: Skill, newSkill: Skill) => void;
} {
const [skills, setSkills] = useLocalStorage<Skill[]>('skills', []);
function addSkill(skill: Skill) {
setSkills([...skills, skill]);
}
function removeSkill(newSkill: Skill) {
setSkills(skills.filter((skill) => skill !== newSkill));
}
function editSkill(deleteSkill: Skill, newSkill: Skill) {
setSkills([
...skills.filter((skill) => skill.title !== deleteSkill.title),
newSkill,
]);
}
return { skills, addSkill, removeSkill, editSkill };
}
Its my first question, If you need more information, I will do my best to provide more.

Categories

Resources