Make react-select option unsearchable - react-select

I'm using react-select and have one option with a blank name and the value of not set and another with the name and value of notification. This means my options array looks something like this (though with many more options):
const options = [
{
label: '',
value: 'not set'
},
{
label: 'Notification',
value: 'notification'
}
];
Because of this, when users are looking for the notification option, they often just type not and then are shown the blank option.
I do want this option to show in the dropdown menu itself if someone is just thumbing through it (with arrow keys) but is there a way I can prevent certain options from showing in the search? Maybe a key I can include in the option?

I recommend you to use the props filterOption to achieve your goal. See more how to use it here.
You can either decide to filter by label only instead of value or add a key for each searchable option like this:
const options = [
{
label: '',
value: 'not set',
searchable: false
},
{
label: 'Notification',
value: 'notification',
searchable: true
}
];
// your custom filterOption function
filterOption = ({ label, value, data }, string) => {
if (string && data.searchable) {
return label.includes(string) || value.toString().includes(string);
} else {
return true;
}
};

Related

Computed not reactive?

I wrote this code to return a list of skills. If the user already has a specific skill, the list-item should be updated to active = false.
This is my initial code:
setup () {
const user = ref ({
id: null,
skills: []
});
const available_skills = ref ([
{value: 'css', label: 'CSS', active: true},
{value: 'html', label: 'HTML', active: true},
{value: 'php', label: 'PHP', active: true},
{value: 'python', label: 'Python', active: true},
{value: 'sql', label: 'SQL', active: true},
]);
const computed_skills = computed (() => {
let result = available_skills.value.map ((skill) => {
if (user.value.skills.map ((sk) => {
return sk.name;
}).includes (skill.label)) {
skill.active = false;
}
return skill;
});
return result;
})
return {
user, computed_skills
}
},
This works fine on the initial rendering. But if I remove a skill from the user doing
user.skills.splice(index, 1) the computed_skills are not being updated.
Why is that the case?
In JavaScript user or an object is a refence to the object which is the pointer itself will not change upon changing the underling properties hence the computed is not triggered
kid of like computed property for an array and if that array get pushed with new values, the pointer of the array does not change but the underling reference only changes.
Work around:
try and reassign user by shadowing the variable
The computed prop is actually being recomputed when you update user.skills, but the mapping of available_skills produces the same result, so there's no apparent change.
Assuming user.skills contains the full skill set from available_skills, the first computation sets all skill.active to false. When the user clicks the skill to remove it, the re-computation doesn't set skill.active again (there's no else clause).
let result = available_skills.value.map((skill) => {
if (
user.value.skills
.map((sk) => {
return sk.name;
})
.includes(skill.label)
) {
skill.active = false;
}
// ❌ no else to set `skill.active`
return skill;
});
However, your computed prop has a side effect of mutating the original data (i.e., in skill.active = false), which should be avoided. The mapping above should clone the original skill item, and insert a new active property:
const skills = user.value.skills.map(sk => sk.name);
let result = available_skills.value.map((skill) => {
return {
...skill,
active: skills.includes(skill.label)
}
});
demo
slice just returns a copy of the changed array, it doesn't change the original instance..hence computed property is not reactive
Try using below code
user.skills = user.skills.splice(index, 1);

Selecting edges based on both edge's data and target's data

I would like a selector for edges matching
edge[type="blocker"]
that have a target matching
node[status="complete"]
In other words, is there a valid way of expressing the following:
cytoscape({
...
style: [
...
{
selector: '( node -> node[status="complete"] )[type="blocker"]',
style: {
display: 'none',
},
},
...
],
...
})
I don't see a way of doing this in the documentation.
Obviously, I could copy the target's data into the node's data and use the following:
edge[type="blocker"][target_status="complete"]
But duplicating the data goes against my every instinct as a software developer.
You can provide function to filter method:
cy.edges().filter(function(ele) {
return ele.data('type') == 'blocker' &&
ele.target().data('status') == 'complete';
})
How about this
EDIT:
var selectedEdges = cy.nodes("[status= 'complete']").connectedEdges("[type = 'blocker']);
var selectedEdges.addClass('specialSnowflake')
And in your stylesheet, just define:
{
"selector": "edge.specialSnowflake",
"style": {
"display": "none"
}
}
If a selector does not suffice, then you could (1) suggest a new feature to enhance the edge -> selector and perhaps even make a PR for it or (2) use a function on the style property instead.
E.g. for (2):
{ selector: 'edge', style: { display: edgeIsDisplayed } }
where the function can be anything, like edgeIsDisplayed(edge) => edge.data('foo') === 'bar' && edge.target().hasClass('baz')
See http://js.cytoscape.org/#style/mappers
There's no selector that will help.
However, it is possible to avoid having to manually update two elements when data changes.
Style sheet value functions are called every time a matching element's data changes. In one those function, one could therefore update the data of incoming edges every time the node's data is updated, keeping the data of the two in sync automatically.
var push_status = function(node) {
node.incomers('edge').forEach( edge => edge.data('target_status', node.data('status')) );
node.outgoers('edge').forEach( edge => edge.data('source_status', node.data('status')) );
};
cytoscape({
...
style: [
...
{
selector: 'node',
style: {
label: node => { push_status(node); return node.data('id'); },
},
},
{
selector: 'edge[type="blocker"][target_status="complete"]',
style: {
display: 'none',
},
},
...
],
...
})
This would qualify as a hack, but it works perfectly. Updating the node's data updates the edge's data, which causes the style to be applied or unapplied as appropriate.
Be wary of creating an infinite loop! In particular, modifying the data of a node's parent will trigger a calculation of the node's style. This problem can be avoided by replacing
ele.data('key', val)
with
// Making changes to a element's data triggers a style recalculation.
// This avoids needlessly triggering the style recalculation.
var set_data = function(node, key, new_val) {
let old_val = node.data(key);
if (new_val != old_val)
node.data(key, new_val);
};
set_data(ele, 'key', val)

Validation of fetched data from API Redux React

So, I will go straight to the point. I am getting such data from api:
[
{
id: 123,
email: asd#asd.com
},
{
id: 456,
email: asdasd.com
},
{
id: 789,
email: asd#asd
},
...
]
and I should validate email and show this all info in a list, something like this:
asd#asd.com - valid
asdasd.com - invalid
asd#asd - invalid
...
My question is what is the best way to store validation data in a store? Is it better to have something like "isValid" property by each email? I mean like this:
store = {
emailsById: [
123: {
value: asd#asd.com,
isValid: true
},
456: {
value: asdasd.com,
isValid: false
},
789: {
value: asd#asd,
isValid: false
}
...
]
}
or something like this:
store = {
emailsById: [
123: {
value: asd#asd.com
},
456: {
value: asdasd.com
},
789: {
value: asd#asd
}
...
],
inValidIds: ['456', '789']
}
which one is better? Or maybe there is some another better way to have such data in store? Have in mind that there can be thousands emails in a list :)
Thanks in advance for the answers ;)
I recommend reading the article "Avoiding Accidental Complexity When Structuring Your App State" by Tal Kol which answers exactly your problem: https://hackernoon.com/avoiding-accidental-complexity-when-structuring-your-app-state-6e6d22ad5e2a
Your example is quite simplistic and everything really depends on your needs but personally I would go with something like this (based on linked article):
var store = {
emailsById: {
123: {
value: '123#example.com',
},
456: {
value: '456#example.com',
},
789: {
value: '789#example.com',
},
// ...
},
validEmailsMap: {
456: true, // true when valid
789: false, // false when invalid
},
};
So your best option would be to create a separate file that will contain all your validations methods. Import that into the component you're using and then when you want to use the logic for valid/invalid.
If its something that you feel you want to put in the store from the beginning and the data will never be in a transient state you could parse your DTO through an array map in your reducer when you get the response from your API.
export default function (state = initialState, action) {
const {type, response} = action
switch (type) {
case DATA_RECIEVED_SUCCESS:
const items = []
for (var i = 0; i < response.emailsById.length; i++) {
var email = response.emailsById[i];
email.isValid = checkEmailValid(email)
items.push(email)
}
return {
...state,
items
}
}
}
However my preference would be to always check at the last moment you need to. It makes it a safer design in case you find you need to change you design in the future. Also separating the validation logic out will make it more testable
First of all, the way you defined an array in javascript is wrong.
What you need is an array of objects like,
emails : [
{
id: '1',
email: 'abc#abc.com',
isValid: true
},
{
id: '2',
email: 'abc.com',
isValid: false;
}
];
if you need do access email based on an id, you can add an id property along with email and isValid. uuid is a good way to go about it.
In conclusion, it depends upon your use case.
I believe, the above example is a good way to keep data in store because it's simple.
What you described in your second example is like maintaining two different states. I would not recommend that.

Virtual "name" field?

I need to have the name field of a model be virtual, created by concatenating two real fields together. This name is just for display only. I've tried the virtual examples in the doc, no luck. Keystone 4 beta5.
var keystone = require('keystone')
_ = require('underscore');
var Types = keystone.Field.Types;
/**
* Foo Model
* ==================
*/
var Foo = new keystone.List('Foo', {
map: {name: 'fooname'},
track: true
});
Foo.add({
bar: { type: Types.Relationship, required: true, initial: true, label: 'Barref', ref: 'Bar', many: false },
order: { type: Types.Select, required: true, initial: true, label: 'Order', options: _.range(1,100) },
price: { type: Types.Money, format: '$0,0.00', label: 'Price', required: true, initial: true },
});
Foo.schema.virtual('fooname').get(function() {
return this.bar+ ' ' + this.order;
});
Foo.defaultColumns = 'fooname, bar, order, price';
Foo.register();
When I use this model definition, I don't see the virtual name in the defaultcolumns list. I want to make a virtual name so lookups are easier when this model is used as a relationship.
You don't need a virtual to do this. Keystone allows you to track and recalculate a field every time the document is saved. You can enable those options in order to create a function which concatenates these two values for you (either synchronously or asynchronously, your choice.)
One other thing I noticed is that bar is a Relationship, which means you will need to populate that relationship prior to getting any useful information out of it. That also means your value function will have to be asynchronous, which is as simple as passing a callback function as an argument to that function. Keystone does the rest. If you don't need any information from this bar, and you only need the _id (which the model always has), you can do without the keystone.list('Bar') function that I included.
http://keystonejs.com/docs/database/#fields-watching
The map object also refers to an option on your model, so you'll need a fooname attribute on your model in any scenario, though it gets calculated dynamically.
var keystone = require('keystone'),
_ = require('underscore');
var Types = keystone.Field.Types;
/**
* Foo Model
* ==================
*/
var Foo = new keystone.List('Foo', {
map: {name: 'fooname'},
track: true
});
Foo.add({
fooname: { type: Types.Text, watch: true, value: function (cb) {
// Use this if the "bar" that this document refers to has some information that is relevant to the naming of this document.
keystone.list('Bar').model.findOne({_id: this.bar.toString()}).exec(function (err, result) {
if (!err && result) {
// Result now has all the information of the current "bar"
// If you just need the _id of the "bar", and don't need any information from it, uncomment the code underneath the closure of the "keystone.list('Bar')" function.
return cb(this.bar.name + " " + this.order);
}
});
// Use this if you don't need anything out of the "bar" that this document refers to, just its _id.
// return cb(this.bar.toString() + " " + this.order);
} },
bar: { type: Types.Relationship, required: true, initial: true, label: 'Barref', ref: 'Bar', many: false },
order: { type: Types.Select, required: true, initial: true, label: 'Order', options: _.range(1,100) },
price: { type: Types.Money, format: '$0,0.00', label: 'Price', required: true, initial: true },
});
Foo.defaultColumns = 'fooname, bar, order, price';
Foo.register();
try this:
Foo.schema.pre('save', function (next) {
this.name = this.bar+ ' '+ this.order;
next();
});
Could you provide more information? What is currently working? How should it work?
Sample Code?
EDIT:
After creating the model Foo, you can access the Mongoose schema using the attribute Foo.schema. (Keystone Concepts)
This schema provides a pre-hook for all methods, which registered hooks. (Mongoose API Schema#pre)
One of those methods is save, which can be used like this:
Foo.schema.pre('save', function(next){
console.log('pre-save');
next();
});

MobX - Select single item in array, unselect all others?

I have the following observable array of search engines.
#observable favoriteSearchEngine = [
{ 'provider' : 'google', 'selected': true },
{ 'provider' : 'yahoo', 'selected': false },
{ 'provider' : 'bing', 'selected': false },
];
The user should only be able to select one at a time from the UI. So if they choose yahoo for example, yahoo would get selected: true and any other provider would get selected: false
This action handles the click:
#action onClickFavoriteSearchEngine = (provider) => {
alert(provider); // yahoo shows here
// How to do this step, only selected provider true and falsify all others in the array?
}
The solution given by #mweststrate works great, but since you are using an action (which also is a transaction), you could just unselect the previously selected, and select the new one if you would prefer:
#action onClickFavoriteSearchEngine = (provider) => {
alert(provider); // yahoo shows here
favoriteSearchEngine.forEach(e => e.selected = false);
favoriteSearchEngine.find(e => e.provider === provider).selected = true;
}
I would introduce a single observable representing selection, and derive the selected state from that:
#observable selection = null
#observable favoriteSearchEngine = [
{ 'provider' : 'google', 'selected': function() {
return selection === this
}
]
If you now assign another engine to the selection a few times, you will see that the selected state of the engines will update accordingly
(N.B. don't use arrow functions if declaring a plain object + derivation like this, to avoid issues with this)