How to update value in a nested Immutable map - reactjs

I am new to immutable.js and trying to figure out a way to update a nested map
Here is my object
let state = OrderedMap({
'name': Map({ id: 'name', hint: 'Search by name', value: '' }),
'job': Map({ id: 'job', hint: 'Search by job title', value: ''}),
'state': Map({ id: 'state', hint: 'Search by state', value: ''})
});
I am trying to set value of the 'name' object using a setIn function
state.setIn(['name', 'value'], 'Test');
The value is not getting updated as expected. Am I missing anything here

setIn doesn't mutate the original state, however it returns you another one so you would essentially have
state = state.setIn(['name', 'value'], 'Test');

I've never used immutable, but it looks to me like the docs are saying you want something more like:
setIn(state, ['name', 'value'], 'Test');
Where you are passing in the collection rather than running a function on it with a dot operator.
https://facebook.github.io/immutable-js/docs/#/setIn

Instead of mutating the state you can use setIn as this will return you a fresh copy instead , So you can do something like :
state = state.setIn(['name', 'value'], 'Test');
You can read more on Facebook Github

Related

Antd design table column -> how to pass array of objects to dataIndex

I'm fetching object (contract) from DB, it contains few fields, one of them is an array of objects (products). I have antd table with column Products and I would like to fill it with product names.
For example, my contract object also has account object, and the way I use dataIndex there is like this:
{
title: 'Account',
dataIndex: ['account', 'name'],
key: 'account',
align: 'center',
sorter: (a, b) => a.length - b.length
}
That works fine! But Account is not an array.
Here is code with issue:
{
title: 'Products',
dataIndex: ['products'],
key: 'products',
align: 'center',
filters: []
}
Here is how I map data that is send to component above which has to fill table:
return data.map((contract) => ({
key: contract._id,
account: contract.account,
products: [contract.products],
}));
I tried returning just contract.products, tried returning Object.values(contract.products), tried to JSON.stringify(contract.products) but then I don't know how to parse it in column and take just name...
The only way I managed to set name was by not sending object. Example:
return data.map((contract) => ({
key: contract._id,
account: contract.account,
products: contract.products.map(product => product.name),
}));
This would indeed solve this problem, but it would made me make another asyncThunk method where I would return whole array of objects.
Hope that I described that well,this is for my college project and it's bean bugging me whole day.
I would be very grateful if someone knows how can I solve this.
According to Antd documentation for column dataIndex, it will only accept a string or an array of strings for nested paths.
dataIndex - Display field of the data record, support nest path by string array - string | string[ ]
So I don't think you can pass an array of objects to dataIndex.
The only way around that I can think of would be to convert the array to a string before passing it to your table.
To do that you can use .join() method to add space and a comma between your array items.
const convertArrayToString = (array) => array.join(', ');
And before returning you could use the above function: Example:
return data.map((contract) => ({
key: contract._id,
account: contract.account,
products: convertArrayToString(contract.products),
}));

How do I preselect a vue-multiselect option when options is an array of objects?

I want to pre-select a particular value in a select drop-down generated by vue-multiselect.
I can get this to work fine if I have a simple array of strings like the following:
['Test 1', 'Test 2', 'Test 3']
However, when I use an array of objects, I can't get this to work. For example, if I have the following:
<v-multiselect :options="[{id: 1, name: 'Test 1'}, {id: 2, name: 'Test 2'}, {id: 3, name: 'Test 3'}]"
label="name"
track-by="id"
v-model="test">
</v-multiselect>
No matter what I set the test data property that v-model is connected to, it won't preselect the value. I've tried 1, 2, 3, '1', '2' and '3' for test when track-by is id and 'Test 1', etc. when track-by is name but nothing seems to work.
What am I doing wrong here? I looked at the docs at https://vue-multiselect.js.org/#sub-single-select-object, but they don't seem to provide an example when you want to preset a value for an array of objects for the options. Googling has also not returned what I'm looking for.
On a related topic, once I get this working, what would I have to change to select multiple values for when I set the component to multiple? Thank you.
track-by usage
The docs indicate that track-by is "Used to compare objects. Only use if options are objects."
That is, it specifies the object key to use when comparing the object values in options. The docs should actually state that track-by is required when the options are objects because <vue-multiselect> uses track-by to determine which options in the dropdown are selected and to properly remove a selected option from a multiselect.
Without track-by, you'd see two buggy behaviors for object-options: (1) the user would be able to re-select already selected options, and (2) attempting to remove selected options would instead cause all options to be re-inserted.
Setting initial values
<vue-multiselect> doesn't support automatically translating a value array, but you could easily do that from the parent component.
Create a local data property to specify track-by and initial multiselect values (e.g., named trackBy and initialValues, respectively):
export default {
data() {
return {
//...
trackBy: 'id',
initialValues: [2, 5],
}
}
}
Bind <vue-multiselect>.track-by to this.trackBy and <vue-multiselect>.v-model to this.value:
<vue-multiselect :track-by="trackBy" v-model="value">
Create a watcher on this.initialValues that maps those values into an object array based on this.trackBy, setting this.value to the result:
export default {
watch: {
initialValues: {
immediate: true,
handler(values) {
this.value = this.options.filter(x => values.includes(x[this.trackBy]));
}
}
}
}
Vue.component('v-multiselect', window.VueMultiselect.default);
new Vue({
el: '#app',
data () {
return {
trackBy: 'id',
initialValues: [5,2],
value: null,
options: [
{ id: 1, name: 'Vue.js', language: 'JavaScript' },
{ id: 2, name: 'Rails', language: 'Ruby' },
{ id: 3, name: 'Sinatra', language: 'Ruby' },
{ id: 4, name: 'Laravel', language: 'PHP' },
{ id: 5, name: 'Phoenix', language: 'Elixir' }
]
}
},
watch: {
initialValues: {
immediate: true,
handler(values) {
this.value = this.options.filter(x => values.includes(x[this.trackBy]));
}
}
}
})
<script src="https://unpkg.com/vue#2.6.6/dist/vue.min.js"></script>
<script src="https://unpkg.com/vue-multiselect#2.1.0"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect#2.1.0/dist/vue-multiselect.min.css">
<div id="app">
<v-multiselect :track-by="trackBy"
:options="options"
v-model="value"
label="name"
multiple>
</v-multiselect>
<pre>{{ value }}</pre>
</div>
Looks like a bug. The workaround is to use an actual reference to the object
Vue.component('v-multiselect', window.VueMultiselect.default);
let testOptions=[{id: 1, name: 'Test 1'}, {id: 2, name: 'Test 2'}, {id: 3, name: 'Test 3'}]
new Vue({
el: '#app',
data: function () {
return {
test: testOptions[1], // <- use an object ref here!
testOptions
};
}
});
The easiest way I found out is sending the whole object from BE, so it gets pre-selected. If you send the same object from BE will get pre-selected. But I don't know if your options are hard coded on FE or they are coming from a database or something. I had the same issue but my values were coming from my database, so it was easy to reproduce the object
In your question just :object="true" is missing actually they didn't know that it is of type string or object when we pass this it knows yes it is object and i need label="name" from v-model="test" and picks it and shows it as a preselected

How can I call a custom function in "onFilter" in ant design?

I would like to call a custom function in "onFilter" property given by ant design on columns. I can go for the custom filter dropdown as an option but I would like to use the default filter option given by ant design. i.e
{
title: 'Address',
dataIndex: 'address',
key: 'address',
filters: [{
text: 'London',
value: 'London',
}, {
text: 'New York',
value: 'New York',
}],
onFilter: (value, record) => record.address.indexOf(value) === 0,
}
But the only thing I need to change here is a custom function (that will trigger the api call and set the new data in the redux state so the component will re render it self) something like
{
...
onFilter: (value, record) => this.getFilteredData(value),
}
But when I do this I get this error which does make sense too.
Warning: setState(…): Cannot update during an existing state
transition
So kindly guide me on how to do that as I am new to react and ant design both.
Dispatching an action inside onFilter is probably not a good idea because it will be called a couple of times on each filter change
But you can edit your reducer to set filterLoading to true when this action getFilteredData is called and the new onFilter can be like this
...
onFilter: (value) => {
if(! this.props.filterLoading) {
this.getFilteredData(value);
}
return true;
}
A couple of things to consider
You have to pass filterLoading to the component
Don't forget to set filterLoading to false on GET_FILTERED_DATA_SUCCESS action is called
Hope this helps

UI-Grid gridOptions -> selectOptions as Variable not possible?

I just tried to use this code:
field: 'field1',
filter: {
term: 1,
condition: uiGridConstants.filter.STARTS_WITH,
placeholder: 'starts with...',
ariaLabel: 'Starts with filter for field1',
flags: { caseSensitive: false },
type: uiGridConstants.filter.SELECT,
//selectOptions: [{ value: '1', label: 'male' }, { value: '2', label: 'female' }],
selectOptions: [$scope.DropdownEntries],
disableCancelFilterButton: false
}
The gridOptions selectOption array which come back form DropdownEntries is empty because the variables in DropdownEntries() are not accessible if gridOptions are not set, they come from a Webservice and it takes longer to load them as gridOptions are set.
Is there any possibility to "reload" the gridOptions after I`m sure that all variables are accessible ? Or is there another way to solve this problem
I am new in AngularJS and Ui-Grid thanks for helping me !
You can initialize the selectOptions with an empty array and reassign them when the data becomes available
See this question.
Then you should wait for that promise to be resolved and after that set selectOptions, something like this:
$http.get(url).then(function(response) {
gridOptions.filter.selectOptions = response.data.dropdownEntries;
})
Another possibility is to resolve that promise before entering the controller that sets ui-grid options.
https://docs.angularjs.org/api/ngRoute/service/$route#example

Angular-Formly Nested Model Not Updating

I am having an interesting issue with angular-formly. I am attempting to use the 'model' tag as shown below because my model is not flat.
{
'key': 'last',
'model': 'model.name',
'templateOptions: {}
}
However, I cannot update the model in a clean manner. Simply replacing model or even model.name with a matching model that contains the updated value does not cause the model to update the view.
var newModel = {
name: {
first: 'Gandalf',
last: 'The White'
}
};
self.model = {
name: {
first: 'Gandalf',
last: 'The Grey'
}
};
function setNewLastName() {
self.model = newModel;
}
setNewLastName();
However if I drill down to the specific property, it works as expected.
self.model.name.last = self.newModel.name.last;
Here is a link to a JSBin where the value updates using the drill-down method immediately above.
Drill-down JSBin
Another JSBin that attempts to update the model by assigning a new model that does not update.
Assign Model JSBin
Has anyone ran into this issue or can you see where I'm doing something wrong?
You replace the model for each key, therefore you never see the changes.
What you need to do is to match the model in the key itself.
vm.fields = [
{
key: 'name.first', // <-- HERE
type: 'input',
//model: vm.model.name, //Wrong
templateOptions: {
label: 'First Name'
}
},
{
key: 'name.first', // <-- AND HERE
type: 'input',
//model: vm.model.name, //Wrong
templateOptions: {
label: 'Last Name'
}
},
//...
];
See corrected example: http://jsbin.com/pupijoc/1/edit?js,console,output
UPDATE: Nested properties are also handled by fieldGroups
Se updated example: http://jsbin.com/pupijoc/3/edit?js,console,output

Resources