Duplicate array items in Vuex state (using Socket.io) - javascript

So I have this app built in Vue and using Vuex. I connect to a Node/Express backend with Socket.Io to be able to push data from the server to client instantly when needed.
The data pushed to the clients are in the form of an object which is then stored in an array in VUEX. Each data object pushed into the array has a unique string attached to it.
This string is used to compare the objects already pushed into the array in VUEX. If there are duplicates they won't be stored. If not equal = they are stored.
I then use ...mapGetters to get the array in Vuex and loop through it. For each object a component is rendered.
HOWEVER - sometimes the same object is rendered twice even though the array in VUEX clearly only shows one copy.
Here is the code in the VUEX Store:
export default new Vuex.Store({
state: {
insiderTrades: [],
},
mutations: {
ADD_INSIDER_TRADE(state, insiderObject) {
if (state.insiderTrades.length === 0) {
// push object into array
state.insiderTrades.unshift(insiderObject);
} else {
state.insiderTrades.forEach((trade) => {
// check if the insider trade is in the store
if (trade.isin === insiderObject.isin) {
// return if it already exists
return;
} else {
// push object into array
state.insiderTrades.unshift(insiderObject);
}
});
}
},
},
getters: {
insiderTrades(state) {
return state.insiderTrades;
},
},
Here is the some of the code in App.vue
mounted() {
// //establish connection to server
this.$socket.on('connect', () => {
this.connectedState = 'ansluten';
this.connectedStateColor = 'green';
console.log('Connected to server');
});
//if disconnected swap to "disconneted state"
this.$socket.on('disconnect', () => {
this.connectedState = 'ej ansluten';
this.connectedStateColor = 'red';
console.log('Disconnected to server');
});
// recieve an insider trade and add to store
this.$socket.on('insiderTrade', (insiderObject) => {
this.$store.commit('ADD_INSIDER_TRADE', insiderObject);
});
},

Your forEach iterates the existing items and adds the new item once for every existing item. Use Array.find:
ADD_INSIDER_TRADE(state, insiderObject) {
const exists = state.insiderTrades.find(trade => trade.isin === insiderObject.isin);
if (!exists) state.insiderTrades.unshift(insiderObject);
},
You don't need the initial length check

Related

Upsert an array of objects using Supabase and Reactjs

I am new to the SQL scene and I am trying to use Supabase for a web app that I am building.
I am trying to push an array objects to the DB, but what I am finding is that it only grabs the first element in the array and adds that to the DB. The end goal functionality is that it grabs the whole array of objects and pushes that and if it detects a change (why I am using upsert) then add that object to the array.
ownedNFTs is an array of objects.
const nakedDB = () => {
try {
setLoading(true);
ownedNFTs.forEach(async (nft) => {
let { data, error, status } = await supabase.from("Users").upsert({
wallet_address: wallet?.accounts[0].address,
NFTS: {
name: nft?.metadata?.name || "",
description: nft?.metadata?.description || "",
image: nft?.metadata?.image || "",
externalUrl: nft?.metadata?.external_url || "",
contractAddress: nft?.contract.address || "",
tokenId: nft?.id.tokenId || "",
blockchain: "Ethereum",
metaverse: nft?.metadata?.name.split(" ")[0] || "",
metadata: nft?.metadata || "",
symbol: nft?.contract.symbol || "",
},
}).eq('wallet_address', wallet?.accounts[0].address);
console.log(data)
if (error && status !== 406) {
throw error;
}
});
} catch (error) {
console.table(error);
} finally {
setLoading(false);
}
};
I have tried following the Supabase documentation on bulkupserting, but that is to create multiple rows at once. I have also tried creating a standard for loop, but that didnt work as well. My current implementation is using forEach() and that is not working as expected.
The problem why it was selecting the first element (I believe) is because the .foreach() function was not running multiple times. This was not a clean way to do a DB call in any case. In addition to that I do think that the data I was sending to the DB was not in the correct format as a pure array of objects. For example when you use .map() it returns two separate objects. Supabase (in my configuration) was not expecting this format.
The solution is below.
I created an empty array
I used .map() on my array of objects
Saved the result of the .map() to the array I created.
Sent the new array to the DB in one call.
const nakedDB = async () => {
try {
ownedNFTs.map((nft) => {
// console.log(nft)
nftArray.push(nft);
});
setLoading(true);
let { data, error, status } = await supabase
.from("Users")
.upsert({
wallet_address: wallet?.accounts[0].address,
NFTS: nftArray,
})
.eq("wallet_address", wallet?.accounts[0].address);
if (error && status !== 406) {
throw error;
}
} catch (error) {
console.table(error);
} finally {
setLoading(false);
}
};
** Updated Solution **
ownedNFTs is already an array of objects in the proper format. I removed the .map() and directly saved ownedNFTs to the DB and it saves all elements. The .map() solution was me over-engineering the issue.

Mutate a result from "useResult" (Vue Apollo)

I am using the Composition API. I have a component which queries the backend and returns a list of events:
const { result, loading, error } = useQuery(FindEvents, variables, { fetchPolicy: 'cache-and-network' })
let events = useResult(result, [], data => data.events)
This works fine, however, I have a child component with a form where the user can edit the data of a single event, which is then mutated back to the backend, and emits an update to the Event list. However my function which handles the update on the list throws an error saying that it cannot update a computed value:
onEventUpdate (mutableEvent) {
try {
const index = _.findIndex(events.value, ({id}) => id === mutableEvent.id) // lodash
if (index < 0) {
events.value.push(mutableEvent);
} else {
events.value[index] = mutableEvent;
}
} catch (err) {
console.error(`Couldn't assign value: ${err}`) // TypeError: Cannot assign to read only property '12' of object '[object Array]'
}
}
Is there some way to update this property?

react redux duplicate keys even when setting a unique key

I have an array of 6 objects which have a uid and nothing else. This is so I can repeat over them and have some placeholder content until an object is ready to be added into the array. I set a unique key when a new object is selected. However if I select the same object twice, even though I'm setting a unique key. It seems to update the unique key on the duplicate item (even though the unique key is different).
Might be easier to see the code/app in action here, an example of the problem would be clicking squirtle then blastoise, take a note of the uid's shown. Then click squirtle again and for some reason it updates the old squirtle with the new squirtles uid causing a duplicate key error. https://codesandbox.io/s/l75m9z1xwq or see code below. Math.random is just placeholder until I can get this working correctly.
const initState = {
party: [
{ uid: 0 },
{ uid: 1 },
{ uid: 2 },
{ uid: 3 },
{ uid: 4 },
{ uid: 5 }
]
};
When I click on something this is triggered:
handleClick = pokemon => {
// setup a uid, will need a better method than math.random later
pokemon.uid = Math.random();
this.props.addToParty(pokemon);
};
This then calls a dispatch which triggers the following reducer. Which essentially just checks if the object has no normal ID then replace the content with the payload sent over. It does this but also somehow updates any previous objects with the same uid even though the if statement does not run against them.
const rootReducer = (state = initState, action) => {
if (action.type === "ADD_POKEMON") {
let foundFirstEmptyPoke = false;
const newArray = state.party.map((pokemon, index) => {
if (typeof pokemon.id === "undefined" && foundFirstEmptyPoke === false) {
foundFirstEmptyPoke = true;
pokemon = action.payload; // set the data to the first object that ios empty
}
// if we get to the last pokemon and it's not empty
if (index === 5 && foundFirstEmptyPoke === false) {
pokemon = action.payload; // replace the last pokemon with the new one
}
return pokemon;
});
return {
party: newArray
};
}
return state;
};
The problem here is that, when you click to select a pokemon, you mutate the data you retrieved from the API:
handleClick = pokemon => {
pokemon.uid = Math.random(); // HERE
this.props.addToParty(pokemon);
};
You actually mutate the react state. What you should do is clone your pokemon data object, add an uid to the clone you just generated and update your redux state with it:
handleClick = pokemon => {
this.props.addToParty({
...pokemon,
uid: Math.random()
});
};
That way, no references to the actual react state are kept. Because that was what was happening when you say it updates the old squirtle with the new squirtles uid. When you tried to add another pokemon, you updated the data you retrieved from your API which was also referenced from your first pokemon slot (from your redux state).
In react/redux it's always better to not mutate objects:
this.props.addToParty({...pokemon, uid: Math.random()});
You are mutating the state. Use spread syntax *** to copy the state before updating.
return {
...state,
party: newArray
}

How to save the objects uid when I push the data in firebase database list, using javascript

I want to save the uid when I push new data in Firebase database. This is possible not for auth users but for data objects.
For example, I want this object schema:
"-Kdsfdsg555555fdsgfsdgfs" : { <------- I want this id
Id : "Kdsfdsg555555fdsgfsdgfs", <--- How to put that inside
name : "A",
time_updated : "6/6/2017"
}
Is any way how to get this id and pushed inside the object?
My code is as follows:
categories: FirebaseListObservable<any[]>;
this.categories = this.db.list('/categories') as FirebaseListObservable<Category[]>;
addCategory(category: any) {
return this.categories.push(category).then( snap => {
this.openSnackBar('New category has been added', 'ok');
}).catch(error => {
this.openSnackBar(error.message, 'ok');
});
}
There are two ways to use Firebase's push() method:
By passing in an argument, it will generate a new location and store the argument there.
By passing in no arguments, it will just generate a new location.
You can use the second way of using push() to just get the new location or its key:
addCategory(category: any) {
var newRef = this.categories.push();
category.Id = newRef.key;
newRef.set(category).then( snap => {
this.openSnackBar('New category has been added', 'ok');
}).catch(error => {
this.openSnackBar(error.message, 'ok');
});
}
But note that it's typically an anti-pattern to store the key of an item inside that item itself too.

Updating json with lodash

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 =)

Categories

Resources