I have a form where I put a float value (1.1, 1.2, 1.9 and so on) and I want to store a bunch of them inside an array on an atom:
import { atom } from 'recoil';
export const valueState = atom({
key: 'values',
default: []
});
Whenever I write a value and it's checked that it's a double, the value gets added to valueState, however, I want to make it so if that the value I write on the form gets deleted, it also deletes the value from the valueState array. I tried by using pop, however, if I do so the program crashes. How can I do it then?
import { valueState as valueStateAtom } from '../../atoms/Atoms';
import { useSetRecoilState } from 'recoil';
const setValue = useSetRecoilState(valueStateAtom);
// The function that handles the onChange event of the form
const setNewValue = (v) => {
if (v !== '') {
const valueNumber = parseFloat(v);
if (!isNaN(valueNumber)) {
setPageValueChanged(true);
pageValue = valueNumber;
// The value gets added to valueState
setValue((prev) => prev.concat({ valueNumber, cardID }));
} else {
setPageValueChanged(false);
}
} else {
setPageValueChanged(false);
// Delete v from the atom array here
}
};
pop did not work for you because it does not return a new array (state immutability)
I think you can do a trick with filter. For example
setValue((prev) => prev.filter((value, index) => index !== prev.length - 1));
Full code
import { valueState as valueStateAtom } from '../../atoms/Atoms';
import { useSetRecoilState } from 'recoil';
const setValue = useSetRecoilState(valueStateAtom);
// The function that handles the onChange event of the form
const setNewValue = (v) => {
if (v !== '') {
const valueNumber = parseFloat(v);
if (!isNaN(valueNumber)) {
setPageValueChanged(true);
pageValue = valueNumber;
// The value gets added to valueState
setValue((prev) => prev.concat({ valueNumber, cardID }));
} else {
setPageValueChanged(false);
}
} else {
setPageValueChanged(false);
setValue((prev) => prev.filter((value, index) => index !== prev.length - 1));
}
};
One more feedback, your concat is seemingly incorrect. It's expecting to have an array param but you passed an object. The modification can be
setValue((prev) => prev.concat([{ valueNumber, cardID }]));
Related
As I am learning React, I've decided to try to create my own online store. Just after learning and implementing createContext functionality and handling, I've come across the problem of it not keeping data on page refresh/reload.
The easiest solution would probably be to store the data into the localStorage, but I can't figure out how to implement and handle the localStorage item as the whole createContext (contextValue) array and keep it updated on each context's function change.
Is there any simple solution for this in React? Or what is the best JS solution in react overall to handle such context?
import { createContext, useState } from "react";
import { productsArray, getProductData } from "./productsConfig";
export const CartContext = createContext({
items: [],
getProductQuantity: () => {},
addOneToCart: () => {},
removeOneFromCart: () => {},
deleteFromCart: () => {},
getTotalCost: () => {}
});
export function CartProvider({children}) {
const [cartProducts, setCartProducts] = useState([]);
function getProductQuantity(id) {
const quantity = cartProducts.find(product => product.id === id)?.quantity;
if (quantity === undefined) {
return 0;
}
return quantity;
}
function addOneToCart(id) {
const quantity = getProductQuantity(id);
if (quantity === 0) {
setCartProducts(
[
...cartProducts,
{
id: id,
quantity: 1
}
]
)
} else { // is in cart
setCartProducts(
cartProducts.map(
product =>
product.id === id
? { ...product, quantity: product.quantity + 1 }
: product
)
)
}
}
function removeOneFromCart(id) {
const quantity = getProductQuantity(id);
if (quantity === 1) {
deleteFromCart(id);
} else {
setCartProducts(
cartProducts.map(
product =>
product.id === id
? { ...product, quantity: product.quantity - 1 }
: product
)
)
}
}
function deleteFromCart(id) {
setCartProducts(
cartProducts =>
cartProducts.filter(currentProduct => {
return currentProduct.id !== id;
})
)
}
function getTotalCost() {
let totalCost = 0;
cartProducts.map((cartItem) => {
const productData = getProductData(cartItem.id);
totalCost += (productData.price * cartItem.quantity);
});
return totalCost;
}
const contextValue = {
items: cartProducts,
getProductQuantity,
addOneToCart,
removeOneFromCart,
deleteFromCart,
getTotalCost
}
return (
<CartContext.Provider value={contextValue}>
{children}
</CartContext.Provider>
)
}
export default CartProvider;
You should use context to manage cart internal state and use local storage to keep your cart state persistent (or you can use an session storage approach). So, use local storage to save your cartProducts array as an object
localStorage.setItem('cart', JSON.stringify(newCartProductsArray))
and update it on add/remove functions, the update function should overwrite the same key value.
To restore your state after an refresh use useEffect hook inside CartProvider.
useEffect(() => {
const savedCartState = JSON.parse(localStorage.getItem('cart'));
setCartProducts(savedCartState);
}, []);
I created a context which has a reducer that has few properties that I am keeping track of their state.
I have 2 buttons that each one sets an active tool property. I click both buttons once each time to set the the reducer property ActiveToolName.
I have an event handler that does something when one of the reducer properties is equal to a certain value.
I am printing the reducer property value every time the event handler is called. It prints it as the correct value, but then it prints previous value. What is the problem here ?? Why does the reducer value ActiveToolName keeps re rendering once with a new value and once with the previous one
Here is a chunk of the code, can someone point out my obvious mistake ?? I am new to react
import React, { useContext, useEffect, useReducer, } from "react";
import MapViewContext from "./mapview-context.js";
const eventsContext = React.createContext({
History: [],
CurrentSelection: {},
SetActiveTool: (name) => {},
ActiveToolName: "",
});
export const MyContextProvider = (props) => {
const mapViewCtx = useContext(MapViewContext);
const reducerFn = (state, action) => {
if (action.type === "SetCurrentSelection") {
let newArray = {};
newArray = { ...action.selectedFeaturesDictionary };
return {
historyArray: state.historyArray,
CurrentFeatureSelection: { ...newArray },
ActiveToolName: state.ActiveToolName
};
}
if (action.type === "ACTIVE_APP") {
let toolName = action.toolname;
switch (toolName) {
case undefined: {
return {
historyArray: state.historyArray,
CurrentFeatureSelection: { ...state.CurrentFeatureSelection },
ActiveToolName: "NULL"
};
}
case "SELECTION": {
console.log("Setting Tool To " + toolName);
return {
historyArray: state.historyArray,
CurrentFeatureSelection: {...state.CurrentFeatureSelection},
ActiveToolName: toolName
};
}
case "PANNING": {
console.log("Setting Tool To " + toolName);
return {
historyArray: state.historyArray,
CurrentFeatureSelection: {...state.CurrentFeatureSelection },
ActiveToolName: toolName
};
}
}
const [reducerTracker, dispachToolFn] = useReducer(reducerFn, {
historyArray: [],
CurrentFeatureSelection: {},
ActiveToolName: "",
});
function ActiveToolHandler(toolName){
dispachToolFn({ type: "ACTIVE_APP", toolname: toolName});
}
if (mapViewCtx.mapView !== undefined) {
mapViewCtx.mapView.on("drag", function (event) {
// In this event which handles mouse drag movement on a
//map, that is where the value keeps changing, even
// though I am setting it twice, but the console logs it
// once with the value SELECTION, and Once with PANNING
console.log(reducerTracker.ActiveToolName);
if (reducerTracker.ActiveToolName != "PANNING") {
event.stopPropagation();
}
});
}
return (
<eventsContext.Provider
value={{
History: reducerTracker.historyArray,
SetActiveTool: ActiveToolHandler
CurrentSelection: reducerTracker.CurrentFeatureSelection
ActiveToolName: reducerTracker.ActiveToolName,
}}>{props.children}
</eventsContext.Provider>
);
};
export default eventsContext;
I have from with input type number, which has an pretty simple onChange function when entered a value the last number is missed out
Example:
If 99 is type in the input box, only 9 is recorded, and when 2523 is typed in the input box 252 is been recorded, the last number is been skipped
Here is the sandbox link
inside of onChange function
const handleRequestingCost = (e, index) => {
setCostsFormValues({
...costsFormValues,
[e.target.name]: [e.target.value]
});
for (const key in costsFormValues) {
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
...
What I am missing here? Or if is this the bad way to do ?
setState is async. So you are reading costsFormValues before it is updated
import "./styles.css";
import { Button, Form } from "react-bootstrap";
import React, { useState } from "react";
export default function App() {
const [costsFormValues, setCostsFormValues] = useState({});
const [row, setRow] = useState([
{
index: 5399,
name: "user name",
quantity: "1000",
remainingQty: 2000,
requestingCost: [],
budgetedCost: [
{
key: "Labour Cost",
value: 176
},
{
key: "Material Cost",
value: 890
}
],
amountForQuantity: [
{
key: "Labour Cost",
value: 150
},
{
key: "Material Cost",
value: 570
}
]
}
]);
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
const updatedFormValues = { // added this local variable which holds latest value update
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
};
for (const key in updatedFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: updatedFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
console.log(updatedFormValues);
};
Please try to make changes to a local variable rather than setting the state object directly as it takes about 300-500ms to update after the setState() call; as it creates queues for performance optimization.
Please refer to the code snippet:
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
let TempValue = {};
TempValue = {
...TempValue,
[e.target.name]: [e.target.value]
};
for (const key in TempValue) {
// loop each array and merge into one object
const mappedData = {
[key]: TempValue[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
console.log(TempValue);
};
As setState is async, you should use the value just after mutate it, that why we have useEffect hook to do it instead
const handleRequestingCost = (e, index) => {
const mappedDataArray = [];
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
for (const key in costsFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
};
useEffect(() => {
console.log(costsFormValues);
}, [costsFormValues]);
The console will show the right value, but be careful with my snippet, your handleRequestCost do some stuff with costsFormValues you should pass it to useEffect as well
const handleRequestingCost = (e, index) => {
setCostsFormValues({
// set all cost value in each array
...costsFormValues,
[e.target.name]: [e.target.value]
});
};
useEffect(() => {
const mappedDataArray = [];
console.log(costsFormValues);
for (const key in costsFormValues) {
// loop each array and merge into one object
const mappedData = {
[key]: costsFormValues[key][0]
};
mappedDataArray.push(mappedData);
const list = [...row];
const item = list[index];
if (item) {
item.requestingCost = mappedDataArray;
setRow(list);
}
}
}, [costsFormValues]);
Something like this, but there is the index that I didnt get from you logic yet
My map dont appears in my component. I'm trying to make a carousel to show phrases and authors (one testimonial / author at time). I put the map in an array but it doesn't work. I have no idea what the best approach would be. I need a little help.
useQuoteQuery.js: (grabbing the data)
import { useStaticQuery, graphql } from 'gatsby'
export const useQuoteQuery = () => {
const data = useStaticQuery(graphql`
query QuoteQuery {
wpPage(databaseId: { eq: 13 }) {
id
ACF_HomePage {
socialProve {
testimony
author
}
}
}
}
`)
return data
}
on graphql: (it works perfectly)
Quote.js
import React, { useState, useEffect } from 'react'
import { useQuoteQuery } from '../../hooks/useQuoteQuery'
import QuoteImg from '../../images/quote.svg'
import { Content, Wrapper } from './Quote.styles'
import { BiRightArrow, BiLeftArrow } from 'react-icons/bi'
const Quote = () => {
const {
wpPage: { ACF_HomePage: data }
} = useQuoteQuery()
// edited - map return array but returns: Array(3)
// 0: {$$typeof: Symbol(react.element) ......
const quotes = data.socialProve.map(quote => {
return <li key={quote.toString()}>{quote.socialProve}</li>
})
// set interval
useEffect(() => {
const timer = window.setInterval(() => {
setActiveIndex(prev => (prev + 1 >= quotes.length ? 0 : prev + 1))
}, 5000)
return () => {
window.clearInterval(timer)
}
}, [quotes])
const [activeIndex, setActiveIndex] = useState(0)
const activeQuote = quotes[activeIndex]
const handleNextClick = () => {
setActiveIndex(prev => (prev + 1 >= quotes.length ? 0 : prev + 1))
}
const handlePrevClick = () => {
setActiveIndex(prev => prev - 1)
}
return (
<Wrapper>
<Content>
<img src={QuoteImg} alt="aspas" />
<h6>{activeQuote.testimony}</h6>
<p>{activeQuote.author}</p>
<BiLeftArrow
size="20"
className="button-arrow"
onClick={handlePrevClick}
>
Anterior
</BiLeftArrow>
<BiRightArrow
size="20"
className="button-arrow"
onClick={handleNextClick}
>
Próximo
</BiRightArrow>
</Content>
</Wrapper>
)
}
export default Quote
the result:
There is no error in the vs code terminal.
The quotes array is wrapping the array produced by the .map in an extraneous array. Remove the extra array around the result of the .map:
const quotes = data.socialProve.map((quote) => {
return <div key={quote.toString()}>{quote.socialProve}</div>;
});
import React, { Component } from 'react'
import Item from './components/item'
import './App.css'
class App extends Component {
state = { item: "", array: [], check: false }
setItem = (event) => {
this.setState({
item: event.target.value
})
}
add = () => {
let item = this.state.item;
if (item != "") {
let arr = []
arr.push(item)
this.setState({ item: "", array: arr, check: true })
}
console.log(this.state.array)
}
render() {
return ( < div > < input type = "text"
value = { this.state.item }
onChange = { this.setItem }
/ > < button onClick = { this.add } > Add < /button > {
this.state.check ? < div > {
this.state.array.map(item => < Item name = { item }
/>) } < /div >: null
} < /div > );
}
}
export default App
I actually wrote this code for building a item buying remainder.The problem is first item added successfully but after that i can'nt add more item.Every time i tried it overwrite the previously added item.
In your add function, if there is no item in the state, your are declaring arr to be a new (empty) array, and only pushing one item to it. Then, you use setState to overrwrite the current array with your new one (Which only contains one item)
To add to the array, you would need to first copy all of the items currently in it, then push onto them
add = () => {
let item = this.state.item;
if (item != '') {
this.setState(prevState => {
let arr = [...prevState.array]; // Shallow copy of the array currently stored in the state
arr.push(item);
return { item: '', array: arr, check: true };
});
}
console.log(this.state.array);
};
You are overwriting your array every time you add a new item.
Try this inside the add function:
before
let arr = []
after
let arr = this.state.array