Storing associative data by ID - javascript

I've got a lot of (user) objects which come from a socket. On every new connection I get a new user object, but on every disconnect I just got the ID of the recently disconnected user.
What's the best way to store these user objects client-side?
Already tried something like this (pseudo code without sockets and just for testing)
var connectedUsers = [];
connectedUsers[12] = {
id: 12,
name: "Test",
// ...
};
connectedUsers[29] = {
id: 29,
name: "Test 2"
};
Kinda works - but now I've got an array with lots of empty spaces. The upside is that it would be easy to remove a user just by his ID.
Using an object to store the objects probably won't be the right choice, since I don't have numeric indexes.

Use an object. The former indices are converted to strings and taken as properties for the object.
var connectedUsers = {};
Working example:
var connectedUsers = {};
connectedUsers[12] = { id: 12, name: "Test", };
connectedUsers[29] = { id: 29, name: "Test 2" };
document.write('<pre>' + JSON.stringify(connectedUsers, 0, 4) + '</pre>');

Related

Push an object into an array inside an object [duplicate]

This question already has answers here:
How can I get the full object in Node.js's console.log(), rather than '[Object]'?
(19 answers)
Closed 1 year ago.
First of all, I'm new in node js. I'm trying to create a REST API which looks like this:
[{
id: 1,
name: 'tushar hasan',
email: 'mtushar**#gmail.com',
devices: {
id: 2,
device_mac: '4A:34:ER:34:12',
relays: [
{
relay_name:"r1",
status:0
},
{
relay_name:"r2",
status:1
}
]
}
}]
But no matter what I'm trying it keeps on showing me below output:
[
{
id: 1,
name: 'tushar hasan',
email: 'mtushar**#gmail.com',
devices: {
id: 2,
device_mac: '4A:34:ER:34:12',
relays: [Array]
}
}
]
I'm providing you source code that's in below:
var x = [];
var data = {id :1, name:'tushar hasan', email:'mtushar**#gmail.com'};
var relay1 = {relay_name:'r1', status:0};
var relay2 = {relay_name:'r2', status:1};
var devices = {id:2, device_mac: '4A:34:ER:34:12'};
devices.relays = [];
devices.relays.push(relay1);
devices.relays.push(relay2);
data.devices = devices;
x.push(data);
console.log(JSON.stringify(x));
console.log(x);
By the way, when I call JSON.stringify(x), it provides me elements inside the relay array property. But without JSON.stringify(x) it doesn't show the elements inside relay property.
Thanks in advance.
Your code is fine. By default, when converting an object to string (like console.log does), the nested arrays will get converted to the text [Array]). The fact that you see the correct output when you call JSON.stringify asserts that it's really there.

What's the best way (ES6 allowed) to extract values from an array and convert them to a string?

I'm trying to take an array like so:
location: [
{Id: "000-000", Name: "Foo"},
{Id: "000-001", Name: "Bar"},
..etc
]
What's the most efficient/cleanest way to pull out the Ids and combine them into a single string while also appending in front of each value a static string ("&myId=")?
More succinctly, what's the most efficient way to turn the above array into the following end-result:
&myId=000-000&myId=000-001
As stated in the title, ES6 is acceptable to use if it offers the best method for accomplishing this.
Use reduce, extracting each Id:
const location2 = [{Id: "000-000", Name: "Foo"}, {Id: "000-001", Name: "Bar"}];
console.log(
location2.reduce((a, { Id }) => `${a}&myId=${Id}`, '')
);
While this is pretty clean and only requires iterating over each item once, in terms of efficiency, for loops are still more performant if you have a huge number of items in the array:
const location2 = [{Id: "000-000", Name: "Foo"}, {Id: "000-001", Name: "Bar"}];
let output = '';
for (let i = 0, { length } = location2; i < length; i++) {
output += '&myId=' + location2[i].Id;
}
console.log(output);
In this particular case, it looks like you’re trying to concatenate URL parameters.
You can iterate over the location array and use the appropriate set of APIs for this: URLSearchParams and URL.
In particular, you’re looking for the append method, which allows mapping multiple value to the same key.
const params = new URLSearchParams(),
locationArray = [
{
Id: "000-000",
Name: "Foo"
},
{
Id: "000-001",
Name: "Bar"
}
];
locationArray.forEach(({ Id }) => params.append("myId", Id));
console.log("Result as a string:", String(params));
console.log(`Explicitly calling \`String\` is usually not needed, since ${params} can just be interpolated, concatenated, or coerced to a String like this.`);
console.log("Result inside a URL:", String(Object.assign(new URL(location), { search: params })));
console.log("Result as a URLSearchParams object (look in the browser console (F12) for better formatting):", params);
But in general, using map and join seems efficient enough.
const staticString = "&myId=",
locationArray = [
{
Id: "000-000",
Name: "Foo"
},
{
Id: "000-001",
Name: "Bar"
}
],
result = locationArray.map(({ Id }) => staticString + Id).join("");
// Or:
// result = staticString + locationArray.map(({ Id }) => Id).join(staticString);
console.log(result);
In the alternative, the first staticString may also be changed to "?myId=", since this looks like query parameters.
But it’s important to use the URLSearchParams API if you’re actually using URL parameters, so that the data is correctly encoded.
Try both approaches with one of the Ids having the value "1&myId=2" and you’ll quickly notice the benefit of the URLSearchParams API.
This API also needs to be used to decode everything again.

Am I updating the React state correctly?

In my React state, I have the state:
this.state = {
users: [{
id: 1,
name: "john",
age: 27
}, {
id: 2,
name: "ed",
age: 18
}, {
id: 3,
name: "mel",
age: 20
}]
}
I am rendering the name correctly. When you click on the name, it should remove the name, which will need an onClick that takes in a function and that returns a removeUser function.
It is my understanding that you do not want to mutate the state in React, but return a new state. So, in my removeUser function, I did:
removeUser(index) {
// Making a new copy of the array
const users = [...this.state.people].splice(index, 1);
this.setState({ users });
}
I bind my method with this.removeUser = this.removeUser.bind(this).
When I tested out my code, I am removing the users as expected. However, when I run my code against a test that my friend wrote, I got a failed test that said: Expected 1 to be 0
That message tells me that I must be mutating the state somehow, but I am not sure how. Am I returning a new array and updating the state correctly? Can someone explain to me how I should update my state correctly in this case?
Here is the full code:
class Group extends Component {
constructor(props) {
super(props);
this.state = {
users: [
{id: 1, name: "john", age: 27},
{id: 2, name: "ed", age: 18},
{id: 3, name: "mel", age: 20}
]
}
this.removeUser = this.removeUser.bind(this);
}
removeUser(index) {
const users = [...this.state.users].splice(index, 1);
this.setState({ users });
}
render() {
const list = this.state.users.map((user, i) => {
return (
<div onClick={() => this.removeUser(i)} key={i}>{user.name}</div>
);
});
return (
<div>
{list}
</div>
);
}
}
you could also change your onClick and pass it the user.id instead of the index. that would allow you to filter the results instead of needing to splice the array.
onClick={() => this.removeUser(user.id)}
removeUser(id) {
const users = this.state.users.filter((user) => user.id !== id);
this.setState({ users });
}
const users = [...this.state.users].splice(index, 1)
looks like it should be removing an item from a collection, which I suppose it technically is. The problem is that users doesn't contain the list of users you want to keep.
Instead, splice has modified your new array in-place and then returned the removed items:
splice docs
Return value
An array containing the deleted elements. If only one element is removed, an array of one element is returned. If no elements are removed, an empty array is returned.
Instead, if you'd like to use splice, create a new array:
const users = [...this.state.users]
and then splice the new array:
users.splice(index, 1)
You'll run into a similar issue if you need to sort an array.
The issue of modifying data in-place is generally frowned upon for react, in favor of immutable references. This is because React uses a lot of direct comparison of objects as a heuristic approach to speed things up. If your function modifies an existing object instance, React will assume the objects haven't changed.
The act of copying to a new object and then operating on the data comes with some tradeoffs. For removing a single item, it's negligible. For removing many items, you may be better served by an alternative method, such as multiple slice calls:
const users = [
...this.state.users.slice(0, index)
...this.state.users.slice(index + 1)
]
However this too is quite verbose.
Another approach is to use an immutable variant of splice:
// this quick example doesn't handle negative start indices
const splice = (start, deleteCount, ...items) => arr => {
const output = []
let i
for (i = 0; i < start && i < arr.length; i++) {
output.push(arr[i])
}
output.push(...items)
for (i += deleteCount; i < arr.length; i++) {
output.push(arr[i])
}
return output
}
const users = splice(index, 1)(this.state.users)
You should use slice() instead of splice() because splice() mutates the original array, but slice() returns a new array. Slice() is a pure function. Pure is better!!
removeUser(index) {
const users = [
...this.state.users.slice(0, index),
...this.state.users.slice(index+1)
];
this.setState({ users });
}
Here is JS Fiddle

Use object as a dictionary or list in AngularJS?

I currently store my data in AngularJS as lists. Such as:
var users = [{id: 1, name: "Mary"}, {id: 2, name: "John"}];
This however leads to for-loops whenever I want to change something.
function gotChangesFromServer(data) {
for (var i=0; i < users.length; ++i) {
if (users[i].id == data.id) {
users[i] = data;
break;
}
}
}
I was looking into underscore.js and found it has some nice functions like values() to get a list of values from a dictionary-like structure. So I could instead do:
var users = { "1": {id: 1, name: "Mary"}, "2": {id: 2, name: "John"} };
function gotChangesFromServer(data) {
users[data.id] = data;
}
function getUsers() {
return _.values(users);
}
But, I realized that AngularJS will call my getUsers() function a lot. On every filter operation or any change to the data.
So it seems I have to choose between two bad solutions. Either for-looping all the time when changing the users, or accessing a user based on id. Or, calling values() often, which I suppose, basically is a for-loop over the properties in the users object.
I expect to have around 1000 - 5000 users in the users list / dictionary.
How would you do it?
You may use underscores findWheremethod to retrieve certain objects:
_.findWhere(users, {id: 123}).data = newDataForThisUser;
Your gotChangesFromServer would then look like this:
function gotChangesFromServer(data) {
_.extend(_.findWhere(users, {id: data.id}), data);
}

prototype JSON to Object

The following is part of a JSON string returned from the server:
{
col1: {
caption: 'Workspace',
combodata: {
c_0: {
id: 0,
value: 'Filter...'
},
c_1: {
id: 1,
value: 'Tax'
},
c_2: {
id: 2,
value: 'HR'
}
}
}
}
After eval, I can access .caption, and .combodata is visible in Firebug as an object, with c_0 and c_1 visible as objects inside .combodata, with id and value in both c_0 and c_1.
How do I step through each object in .combodata? I tried .combodata.each(c), but that throws an exception. I won't know the names of the objects in .combodata during run time.
You can use a regular for loop for that:
for(var key in obj.col1.combodata) {
var combo_obj = obj.col1.combodata[key];
...
}
Can I suggest that you do not eval() the JSON that's returned? What you should be doing is:
var jsondata = { ... };
var obj = JSON.parse(jsondata);
The reason is because eval'ing a string can be dangerous. Imagine if your JSON data looked like this:
"{ some json data here }; alert(document.cookie)"
When you eval that, the users cookie is displayed to them. Now think what happens if instead of alert, that cookie is posted to an attackers URL. They now have access to that users account if such exists.
if
var result = {col1: { caption: 'Workspace',combodata: {c_0: {id: 0,value: 'Filter...'},c_1: {id: 1, value: 'Tax'},c_2: {id: 2, value: 'HR'}}}};
then
for ( i in result.col1.combodata ) {
var item = result.col1.combodata[i];
//Do stuff with item
}
I have found the following to work as well and will use this:
Object.values(col1.combodata).each(function(c2) {
id = c2.id;
});

Categories

Resources