Immutable state update. Update an array of objects in Redux - redux

I have a state of type array that initially looks like that:
const initialState = {
spots: []
}
On the first update it gets filled with objects and looks like that:
state.spots = [
{ id: '1', available: 1, path: 'xxx' },
{ id: '2', available: 1, path: 'xxz' },
{ id: '3', available: 1, path: 'xxy' },
{ id: '4', available: 1, path: 'bxy' },
{ id: '5', available: 1, path: 'vxy' },
{ id: '6', available: 1, path: 'fxy' }
]
On all subsequent updates we get all the spots with keys of 'id' and 'available' but not the path.
So the payload looks like this:
payload = [
{ id: '1', available: 1 },
{ id: '2', available: 0 },
{ id: '3', available: 0 },
{ id: '4', available: 1 },
{ id: '5', available: 0 },
{ id: '6', available: 1 }
]
I'm trying to figure out the immutable ES6 pattern to update the state but I cannot seem to get it right.
EDIT:
Best solution I have found so far is the following:
state.spots = state.spots.map( (spot, key) => {
let new_spot = payload.find(o => o.id === spot.id);
return { ...spot, ...new_spot }
})
What is the most efficient way to properly update all objects of the array "spots" with the new payload.
Hope it is clear.
Thanks

CodeSandbox
You can create a hash map of your payload using each spot's id as the key:
const payload = [
{ id: '1', available: 1 },
{ id: '2', available: 0 },
{ id: '3', available: 0 },
{ id: '4', available: 1 },
{ id: '5', available: 0 },
{ id: '6', available: 1 }
]
// You can adjust the map to how you like.
const map = payload.reduce((acc, entry) => {
acc[entry.id] = entry
return acc
}, {})
and then update state.spots in your reducer:
return {
...state,
spots: state.spots.map(e => {
if (map[e.id]) return { ...e, ...map[e.id] };
return e;
})
};

Related

"TypeError: Cannot read property 'Network' of undefined" in vis-network

I am trying to create a vis-network in vueJS 2.6
My vis-network version is 8.3.2
I get TypeError: Cannot read property 'Network' of undefined" in vis-network
following is my code
<div id="mynetwork"></div>
</template>
<script>
import vis from 'vis-network'
export default {
data() {
return {
network: null,
nodes: [
{ id: 1, shape: "circularImage"},
{ id: 2, shape: "circularImage"},
],
edges: [
{ from: 1, to: 2 },
],
options: {
},
},
container: "",
};
},
mounted() {
this.container = document.getElementById("mynetwork");
var data = {
nodes: this.nodes,
edges: this.edges,
};
this.network = new vis.Network(this.container, data, this.options);
},
};
</script>
Solved It by replacing the import statement
import {Network } from 'vis-network/standalone/umd/vis-network.min'

gPRC how to send array data from the server to the client [gPRC + Node.js]

I m trying to send an array data object from the server.js to the client.js but I get undefined.
What is the correct way to send this data types
- string
- number
- Object
- array
I m trying to send a simple array data from the master to the client. I dont want to even imagine to send a more complex data like Object
Can someone show me, a simple working example where I can send from the server.js this data
[
{ id: '1', title: 'Note 1', content: 'Content 1'},
{ id: '2', title: 'Note 2', content: 'Content 2'},
{ id: '3', title: 'Note 3', content: 'Content 3'}
]
and on the client, I want to see this response if I run
node .\client.js
[
{ id: '1', title: 'Note 1', content: 'Content 1'},
{ id: '2', title: 'Note 2', content: 'Content 2'},
{ id: '3', title: 'Note 3', content: 'Content 3'}
]
notes.proto
syntax = "proto3";
package notes;
service NoteService {
rpc GetNoteList (Empty) returns (NoteList) {} <--- this not workig
rpc GetNoteItem (Empty) returns (Note) {} <--- this works
}
message Empty {}
message Note {
string id = 1;
string title = 2;
string content = 3;
}
message NoteList {
repeated Note notes = 1;
}
server.js
const grpc = require('grpc');
const protoLoader = require('#grpc/proto-loader');
const PROTO_PATH = __dirname + '/../../protos/notes.proto';
// const notesProto = grpc.load('notes.proto')
const packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
}
);
const notesProto = grpc.loadPackageDefinition(packageDefinition).notes;
// returns a list of notes.
const getNoteList = (call, callback) => {
// mock data
const notes = [
{ id: '1', title: 'Note 1', content: 'Content 1'},
{ id: '2', title: 'Note 2', content: 'Content 2'},
{ id: '3', title: 'Note 3', content: 'Content 3'},
];
callback(null, { message: notes });
}
function getNoteItem(call, callback) {
const data = { id: '1', title: 'Note 1', content: 'Content 1'};
return callback(null, data)
}
/**
* Starts an RPC server that receives requests for the Greeter service at the
* sample server port
*/
function main() {
var server = new grpc.Server();
server.addService(notesProto.NoteService.service, {
GetNoteList: getNoteList,
GetNoteItem: getNoteItem
});
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
console.log('Server running at http://127.0.0.1:50051')
server.start();
}
main();
client.js
// var PROTO_PATH = __dirname + '/../../protos/model.proto';
var PROTO_PATH = __dirname + '/../../protos/notes.proto';
var grpc = require('grpc');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
}
);
const notesProto = grpc.loadPackageDefinition(packageDefinition).notes;
function main() {
const client = new notesProto.NoteService('localhost:50051', grpc.credentials.createInsecure());
var user;
if (process.argv.length >= 3) {
user = process.argv[2];
} else {
user = 'world';
}
// console.log({user : user});
// expected to return array of objects
client.getNoteList('test', (err, res) => {
if (!err) {
console.log('list1: ', res);
console.log('list2: ', res.message);
} else {
console.error(err);
}
});
// get a single item
client.getNoteItem('test', (err, res) => {
if (!err) {
console.log('getNoteItem res: ', res);
} else {
console.error(err);
}
});
}
main();
output
PS C:\dev\george\tests\GRPC\grpc-test\server\node> node .\client.js
getNoteItem res: { id: '1', title: 'Note 1', content: 'Content 1' }
list1: { notes: [] }
list2: undefined
Have you tried to invoke callback per item?
I found, the problem and was this
// returns a list of notes.
const getNoteList = (call, callback) => {
// mock data
const data = [
{ id: '1', title: 'Note 1', content: 'Content 1'},
{ id: '2', title: 'Note 2', content: 'Content 2'},
{ id: '3', title: 'Note 3', content: 'Content 3'},
];
callback(null, { notes: data }); <---
}

How to add a css class to the last row in jsGrid

I want to add some styling to the last row in my grid. I wont know what the row number is as there could be any number of rows. How can I go about this? I've seen rowClass and rowRenderer but not a working example. Here is the code I have:
var displayData = function (itemViewModelList) {
var fields = [
{
name: 'ConsultantName', type: 'text', width: 100, title: 'Consultant Name'
},
{
name: 'BranchName', type: 'text', width: 100, title: 'Branch Name', css: "red"
},
{ name: 'NumberOfInvestments', type: 'text', title: 'Number Of Investments' },
{
name: 'ValueOfInvestments', type: 'money', width: 150, title: 'Value Of Investments',
itemTemplate: function (value) {
return tisCommon.formatForMoney(value);
}
},
{
name: 'AverageValueOfInvestments', type: 'money', width: 150, title: 'Average Value Of Investments',
itemTemplate: function (value) {
return tisCommon.formatForMoney(value);
}
},
{
name: 'Month', type: 'text', width: 100, title: 'Month',
itemTemplate: function (value) {
return moment(value, 'M').format('MMMM');;
}
},
];
var options = {
inserting: false,
editing: false,
pageSize: 20,
fields: fields,
rowHeaders: false,
colHeaders: false,
data: itemViewModelList,
controller: controller = {
loadData: function () {
},
},
};
$('#investment-grid').tisGrid('', options);
if (itemViewModelList[0].ConsultantName != null) {
$("#investment-grid").jsGrid("fieldOption", "BranchName", "visible", false);
} else {
$("#investment-grid").jsGrid("fieldOption", "ConsultantName", "visible", false);
}
};
My data being passed "itemViewModelList" is an array of objects
I resolved this by using rowClass as follows:
controller: controller =
{
loadData: function () {},
},
rowClass: function (item, itemIndex) //item is the data in a row, index is the row number.
{
if ((item.ConsultantName == "Totals") || (item.BranchName == "Totals"))
{
return "totalItem highlight";
}
}
I have my if statement where I find the item in the last row based on my conditions. When they are met, I add my custom CSS classes to that row.

Why doesn't ui-grid in Angular Meteor show its data when filter is activated?

Hello I have a problem
Working:
Meteor
Angular Meteor
ui-grid
I follow the plunker example in documentation ui-grid link
The problem is that the data don't show when filters are activated.
I have no errors in console.
I put my code:
html file
<button id='toggleFiltering' ng-click="inventario.toggleFiltering()" class="btn btn-success">Toggle Filtering</button>
<div id="grid1" ui-grid="inventario.gridOptions" class="grid"></div>
js file
class Inventario {
constructor($scope, $reactive, $uibModal, $http, uiGridConstants) {
'ngInject';
$reactive(this).attach($scope);
this.$uibModal = $uibModal;
var today = new Date();
var nextWeek = new Date();
this.highlightFilteredHeader = (row, rowRenderIndex, col, colRenderIndex) => {
if( col.filters[0].term ){
return 'header-filtered';
} else {
return '';
}
};
this.gridOptions = {
enableFiltering: true,
onRegisterApi: (gridApi) => {
this.gridApi = gridApi;
},
columnDefs: [
// default
{ field: 'name', headerCellClass: this.highlightFilteredHeader },
// pre-populated search field
{ field: 'gender', filter: {
term: '1',
type: uiGridConstants.filter.SELECT,
selectOptions: [ { value: '1', label: 'male' }, { value: '2', label: 'female' }, { value: '3', label: 'unknown'}, { value: '4', label: 'not stated' }, { value: '5', label: 'a really long value that extends things' } ]
},
cellFilter: 'mapGender', headerCellClass: this.highlightFilteredHeader },
// no filter input
{ field: 'company', enableFiltering: false, filter: {
noTerm: true,
condition: (searchTerm, cellValue) => {
return cellValue.match(/a/);
}
}},
// specifies one of the built-in conditions
// and a placeholder for the input
{
field: 'email',
filter: {
condition: uiGridConstants.filter.ENDS_WITH,
placeholder: 'ends with'
}, headerCellClass: this.highlightFilteredHeader
},
// custom condition function
{
field: 'phone',
filter: {
condition: (searchTerm, cellValue) => {
var strippedValue = (cellValue + '').replace(/[^\d]/g, '');
return strippedValue.indexOf(searchTerm) >= 0;
}
}, headerCellClass: this.highlightFilteredHeader
},
// multiple filters
{ field: 'age', filters: [
{
condition: uiGridConstants.filter.GREATER_THAN,
placeholder: 'greater than'
},
{
condition: uiGridConstants.filter.LESS_THAN,
placeholder: 'less than'
}
], headerCellClass: this.highlightFilteredHeader},
// date filter
{ field: 'mixedDate', cellFilter: 'date', width: '15%', filter: {
condition: uiGridConstants.filter.LESS_THAN,
placeholder: 'less than',
term: nextWeek
}, headerCellClass: this.highlightFilteredHeader
},
{ field: 'mixedDate', displayName: "Long Date", cellFilter: 'date:"longDate"', filterCellFiltered:true, width: '15%',
}
]
};
$http.get('https://cdn.rawgit.com/angular-ui/ui-grid.info/gh-pages/data/500_complex.json')
.success((data) => {
this.gridOptions.data = data;
this.gridOptions.data[0].age = -5;
data.forEach( function addDates( row, index ){
row.mixedDate = new Date();
row.mixedDate.setDate(today.getDate() + ( index % 14 ) );
row.gender = row.gender==='male' ? '1' : '2';
});
});
this.toggleFiltering = () => {
this.gridOptions.enableFiltering = !this.gridOptions.enableFiltering;
this.gridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
};
}
}
const name = 'inventario';
// Módulo
export default angular
.module(name, [
uiRouter,
EditarArticulo
])
.component(name, {
templateUrl: `imports/ui/components/${name}/${name}.html`,
controllerAs: name,
controller: Inventario
})
.config(config)
.filter('mapGender', function() {
var genderHash = {
1: 'male',
2: 'female'
};
return function(input) {
if (!input){
return '';
} else {
return genderHash[input];
}
};
});
Given that everything seems to work when filtering is disabled, you must have a problem with the (multiple) filters you have declared.
It is most likely a combination of the filters that is excluding all of your data. Start by commenting out all of the filters (you should see all the data), and then re-introduce the filters one by one until you see the problem again.
This will narrow down the problem, and allow you to see which filter is wrong.

Rally Grid load event is not firing

I've been trying to make sure that the load event in the Rally.ui.grid.Grid is firing since I have a problem because my Grid is not filtering. I tried calling the methods myStore.setFilter() and myStore.load(), these two are firing, but I can't be sure the Grid is working properly since the first time, when it all loads, it does the filtering right, but then when I change the dropdown or combobox it doesn't.
This is how I load myStore:
this.myStore=Ext.create("Rally.data.wsapi.Store",{
model:"Task",
autoLoad:true,
filters: myFilters,
listeners:{
load:function(myStore,myData,success){
if(!this.myGrid) //IT CREATES THE GRID FOR THE FIRST TIME
{
this._loadGrid(myStore)
console.log('Grid Created!');
// this.myStore.setFilter();
// this.myStore.load();
}
else
{
this.myStore.setFilter();
//this.myStore.load();
console.log('Grid reloaded!');
console.log(myFilters);
}
},
scope:this
},
fetch:["FormattedID","State","Iteration", "Release"]
}
)
}
And this is how I load myGrid:
_loadGrid:function(myStoryStore){
this.myGrid = Ext.create("Rally.ui.grid.Grid",{
store:myStoryStore,
columnCfgs:["FormattedID","State","Iteration", "Release"],
listeners: {
load: function(myGridy){
console.log('myGrid did load!');
},
scope:this
}
});
this.add(this.myGrid);
}
Here is an example by David Thomas from his videos on building Rally apps that uses reconfigure method to which a store is passed: _myGrid.reconfigure(myStore)
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var relComboBox = Ext.create('Rally.ui.combobox.ReleaseComboBox',{
listeners:{
ready: function(combobox){
//console.log('loaded release name', combobox.getRecord().get('Name')); //getRecord() returns currently selected item
var releaseRef = combobox.getRecord().get('_ref');
this._loadStories(releaseRef);
//console.log('what is this', this);
},
select: function(combobox){
var releaseRef = combobox.getRecord().get('_ref');
this._loadStories(releaseRef);
},
scope: this
}
});
this.add(relComboBox);
},
_loadStories: function(releaseRef){
console.log('loading stories for ', releaseRef);
var myStore = Ext.create('Rally.data.WsapiDataStore',{
model: 'User Story',
autoLoad:true,
fetch: ['Name','ScheduleState','FormattedID'],
filters:[
{
property : 'Release',
operator : '=',
value : releaseRef
}
],
listeners: {
load: function(store,records,success){
console.log("loaded %i records", records.length);
this._updateGrid(myStore);
},
scope:this
}
});
},
_createGrid: function(myStore){
console.log("load grid", myStore);
this._myGrid = Ext.create('Ext.grid.Panel', {
title: 'Stories by Release',
store: myStore,
columns: [
{text: 'ID', dataIndex: 'FormattedID', flex: 1},
{text: 'Story Name', dataIndex: 'Name', flex: 2},
{text: 'Schedule State', dataIndex: 'ScheduleState', flex: 2}
],
height: 400
});
this.add(this._myGrid);
},
_updateGrid: function(myStore){
if(this._myGrid === undefined){
this._createGrid(myStore);
}
else{
this._myGrid.reconfigure(myStore);
}
}
});

Resources