So I want to update the previous properties of my state with array of objects.Initially I made only two objects.I want to implement bookmark functionality in my moviedb app.When I click on the icon it should change color as well as change the value of isFavorited property in my useState.
const [isActive, setActive] = useState({
favorited: false,
customColor: "white"
});
const handleToggle = () => {
setActive({ ...isActive, !favorited});
if(isActive.favorited){
setActive( {...isActive, customColor: "red"});
}else{
setActive( {...isActive, customColor: "white"});
}
}
I am calling the handleToggle function on clicking a icon.How to make it so that the value of favorited toggles everytime I click it using useState?I get a error squiggle on !favorited
Writing {...isActive, !favorited} is not a valid JS syntax.
Try rewriting the function to:
const handleToggle = () => {
setActive((prevState) => {
return {
...prevState,
favorited: !prevState.favorited,
customColor: prevState.favorited ? "red" : "white",
};
});
};
You should update the state by using the prevState :-
setActive((prevState) => ({
...prevState, favorited : !favorited}
));
Related
I am making a kind of input in react.
I have the following
=> A component returning a onSelectionChange that return an array of selected element (works fine)
useEffect(() => {
onSelectionChange?.(selection.items);
}, [selection]);
=> A input that wrap the above component and contain a button save/cancel. When use click save, I want to dispatch a onChange with the latest value selected. If user click cancel, it will reset to initial value. ( I intentionally removed some of the component code to keep the problematic parts)
export const VariantsInput = forwardRef<any, any>(
({ value = [], ...rest }, ref) => {
//....
const [selectedProducts, setSelectedProducts] = useState<VariantSetDetail[]>(value);
const onValidate = useCallback(() => {
console.log(selectedProducts); // always the previous state of selectedProducts
onChange(selectedProducts);
closeDialog(dialogId);
}, [selectedProducts]);
useEffect(() => {
console.log(selectedProducts); // always the latest state of selectedProducts
}, [selectedProducts]);
//...
<VariantSelector
selectedSku={value}
onSelectionChange={(selection) => {
setSelectedProducts(() => [
...selection.map((selected) => {
const alreadySelected = value.find(
(product) =>
product.productVariantId === selected.productVariantId
);
return alreadySelected ? alreadySelected : selected;
}),
]);
}}
/>
<button onClick={onValidate}> Save</button>
//....
The issue is selectedProducts is always the previous state in the onValidate. If I check all my events onSelectionChange, or put log inside the effect and the root of the component to display the state, it contains exactly what I want. But in the on validate, it is always []....
I am really confused as to why.
I am getting this error:
Actually, I am trying to change the button text for a few seconds when I click on the button.
This is basically for Add To Cart Button, I want to hold the button process for a few seconds for my API call in the same function.
My Code:
const AddToCart = async (sku) => {
this.setState({ buttonText: "Loading..." });
setTimeout(() => {
this.setState({ buttonText: "Saved" });
}, 5000);
}
const [value, setvalue] = useState([]);
const initialState = "ADD TO CART";
const [buttonText, setButtonText] = useState("ADD TO CART");
render() {
return (
<a onClick={()=>{AddToCart(item.sku)}} className="add-to-cart-btn">
<i className="fa fa-solid fa-cart-shopping"></i>{buttonText}</a>
);
}
My Final code is what I am trying to do.
const AddToCart = async (sku) => {
this.setState({ buttonText: "Loading..." });
const user_id = localStorage.getItem('card_id');
let product_details = {
sku_name: sku,
qty: 1,
quote_id: user_id
}
setTimeout(() => {
this.setState({ buttonText: "Saved" });
}, 5000);
dispatch(useraction.addtocartRequest(product_details));
}
I am new to this technology. Any other suggestions also help me a lot.
Thank you very much for your consideration! :-)
Here you are using this.setState which is used for class-based components but you are using functional-based component so instead of this.setState simply write setButtonText('Loading...')
https://reactjs.org/docs/hooks-intro.html
you should use react hooks instead of this.setState, here how to do it:
const [buttonText, setButtonText] = useState("ADD TO CART");
// buttonText is value getter
// setButtonText is value setter
so if you want to update the value of you state:
setButtonText(newValue)
I created a custom hook to store Objects in a useState hook and allow changing properties without loosing the other entries.
const useObject = initialValue => {
const [state, setState] = useState(initialValue);
return [
state,
newState => {
setState({
...state,
...newState
});
}
];
};
This hook works in my component but doesn't when I assign it to my context.
Here is what I did:
I created a context:
export const navigation = createContext();
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/store.js:40-83
I created a useObject variable and assigned it as value to my Context Provider
<navigation.Provider value={useObject()}>
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/Layout.js:234-284
I load the context via useContext and change its value
const [navigationState, setNavigationState] = useContext(navigation);
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js:476-616
Result:
The context always stores the new entry and removes all existing entries.
Anyone knows why ?
Here is the Sandbox link. You can test it by clicking the filter button. I expected to see {search:true, icon: 'times'} as context value. Thx!
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js
There is one important things to note here. useEffect in App.js is run once and hence the onClick function set with setNavigationState will use the values from its closure at the point at which it is defined i.e initial render.
Due to this, when you call the function within Header.js from context's the value along with the localState are being reset to the initial value.
SOLUTION 1:
One solution here is to use callback approach to state update. For that you need to modify your implementation on useObject a bit to provide the use the capability to use the callback value from setState
const useObject = initialValue => {
const [state, setState] = useState(initialValue);
return [
state,
newState => {
if(typeof newState === 'function') {
setState((prev) => ({ ...prev, ...newState(prev)}));
} else {
setState({
...state,
...newState
});
}
}
];
};
and then use it in onContextClick function like
const onContextClick = () => {
setState(prevState => {
setNavigationState(prev => ({ icon: ICON[prevState.isOpen ? 0 : 1] }));
return { isOpen: !prevState.isOpen };
});
};
Working DEMO
SOLUTION 2:
The other simpler approach to solving the problem is to use useCallback for onContextClick and update the navigation state with useEffect, everytime the closure state is updated like
const onContextClick = React.useCallback(() => {
setNavigationState({ icon: ICON[state.isOpen ? 0 : 1] });
setState({ isOpen: !state.isOpen });
}, [state]);
useEffect(() => {
setNavigationState({
search: true,
icon: ICON[0],
onClick: onContextClick
});
}, [onContextClick]);
Working demo
I have a weird bug that only happens some of the time - onChange fires but does not change the value. Then if I click outside of the input with the onChange function, then click back inside the input box, the onChange function starts working.
The onChange function is like so:
const handleBarAmountChange = (event) => {
let newWidthAmount = event.target.value / 10;
setNewWidth(newWidthAmount);
setNewBarAmount(event.target.value);
};
A parent div is using a ref with useRef that is passed to this function:
import { useEffect, useState } from 'react';
const useMousePosition = (barRef, barInputRef, barContainerRef) => {
const [ mouseIsDown, setMouseIsDown ] = useState(null);
useEffect(() => {
const setMouseDownEvent = (e) => {
if (e.which == 1) {
if (barContainerRef.current.contains(e.target) && !barInputRef.current.contains(e.target)) {
setMouseIsDown(e.clientX);
} else if (!barInputRef.current.contains(e.target)) {
setMouseIsDown(null);
}
}
};
window.addEventListener('mousemove', setMouseDownEvent);
return () => {
window.removeEventListener('mousemove', setMouseDownEvent);
};
}, []);
return { mouseIsDown };
};
Is the onChange conflicting somehow with the eventListener?
How do I get round this?
There were a few syntax errors and missing hook dependencies that were the cause of your bugs. However, you can simplify your code quite a bit with a few tweaks.
When using state that relies upon other state, I recommend lumping it into an object and using a callback function to synchronously update it: setState(prevState => ({ ...prevState, example: "newValue" }). This is similar to how this.setState(); works in a class based component. By using a single object and spreading out it properties ({ ...prevState }), we can then overwrite one of its properties by redefining one of them ({ ...prevState, newWidth: 0 }). This way ensures that the values are in sync with each other.
The example below follows the single object pattern mentioned above, where newWidth, newBarAmount and an isDragging are properties of a single object (state). Then, the example uses setState to update/override the values synchronously. In addition, the refs have been removed and allow the bar to be dragged past the window (if you don't want this, then you'll want to confine it within the barContainerRef as you've done previously). The example also checks for a state.isDragging boolean when the user left mouse clicks and holds on the bar. Once the left click is released, the dragging is disabled.
Here's a working example:
components/Bar/index.js
import React, { useEffect, useState, useCallback } from "react";
import PropTypes from "prop-types";
import "./Bar.css";
function Bar({ barName, barAmount, colour, maxWidth }) {
const [state, setState] = useState({
newWidth: barAmount / 2,
newBarAmount: barAmount,
isDragging: false
});
// manual input changes
const handleBarAmountChange = useCallback(
({ target: { value } }) => {
setState(prevState => ({
...prevState,
newWidth: value / 2,
newBarAmount: value
}));
},
[]
);
// mouse move
const handleMouseMove = useCallback(
({ clientX }) => {
if (state.isDragging) {
setState(prevState => ({
...prevState,
newWidth: clientX > 0 ? clientX / 2 : 0,
newBarAmount: clientX > 0 ? clientX : 0
}));
}
},
[state.isDragging]
);
// mouse left click hold
const handleMouseDown = useCallback(
() => setState(prevState => ({ ...prevState, isDragging: true })),
[]
);
// mouse left click release
const handleMouseUp = useCallback(() => {
if (state.isDragging) {
setState(prevState => ({
...prevState,
isDragging: false
}));
}
}, [state.isDragging]);
useEffect(() => {
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
}, [handleMouseMove, handleMouseUp]);
return (
<div className="barContainer">
<div className="barName">{barName}</div>
<div
style={{ cursor: state.isDragging ? "grabbing" : "pointer" }}
onMouseDown={handleMouseDown}
className="bar"
>
<svg
width={state.newWidth > maxWidth ? maxWidth : state.newWidth}
height="40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
colour={colour}
>
<rect width={state.newWidth} height="40" fill={colour} />
</svg>
</div>
<div className="barAmountUnit">£</div>
<input
className="barAmount"
type="number"
value={state.newBarAmount}
onChange={handleBarAmountChange}
/>
</div>
);
}
// default props (will be overridden if defined)
Bar.defaultProps = {
barAmount: 300,
maxWidth: 600
};
// check that passed in props match patterns below
Bar.propTypes = {
barName: PropTypes.string,
barAmount: PropTypes.number,
colour: PropTypes.string,
maxWidth: PropTypes.number
};
export default Bar;
React uses SyntheticEvent and Event Pooling, from the doc:
Event Pooling
The SyntheticEvent is pooled. This means that the SyntheticEvent object will be reused and all properties will be nullified after the event callback has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way.
You could call event.persist() on the event or store the value in a new variable and use it as follows:
const handleBarAmountChange = (event) => {
// event.persist();
// Or
const { value } = event.target;
let newWidthAmount = value / 10;
setNewWidth(newWidthAmount);
setNewBarAmount(value);
};
I have a demo here
It's a simple React app with a checkbox.
I'd like to add something to state when the checkbox is selected and then remove it from the state when it's unselected.
I have it working when it's selected but how do I remove it from the state when unselected.
handleChange = (e) => {
const array = [...this.state.colors];
const index = array.indexOf(e.target.name)
if (index !== -1) {
this.setState({colors: this.state.colors.filter(() => {
color !== e.target.name
})})
}else{
this.setState({colors: e.target.name})
}
}
You can access the state of a checkbox with event.target.checked.
Previous state can be accessed with callback version of setState
Docs
handleChange = (e) => {
const checked = e.target.checked;
const selectedColor = e.target.name;
if(checked) {
this.setState(prevState => ({
colors: [...prevState.colors, selectedColor]
}));
} else {
this.setState(prevState => ({
colors: prevState.colors.filter(color => color!==selectedColor)
}));
}
}
Stackblitz