Typeguard against null[] - arrays

I am working with some autogenerated types from graphQL-data and need a way to make a typeguard against null, undefined and null[] to narrow types.
So far the I got this:
export default function validData<T>(
condition: T
): asserts condition is Exclude<T, null | undefined | null[]> {
if (
condition === null ||
condition === undefined ||
(Array.isArray(condition) && typeof condition[0] === null)
) {
throw new Error(
`missing data: ${condition} returned null, null[] or undefined`
);
}
}
And then I simply use the function like this:
validData(menuDataToNarrow):
The function is working against null and undefined, but I can't seem to wrap my head around how to catch and throw errors if the data contains an empty array? The above code was my best (non-working) attempt so far.
The types are narrowed as expected when it comes to null and undefined, but I still get:
XXX is not assignable to type null[]
Any idea how to fix this?
Edit: I came up with this finished guard that works:
export default function validData<T>(
condition: T
): asserts condition is Exclude<T, null | undefined | null[]> {
if (
condition === null ||
condition === undefined ||
(Array.isArray(condition) && condition.every((x) => x === null))
) {
throw new Error(
`missing data: ${condition} returned null, null[] or undefined`
);
}
}

null[] Would be an array with nulls. You can check that with Array.prototype.every()
I think you are looking for 'an empty array' which is [] an array with length 0 or Array.isArray(data) && data.length === 0 as #VLAZ suggested in the comments
Edit based on your comment:
Be careful to correctly check for null and/or undefined in your code. I've tried defining an empty array but it does not seem trivial in TS

Related

How to access values by key from modified React final form

I was making confirmation when the user tried to close the form by modified and values length > 0.
if (modified) {
return (
Object.keys(modified).filter(
(modifiedItem) => modified[modifiedItem] && values[modifiedItem]?.length > 0,
).length > 0
)
}
Everything is working fine until there are values with an array:
when I try to access by values[answers.0.icon] there is undefined, of course, it should be accessed by values.answers[0].icon, by is it possible to do it when iterating modified keys? Or another way should be appreciated.
Thank you beforehand.
Below screenshots of values:
Modified keys:
I'd suggest to include lodash and use the get function. This will resolve the "path" for you.
For example _.get(values, modifiedItem).
More info can be found at https://lodash.com/docs/4.17.15#get
you could add undefined and null in the if statement, to check if it's true and not undefined and not null before it filter else it will be null or you can put something else.
if (modified && modified !== undefined && modified !== null) {
return (
Object.keys(modified).filter(
(modifiedItem) => modified[modifiedItem] && values[modifiedItem]?.length > 0,
).length > 0
)
}
else {
null
}
You can perhaps check for such situation if its an array and treat it differently.
I agree with solution provided by #Stijn answer, however if you dont wanna carry unnecessary baggage of Lodash you can use below code.
const parseDeep = (obj, path, def) => (() => typeof path === 'string' ?
path.replace(/\[(\d+)]/g,'.$1') : path.join('.'))()
.split('.')
.filter(Boolean)
.every(step => ((obj = obj[step]) !== undefined)) ? obj : def
if (modified) {
return (
Object.keys(modified).filter(
(modifiedItem) =>
Array.isArray(modifiedItem) ?
modified[modifiedItem] && parseDeep(values, modifiedItem, false) : //modify as required
modified[modifiedItem] && values[modifiedItem]?.length > 0,
).length > 0
)
}

Error argument of type 'Item' is not assignable to parameter of type 'string'. TS2345

I am getting error with type 'string'. TS2345
Argument of type 'Item' is not assignable to parameter of type
'string'. TS2345
filter(resortList:ResortResult[], selectedFilters:SelectedFilters) {
return resortList
.filter(resort => {
let roomTypes = resort.available.flat().map(room => room.roomType);
let activities = resort.activities;
let hasRoomType = selectedFilters["room"].some(
(rt:Item)=> roomTypes.indexOf(rt) > -1
);
let hasActivity = selectedFilters["activity"].some(
(act:Item) => ((activities.indexOf(act.value)) > -1)
);
return !(
(selectedFilters["area"].length === 0 ||
selectedFilters["area"].indexOf(resort.city) > -1) &&
// (selectedFilters["views"].length === 0 ||
// selectedFilters["views"].indexOf(resort.city) > -1) &&
(selectedFilters["activity"].length === 0 || hasActivity) &&
(selectedFilters["room"].length === 0 || hasRoomType)
);
})
.map(resort => {
return resort.propertyCode;
});
}
How can I fix this error with react-typescript? Thanks alot!
So it looks like you are using typescript. Typescript is a strongly-typed language, meaning that typically you have to define the datatype for a variable before you can use it. This also means that you can only set a variable to the type that it is assigned.
What this error is saying is that you are trying to assign a value that is not a string to a variable where the type is "string". More specifically, it looks like you are trying to assign a whole object (of type "Item") to a variable with the type "string".
You need to check your code and make sure that you are not trying to assign the wrong datatype somewhere. In the small code snippet that you gave, it's a bit difficult to see where the error is.
When you have to assign a type to an array callback, like (rt:Item) => and (act:Item) =>, that's a sign that some type is either missing or incorrect higher up in the chain. The callback will get the correct type automatically if the array itself has the correct type.
It seems like the error is most likely in your indexOf calls, though it's hard to know for sure without seeing your types for ResortResult and SelectedFilters. You are checking that roomTypes contains an element rt which is an Item. If roomTypes is an array of string then you would need to check roomTypes.indexOf(rt.value) instead of roomTypes.indexOf(rt).
If it is in fact an array of Item, you might have confusing results because indexOf is checking for strict equality. That is, it's checking that you have literally the same object instance in both places.
This code passes all type checks, but it might not match your actual types:
interface Item {
value: string;
}
interface ResortResult {
available: {roomType: string}[][];
activities: string[];
propertyCode: string;
city: string;
}
interface SelectedFilters {
room: Item[];
activity: Item[];
area: string[];
}
function filter(resortList: ResortResult[], selectedFilters: SelectedFilters) {
return resortList
.filter(resort => {
let roomTypes = resort.available.flat().map(room => room.roomType);
let activities = resort.activities;
let hasRoomType = selectedFilters["room"].some(
(rt) => roomTypes.indexOf(rt.value) > -1
);
let hasActivity = selectedFilters["activity"].some(
(act) => ((activities.indexOf(act.value)) > -1)
);
return !(
(selectedFilters["area"].length === 0 ||
selectedFilters["area"].indexOf(resort.city) > -1) &&
// (selectedFilters["views"].length === 0 ||
// selectedFilters["views"].indexOf(resort.city) > -1) &&
(selectedFilters["activity"].length === 0 || hasActivity) &&
(selectedFilters["room"].length === 0 || hasRoomType)
);
})
.map(resort => {
return resort.propertyCode;
});
}
TypeScript Playground Link
You have to assign a different value to a variable as you cannot assign a string to all of the variables.

How to avoid Object is possibly null for Typescript value that can be null or undefined

How can I allow nulls/undefined in this reference if the value can actually be null or undefined. My if statement doesn't seem to capture it.
I get error TS2531. Object is possibly 'null'. Please help.
type formatterType = string | number | undefined | null;
export const formatPercent = (value: formatterType, multiplier = 1): string => {
const val = numeral(value);
if (value !== null || value !== undefined) {
if (val.value()) {
return `${val.value() * multiplier}%`;
}
}
return '0%';
};
val is an instance of the Numeral class. The method value() on this class has return type number | null. Therefore, it is illegal to multiply the return value without checking for null. You cannot guard against this by calling val.value() in the if statement, as typescript has no way of knowing whether the two calls will return the same value. You must use a variable for storing the result:
let v = val.value()
if (v) {
return `${v * multiplier}%`;
}

Cannot read property 'toJS' of undefined

I have two arrays. But when one of them is null it gives the following error:
Cannot read property 'toJS' of undefined in that line
Here's the relevant call that triggers the error: {groupsGuney.toJS()}
Here's my declaration of the variables let groupsGuney, groupsKuzey;
And finally here are my two arrays. But when one of them is null it gives the error:
...
if (muso == 1) {
groupsGuney = this.props.groups
.groupBy((group, idx) => idx % maxRows)
.map((ggs, idx) => {
return this.renderGroups(ggs, idx);
}).toList().flatten(true);
}
if (muso == 2) {
groupsKuzey = this.props.groups
.groupBy((group, idx) => idx % maxRows)
.map((ggs, idx) => {
return this.renderGroups(ggs, idx);
}).toList().flatten(true);
}
var result = (
<div>
<div className={classSelector + ' discard-mini-box-area'} >
{ groupsGuney.toJS() }
</div>
<div className={classSelector + ' discard-mini-box-area'} >
{ groupsKuzey.toJS() }
</div>
</div>
);
return result;
}
}
export default DiscardMiniBoxArea;
Instead of doing:
<div>
<div className={classSelector + ' discard-mini-box-area'} >
{groupsGuney.toJS()}
</div>
....
you should do:
<div>
<div className={classSelector + ' discard-mini-box-area'} >
{groupsGuney && groupsGuney.toJS()}
</div>
....
Before calling a function on your object, you need to make sure it's there. If you're uncertain about your object having the function at all times, you will need an additional check, that makes sure toJS is there and that it's a valid function.
If that's the case, update what's inside your container to:
{groupsGuney && typeof groupsGuney.toJS === 'function' && groupsGuney.toJS()}
However, ideally, you would not render at all this specific group if what you would like to render is not there. You should move these checks to before you render your component.
My motivation here is mostly that my call .get of undefined poops itself really hard, and initializing properly all over the place helps, but doesn't catch all edge cases. I just want the data or undefined without any breakage. Specific type checking causes me to do more work later if I want it to make changes.
This looser version solves many more edge cases(most if not all extend type Iterable which has .get, and all data is eventually gotten) than a specific type check does(which usually only saves you when you try to update on the wrong type etc).
/* getValid: Checks for valid ImmutableJS type Iterable
returns valid Iterable, valid Iterable child data, or undefined
Iterable.isIterable(maybeIterable) && maybeIterable.get(['data', key], Map()), becomes
getValid(maybeIterable, ['data', key], Map())
But wait! There's more! As a result:
getValid(maybeIterable) returns the maybeIterable or undefined
and we can still say getValid(maybeIterable, null, Map()) returns the maybeIterable or Map() */
export const getValid = (maybeIterable, path, getInstead) =>
Iterable.isIterable(maybeIterable) && path
? ((typeof path === 'object' && maybeIterable.getIn(path, getInstead)) || maybeIterable.get(path, getInstead))
: Iterable.isIterable(maybeIterable) && maybeIterable || getInstead;
//Here is an untested version that a friend requested. It is slightly easier to grok.
export const getValid = (maybeIterable, path, getInstead) => {
if(valid(maybeIterable)) { // Check if it is valid
if(path) { // Check if it has a key
if(typeof path === 'object') { // Check if it is an 'array'
return maybeIterable.getIn(path, getInstead) // Get your stuff
} else {
maybeIterable.get(path, getInstead) // Get your stuff
}
} else {
return maybeIterable || getInstead; // No key? just return the valid Iterable
}
} else {
return undefined; // Not valid, return undefined, perhaps should return false here
}
}
Just give me what I am asking for or tell me no. Don't explode. I believe underscore does something similar also.

AngularJS - What is the best way to detect undefined null or empty value at the same time?

Sometimes I need to check a value for three conditions at the same time, null, undefined or "". Due that I havenĀ“t found any method to do this, I coded my own and it works.
$scope.isNullOrEmptyOrUndefined = function (value) {
if (value === "" || value === null || typeof value === "undefined") {
return true;
}
}
Just wanted to know if there is a better way to accomplish the same thing.
Thanks so much in advance,
Guillermo
How about this? Since you seem to be returning true in those null/undefined cases:
$scope.isNullOrEmptyOrUndefined = function (value) {
return !value;
}
http://jsfiddle.net/1feLd9yn/3/
Note that an empty array and empty object will also return false as they are truthy values. If you want the true/false return to be flip-flopped, then omit the ! before value.
Update
As mentioned in the comments it's better to use return !value.
$scope.isValid = function(value) {
return !value
}
Old and incomplete Answer
A proper way is simply to use angular.isDefined()
**AngularJS - What is the best way to detect undefined null or empty value at the same time?**
You can define a function or you can use inline command.
$scope.isNullOrUndefined = function(value){
return (!value) && (value === null)
}
or
var temp = null;
(temp) ? 'I am not null' : 'yes I am null or undefined';
"yes I am null or undefined"
var temp = undefined
(temp) ? 'I am not null' : 'yes I am null or undefined';
"yes I am null or undefined"
temp = '123123Rec'
(temp) ? 'I am not null' : 'yes I am null or undefined';
"I am not null"
I tried the below code, it's working perfect in my angular js application.
function isValid(value) {
return typeof value !== 'undefined';
};

Resources