I have this object that can have multiple values inside:
const [sort, setSort] = useState({
"city": [],
"price": [],
"year": []
});
When you click "add", I want "city" to have this value for example: ["Los Angeles"]
or both ["Los Angeles", "New York"]
How can I do that?
I have tried this but it doesn't work. I'm newbie in javascript/reactjs.
import { useState, useEffect } from "react";
function Items() {
const [sort, setSort] = useState({
"city": [],
"price": [],
"year": []
});
useEffect(() => {
console.log(sort);
}, [sort])
function addItem(itemType, itemValue)
{
for (var type in sort)
{
if(type.hasOwnProperty(type))
{
if(type.toLowerCase() === itemType.toLowerCase())
{
sort.type.push(itemValue);
setSort(sort);
break;
}
}
}
}
return (
<>
<div className="item" onClick={(e) => addItem(e, "City", "Los Angeles")}>
<span>Add LA</span>
</div>
<div className="item" onClick={(e) => addItem(e, "City", "New York")}>
<span>Add NY</span>
</div>
</>
)
}
export default Items
CodePudding user response:
You can't change the state value directly. So you need to copy the state, then do changes and then updateState
change this part of code
sort.type.push(itemValue);
setSort(sort);
To this
setSort(oldData => {
const newData = {...oldData};
newData[itemType].push(itemValue);
return newData;
});
if you have nested objects, make sure to do deepClone of your object instead of spread operator
CodePudding user response:
This will sort it
function Items() {
const [sort, setSort] = useState({
city: [],
price: [],
year: []
});
useEffect(() => {
console.log(sort);
}, [sort]);
function addItem(itemType, itemValue) {
console.log("itemType", itemType, itemValue);
// check for property
if (sort?.[itemType] !== undefined) {
const items = sort?.[itemType];
if (
!items.map((e) => e.toLowerCase()).includes(itemValue.toLowerCase())
) {
// creating new array on change
setSort((s) => {
return {
...s,
// set updated property
[itemType]: [...sort?.[itemType], itemValue]
};
});
}
}
}
//Change addItem(e, "City", "Los Angeles") to addItem("city", "Los Angeles")}
return (
<>
<div className="item" onClick={(e) => addItem("city", "Los Angeles")}>
<span>Add LA</span>
</div>
<div className="item" onClick={(e) => addItem("city", "New York")}>
<span>Add NY</span>
</div>
</>
);
}
You can check the new beta docs on how to work with arrays
Hope it helps
CodePudding user response:
The easiest method maybe to take the existing state from setSort
and use that to build (using the spread syntax) a new object.
// Accept the type and value
function addItem(itemType, itemValue) {
// For convenience create a key by lowercasing the type
const key = itemType.toLowerCase();
// Take the previous state, and...
setSort(prev => {
// ...return a new object by spreading out
// that previous state, and then using the key to
// update that array but creating a new one by
// also spreading out that property into a new array
// and adding the new value to it
return {
...prev,
[key]: [ ...prev[key], itemValue ]
}
});
}
Full example. Note: you don't need to pass the event into the function if you don't use it.
const { useEffect, useState } = React;
function Items() {
const [sort, setSort] = useState({
city: [],
price: [],
year: []
});
useEffect(() => console.log(sort), [sort]);
function addItem(itemType, itemValue) {
const key = itemType.toLowerCase();
setSort(prev => {
return {
...prev,
[key]: [ ...prev[key], itemValue ]
}
});
}
return (
<main>
<div
className="item"
onClick={e => addItem("City", "Los Angeles")}>
<span>Add LA</span>
</div>
<div
className="item"
onClick={e => addItem("City", "New York")}>
<span>Add NY</span>
</div>
</main>
);
}
ReactDOM.render(
<Items />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
CodePudding user response:
Don't change state directly, React.js will not re-render if not use setState
, When you have a object like that "sort" you could consider use useReducer
.
CodePudding user response:
You have a few issues:
- First,
addItem
should be a callback, so wrap it in theuseCallback
hook. - Next, You should use the callback version of
setSort
so that you do not needsort
as a dependency - Finally, you can simplify your state setting logic (ES5/6)
Note: You should avoid using var
; stick with const
and let
import { useState, useEffect, useCallback } from "react";
const Items = () => {
const [sort, setSort] = useState({
city: [],
price: [],
year: []
});
useEffect(() => {
console.log(sort);
}, [sort]);
const addItem = useCallback((_event, itemType, itemValue) => {
setSort((currentSort) => {
const key = itemType.toLowerCase(),
existing = currentSort[key] ?? [];
return { ...currentSort, [key]: [...existing, itemValue] };
});
}, []);
return (
<>
<div className="item" onClick={(e) => addItem(e, "City", "Los Angeles")}>
<span>Add LA</span>
</div>
<div className="item" onClick={(e) => addItem(e, "City", "New York")}>
<span>Add NY</span>
</div>
</>
);
}
export default Items;
Here is a demo of the code above:
const { Fragment, useState, useEffect, useCallback } = React;
const Items = () => {
const [sort, setSort] = useState({
city: [],
price: [],
year: []
});
useEffect(() => {
console.log(JSON.stringify(sort));
}, [sort]);
const addItem = useCallback((_event, itemType, itemValue) => {
setSort((currentSort) => {
const key = itemType.toLowerCase(),
existing = currentSort[key] || [];
return { ...currentSort, [key]: [...existing, itemValue] };
});
}, []);
return (
<Fragment>
<div className="item" onClick={(e) => addItem(e, "City", "Los Angeles")}>
<span>Add LA</span>
</div>
<div className="item" onClick={(e) => addItem(e, "City", "New York")}>
<span>Add NY</span>
</div>
</Fragment>
);
};
const App = () => (
<div>
<Items />
</div>
);
ReactDOM
.createRoot(document.getElementById('root'))
.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>