I have a brands list array, every brand object has a brand name & products
So the first time, I pushed the new brand object to the brands list, and that's work well,
Now if I want to push a new brand to the brands list and keep the previous brand data it does not work as expected.
and it looks like this
the expected should be like this
I'm trying to use flat() but not work here
minimal reproduction here
Code
import create from 'zustand';
import {persist} from 'zustand/middleware';
import {zustandStorage} from '../utils/Storage';
export const useFavoritesStore = create(
persist(
set => ({
brandsFavoritesList: [],
updateFavList: brandData => {
set(state => ({
brandsFavoritesList:
// check if there are any brands on the list before
state.brandsFavoritesList.length > 0
? state.brandsFavoritesList.map(brand =>
// if the brand exists before pushing new products
brand.tenant === brandData?.tenant
? {
...brand,
products: [...brand.products, ...brandData.products],
}
: // if the brand does not exist before push the new brand data to the brandsFavoritesList
// Issue here...
[...state.brandsFavoritesList, brandData].flat(),
)
: [...state.brandsFavoritesList, {...brandData}],
}));
},
}),
{
name: 'favorites-local',
getStorage: () => zustandStorage,
},
),
);
It looks like you want something like
function updateBrandsFavoritesList(brandsFavoritesList, brandData) {
const brandIndex = brandsFavoritesList.findIndex((brand) => brand.tenant === brandData.tenant);
if (brandIndex < 0) {
// This is a new brand, append it to the list.
return [...brandsFavoritesList, brandData];
}
// This is an existing brand, update it.
const existingBrand = brandsFavoritesList[brandIndex];
const updatedBrand = {
...existingBrand,
products: [...existingBrand.products, ...brandData.products],
};
return brandsFavoritesList.map((brand, index) => (index === brandIndex ? updatedBrand : brand));
}
const updateFavList = (brandData) => {
if (!brandData) return;
set(({brandsFavoritesList}) => ({
brandsFavoritesList: updateBrandsFavoritesList(brandsFavoritesList, brandData),
}));
}
– no need for flat.
The complex logic of finding and updating a brand has been moved to a separate free function which should be easier to e.g. test in isolation too.
(Even better still would be to use TypeScript so errors would be more obvious...)
Related
I got a situation where I do not have the experience to know which method is the best and what im doing wrong. The situation is as following:
I got a page with products which have a input + order button, which will add the order to the shoppingcart. My thought was to first set the state for each order you make:
const [amountItem, setAmountItem] = useState({
product: {
id: '',
amount: ''
}
});
Updating:
function handleChange(evt, id) {
const value = evt.currentTarget.value;
setAmountItem({
...amountItem,
product:{
id: id,
amount: value
}
});
console.log(amountItem);
}
Which then I push to the shoppingcart/checkout page (no modal):
if (e.target[0].value < productItem.stock) {
history.push({
pathname: `/winkelwagen/`,
state: {data: amountItem}
});
On this page, i first check if location.state exists before using the shoppingcart component:
if (location.state !== null && shoppingCartItems === '') {
console.log(location.state.data);
setShoppingCartItems(location.state.data);
setShoppingCartActive(true);
let cartString = JSON.stringify(shoppingCartItems);
localStorage.setItem('shopping_carts', cartString)
}
When it does exist, some product is ordered with an amount and must be set to localstorage, the product is 'always' visible when refreshing, etc. Until this point it works, the localstorage item exists:
(key)shopping_carts (value){"product":{"id":3,"amount":"2"}}
After that comes the shoppingcart component:
<ShoppingCart
shoppingCartItems={shoppingCartItems}
setShoppingCartItems={setShoppingCartItems}
shoppingCartActive={shoppingCartActive}
setShoppingCartActive={setShoppingCartActive}
/>
This is where my problem starts. Long story short, it only shows the single item from the state, which obviously will be gone.
In this file I got a useEffect part for the localstorage:
useEffect(() =>{
let shoppingCart = localStorage.getItem("shopping_carts");
console.log('shoppingcartitems ');
shoppingCart = JSON.parse(shoppingCart);
console.log(shoppingCart);
if (shoppingCart !== "") {
const id = shoppingCartItems.id;
const amount = shoppingCartItems.amount;
//setShoppingCartItems(shoppingCart)
setShoppingCartItems(prevState => ({
...prevState,
product: {
...shoppingCartItems,
id: id,
amount: amount
}
}))
}
}, [setShoppingCartItems])
The output for 'shoppingCart' is <empty string>. Why is that? Is the format wrong? I'm also using the localstorage for other info, which works fine. I know the setShoppingCartItems is not correct for multiple values, but I wanted to test this single entry first.
Update:
const CheckoutPage = () => {
const location = useLocation();
const [shoppingCartItems, setShoppingCartItems] = useState('');
const [shoppingCartActive, setShoppingCartActive] = useState(false);
const [mode, setMode] = useState('init');
let savedShoppingCart = JSON.parse(localStorage.getItem("shopping_carts"));
console.log('saved shopping cart: ')
console.log(savedShoppingCart);
if (savedShoppingCart !== "" && mode === 'init') {
const id = savedShoppingCart.id;
const amount = savedShoppingCart.amount;
//setShoppingCartItems(shoppingCart)
setShoppingCartItems(prevState => ({
...prevState,
product: {
...shoppingCartItems,
id: id,
amount: amount
}
}))
setMode('data');
//setShoppingCartActive(true);
}
if (location.state !== null && shoppingCartItems === '') {
console.log(location.state.data);
setShoppingCartItems(location.state.data);
setShoppingCartActive(true);
let cartString = JSON.stringify(shoppingCartItems);
localStorage.setItem('shopping_carts', cartString)
}
return (
<div className="shoppingCartPage">
<ShoppingCart
shoppingCartItems={shoppingCartItems}
setShoppingCartItems={setShoppingCartItems}
shoppingCartActive={shoppingCartActive}
setShoppingCartActive={setShoppingCartActive}
/>
</div>
)
}
So basically I want to do 3 things here:
Get the data from the localstorage item
Is there a saved localstorage item? Add it to existing shoppingCartItems (prevstate)
Save the updated (or new when no localstorage item exists) shoppingCartItems after that
After that I want to pass the data to the shoppingcart where i can increase/decrease items or remove/splice the values.
Treat useEffect with caution as an eventListener on React state.
Therefore you need to specify in the dependency array everything might change, in order to trigger the useEffect callback.
In your useEffect dependencies, where you are updating your shoppingCartItems, you have added only setShoppingCartItems - which I assume that its a setState function. This results in your useEffect te be called only once at the app start because setState functions never change.
So, to have your shoppingCartItems updated via useEffect you need to add it to dependencies.
useEffect(() => {
// your code
}, [setShoppingCartItems, shoppingCartItems])
This may fix your problem, because you never call logic that saves update shopping cart state, the second time, therefore you get empty in your console log.
I have a react hooks function that has a state object apiDATA. In this state I store an object of structure:
{
name : "MainData", description: "MainData description", id: 6, items: [
{key: "key-1", name : "Frontend-Test", description: "Only used for front end testing", values: ["awd","asd","xad","asdf", "awdr"]},
{key: "key-2", name : "name-2", description: "qleqle", values: ["bbb","aaa","sss","ccc"]},
...
]
}
My front end displays the main data form the object as the headers and then I map each item in items. For each of these items I need to display the valuesand make them editable. I attached a picture below.
Now as you can see I have a plus button that I use to add new values. I'm using a modal for that and when I call the function to update state it does it fine and re-renders properly. Now for each of the words in the valuesI have that chip with the delete button on their side. And the delete function for that button is as follows:
const deleteItemFromConfig = (word, item) => {
const index = apiDATA.items.findIndex((x) => x.key === item.key);
let newValues = item.value.filter((keyWord) => keyWord !== word);
item.value = [...newValues];
api.updateConfig(item).then((res) => {
if (res.result.status === 200) {
let apiDataItems = [...apiDATA.items];
apiDataItems.splice(index, 1);
apiDataItems.splice(index, 0, item);
apiDATA.items = [...apiDataItems];
setApiDATA(apiDATA);
}
});
};
Unfortunately this function does not re-render when I update state. And it only re-renders when I update some other state. I know the code is a bit crappy but I tried a few things to make it re-render and I can't get around it. I know it has something to do with React not seeing this as a proper update so it doesn't re-render but I have no idea why.
It is not updating because you are changing the array items inside apiDATA, and React only re-render if the pointer to apiDATA changes. React does not compare all items inside the apiDATA.
You have to create a new apiDATA to make React updates.
Try this:
if (res.result.status === 200) {
let apiDataItems = [...apiDATA.items];
apiDataItems.splice(index, 1);
apiDataItems.splice(index, 0, item);
setApiDATA(prevState => {
return {
...prevState,
items: apiDataItems
}
});
}
Using splice isn't a good idea, since it mutates the arrays in place and even if you create a copy via let apiDataItems = [...apiDATA.items];, it's still a shallow copy that has original reference to the nested values.
One of the options is to update your data with map:
const deleteItemFromConfig = (word, item) => {
api.updateConfig(item).then((res) => {
if (res.result.status === 200) {
const items = apiDATA.items.map(it => {
if (it.key === item.key) {
return {
...item,
values: item.value.filter((keyWord) => keyWord !== word)
}
}
return item;
})
setApiDATA(apiData => ({...apiData, items});
}
});
}
I am getting data from word press API then I structure the data inside the map function. I am not able to set state as array of objects inside map function I
get Arrays of different objects or Objects of different objects but not a single
Array of Objects
const rsp = response.data;
const map = rsp && rsp.map(item => {
let struct = {};
let data = [];
const id = item.id;
const title = item.title['rendered'];
const image = item['_embedded']['wp:featuredmedia'][0].source_url;
const banner = item.ACF.banner_image.url;
const products = item.ACF.celebrity_products;
let store={
id:id,
title:title,
image:image,
banner:banner,
products:products
};
setRes(prevState => {
// struct['data'] =store;
// data.push(store);
return{ ...prevState,
id: store.id,
name: store.title,
uri: store.image,
banner: store.banner,
products: store.products
}})
});
I think you'd want more something like :
const [items, setItems] = useState([])
rsp && setItems(rsp.map(item => ({
id: item.id,
name: item.title['rendered'],
uri: item['_embedded']['wp:featuredmedia'][0].source_url,
banner: item.ACF.banner_image.url,
products: item.ACF.celebrity_products
})))
I think you didn't provide the complete story of you code. but I think we can help.
if you intend to have the result of your processing as an array inside the map variable then you should've returned something inside it like
let oddNumber = [ 1, 3, 5 ];
const map = oddNumber.map( oneOddNumber => { return oneOddNumber * 2; } );
// now map is [ 2, 6, 10 ]
or if you intend to save the array you want using setRes function then you should've done something like
setRes(prevState => ({
...prevState,
theResultArray: [...prevState.theResultArray, { id: store.id, ..... } ]
}))
and now you state have the property name theResultArray which is an array of object which you've iterated on.
hope you can provide more complete code so help can be more clear next time :D
above i have the picture of my arrays, below code i have mapped 2 sets of array .Purpose of this function is to make '_polygons' empty array. i have a rest service which is 'this.siAsset.updateComponent(_id, _polygons)' that is designed to update each asset. only thing is, i cant seem to pass an array of data to the update component service i have, its designed to take one id and one object as a parameter (if there is a way around , please provide) as you can see by the picture, both _id and _polygons have arrays of id's an arrays of objects. Question is, how can i loop and match to call the rest to go through each id and object instead of calling by writing the code 9 times like this.siAsset.updateComponent(_id[0], _polygons[0]) ...this.siAsset.updateComponent(_id[9], _polygons[9])
deleteAllZones = () => {
let assetVal = this.asset.$$state.value
console.log('tenant', assetVal)
let _polygons = assetVal.map(function (a) {
a.polygon = []
return a
})
console.log('a',_polygons)
let _id = assetVal.map(function (a) {
let id = a.id
return id
})
console.log('id',_id)
let message = this.$filter('translate')('SI-MESSAGES.DELZONE-MSG');
let title = this.$filter('translate')('SUBHEADER.DELZONE-TITLE');
let button = this.$filter('translate')('BUTTON.DELETE');
this.siAlertDialog.confirm(message, title, button)
.then(() => {
this.siAsset.updateComponent(_id, _polygons).then(() => {
this.toastIt.simple(this.$filter('translate')('SI-MESSAGES.ZONE-DELETED'))
}, (err) => {
this.siAlertDialog.error();
}
)
})
}
Your code snippet doesn't work. I've made dummy entries. This should give you an idea. Basically, you need to loop over one array and use the index to find corresponding items in the second
// Mocking data here
let assetVal = [{
id: 1
},
{
id: 2
},
{
id: 3
}
]
let _polygons = assetVal.map(function(a) {
a.polygon = []
return a
})
//console.log('a', _polygons)
let _id = assetVal.map(function(a) {
let id = a.id
return id
})
//console.log('id', _id)
// Mocking done
let updateComponent = (id, polygon) => {
return new Promise((resolve, reject) => {
resolve({
id,
polygon
})
})
}
_id.forEach((id, i) => {
updateComponent(id, _polygons[i]).then(result => {
console.log(result)
})
})
Also, by looking at your code, it doesn't look like your updateComponent method needs id to be passed as a parameter as it is already present in the polygon as well. Maybe you want to refactor that.
Actually I need to handle mysite frontend fully with json objects(React and lodash).
I am getting the initial data via an ajax call we say,
starred[] //returns empty array from server
and am adding new json when user clicks on star buton it,
starred.push({'id':10,'starred':1});
if the user clicks again the starred should be 0
current_star=_findWhere(starred,{'id':10});
_.set(curren_star,'starred',0);
but when doing console.log
console.log(starred); //returns
[object{'id':10,'starred':0}]
but actually when it is repeated the global json is not updating,while am performing some other operations the json is like,
console.log(starred); //returns
[object{'id':10,'starred':1}]
How to update the global , i want once i changed the json, it should be changed ever.Should I get any idea of suggesting some better frameworks to handle json much easier.
Thanks before!
Working with arrays is complicated and usually messy. Creating an index with an object is usually much easier. You could try a basic state manager like the following:
// This is your "global" store. Could be in a file called store.js
// lodash/fp not necessary but it's what I always use.
// https://github.com/lodash/lodash/wiki/FP-Guide
import { flow, get, set } from 'lodash/fp'
// Most basic store creator.
function createStore() {
let state = {}
return {
get: path => get(path, state),
set: (path, value) => { state = set(path, value, state) },
}
}
// Create a new store instance. Only once per "app".
export const store = createStore()
// STARRED STATE HANDLERS
// Send it an id and get back the path where starred objects will be placed.
// Objects keyed with numbers can get confusing. Creating a string key.
const starPath = id => ['starred', `s_${id}`]
// Send it an id and fieldId and return back path where object will be placed.
const starField = (id, field) => starPath(id).concat(field)
// import to other files as needed
// Add or replace a star entry.
export const addStar = item => store.set(starPath(item.id), item)
// Get a star entry by id.
export const getStar = flow(starPath, store.get)
// Get all stars. Could wrap in _.values() if you want an array returned.
export const getStars = () => store.get('starred')
// Unstar by id. Sets 'starred' field to 0.
export const unStar = id => store.set(starField(id, 'starred'), 0)
// This could be in a different file.
// import { addStar, getStar, getStars } from './store'
console.log('all stars before any entries added:', getStars()) // => undefined
const newItem = { id: 10, starred: 1 }
addStar(newItem)
const star10a = getStar(10)
console.log('return newItem:', newItem === star10a) // => exact match true
console.log('star 10 after unstar:', star10a) // => { id: 10, starred: 1 }
console.log('all stars after new:', getStars())
// Each request of getStar(10) will return same object until it is edited.
const star10b = getStar(10)
console.log('return same object:', star10a === star10b) // => exact match true
console.log('return same object:', newItem === star10b) // => exact match true
unStar(10)
const star10c = getStar(10)
console.log('new object after mutate:', newItem !== star10c) // => no match true
console.log('star 10 after unstar:', getStar(10)) // => { id: 10, starred: 0 }
console.log('all stars after unstar:', getStars())
I think the problem is in mutating original state.
Instead of making push, you need to do the following f.e.:
var state = {
starred: []
};
//perform push
var newItem = {id:10, starred:1};
state.starred = state.starred.concat(newItem);
console.log(state.starred);
//{ id: 10, starred: 1 }]
var newStarred = _.extend({}, state.starred);
var curr = _.findWhere(newStarred, {id: 10});
curr.starred = 0;
state = _.extend({}, state, {starred: newStarred});
console.log(state.starred)
//{ id: 10, starred: 0 }]
To solve this in a more nice looking fashion, you need to use either React's immutability helper, or ES6 stuff, like: {...state, {starred: []}} instead of extending new object every time. Or just use react-redux =)