Conditionally rendering content in react - javascript

I am having a functional component that takes two items as props. The values of each properties could be undefined, "", null, "null", or a valid string (for example, "test").
I need to conditionally render these props based on values
if prop1 and prop2 are both present then it should display as prop1(prop2), if either one of them are present then it should either be prop1 or just prop2. In case both of them are not present then should display "Not Available". Only valid strings should be taken. If values has either undefined, "", null, "null" it should not be displayed.
I am having trouble building up the logic. This is what I have tried.
const Test = (props) => {
let { prop1, prop2 } = props;
let content: string;
if ((!prop1 || prop1 === "null") && (!prop2 || prop2 === "null")) {
content = "Not Available";
} else if (!!prop1 && !!prop2) {
content = `${prop1} (${prop2})`;
} else {
content = `${prop1 || prop2}`;
}
return (
<>
{content}
</>
);
}

This maybe one way to achieve the desired objective:
const isPropInvalid = inp => [undefined, null, '', ' '].includes(inp);
const content = isPropInvalid(prop1)
? isPropInvalid(prop2) ? 'Not Available' : prop2
: isPropInvalid(prop2) ? prop1 : `${prop1} (${prop2})`
Explanation
set-up an array with elements that are considered 'invalid' (ie, undefined, null, '', 'null')
use simple if-else or ?: ternary-operators to assign the appropriate value to content based on whether one or both props are invalid.
Code Snippet
const isPropInvalid = inp => [undefined, null, '', 'null'].includes(inp);
const content = (prop1, prop2) => isPropInvalid(prop1)
? isPropInvalid(prop2) ? 'Not Available' : prop2
: isPropInvalid(prop2) ? prop1 : `${prop1} (${prop2})`;
console.log(content('abc', 'xyz'));
console.log(content());
console.log(content(null, 'xyz'));
console.log(content('abc', 'null'));

I guess this is a tricky condition by itself, so I wouldn't worry too much if it looks a bit weird.
One think you can do tho is to organize the component code in variables and smaller functions like so:
const isDefined = (value) => value !== "null" && !!value
const buildString = (prop1, prop2) => {
let string = prop1 || prop2
return prop1 && prop2
? `${string} (${prop2})`
: string
}
const Test = ({ prop1, prop2 }) => {
const someDefined = isDefined(prop1) || isDefined(prop2);
return (
<>
{!someAreDefined && "Not Available"}
{someAreDefined && buildString(prop1, prop2)}
</>
);
}
I think that helps a lot with readability and understanding the flow and possible outputs for this component.

How about a separate function that returns whether or not the value is valid?
function propIsValid(value) {
// valid if truthy and not "null" (string)
return !!prop && value != "null";
}
The above checks for a thuthy value, which means that all falsy values are considered invalid. This includes null, undefined and "", but also false, NaN and 0. Which might or might not be a problem, depending on your context.
If you want a more target approach you could use the following:
const invalid = [undefined, null, "", "null"];
return !invalid.includes(value);
Then simplify your component to:
const Test = ({ prop1, prop2 }) => {
const allValid = [prop1, prop2].every(propIsValid);
const firstValid = [prop1, prop2].find(propIsValid);
if (allValid) {
return <>{prop1}({prop2})</>;
} else if (firstValid) {
return <>{firstValid}</>;
} else {
return <>Not Available</>;
}
}
This uses every() to check if both are valid, but this could also be written as propIsValid(prop1) && propIsValid(prop2). find() is used to find the first valid value (if any), this does assume that a valid value is always thuthy.

Just use a ternary
(prop1 && !/null/.test(prop1)) && (prop2 && !/null/.test(prop2)) ? `${prop1}(${prop2})` : prop1 && !/null/.test(prop1) ? `${prop1}` : prop2 && !/null/.test(prop2) ? `${prop2}` : 'Not Available';
Since undefined "" and null are falsey in nature it will only use regex to test for the pattern null if it needs to. If there are any other values that you want to be considered invalid, just add them to the regex.

Related

JavaScript multiple condition filtering with array of conditions?

I have a long array by which now i need to filter it based on users conditions:
My condition sample,
const filterCond = [{AC:'true'},{nonAC:'true'},{sleeper:'true'}];
And my array:
const array = [
{
"AC": "false",
"nonAC": "true",
"sleeper":"true",
"availCatCard": "false",
},
{
"AC": "true",
"nonAC": "false",
"sleeper":"false",
"availCatCard": "false",
},
{
"AC": "true",
"nonAC": "true",
"sleeper":"false",
"availCatCard": "false",
}
]
By this, I have multiple conditions;
Either AC or nonAC, but sometimes it can be only AC and it can be only nonAC
Sleeper & seater should be in or "||", sometimes it can be only Sleeper and it can be only seater
Like wise, the conditions goes on, so far now am using redux and so collecting user selected filters in an array and its "filterCond" (check above).
So far i have tried like this :
function filter(props){
if( _.some(props, {AC:'true'}) && _.some(props, {nonAC:'true'}) === false )
{
return console.log(_.filter(array, {AC:'true'}).length)
}
else if( _.some(props, {nonAC:'true'}) && _.some(props, {AC:'true'}) === false )
{
return console.log(_.filter(array, {nonAC:'true'}).length)
}
else if( _.some(props, {AC:'true'}) === true && _.some(props, {AC:'true'}) === true )
{
return console.log(_.filter(array, function(data) {
return data.AC === 'true' || data.nonAC === 'true';
}).length)
}
};
console.log(filter([{AC:'true'},{nonAC:'true'},{sleeper:'true'}]));
And when the conditions goes beyond the codes extends and for now am doing things for AC
& nonAC but how to perform that including sleeper :( totally confused
You can use a higher order filter function to handle applying filter conditions and is passed to your array.prototype.filter function.
const filterData = ({ filters = [], every = false }) => (el) =>
filters[every ? "every" : "some"]((filterFn) => filterFn(el));
This consumes an options object that defines an array of filter condition functions that accept a single element value/object from your data array and returns the function used for the array filtering.
Example:
// AC AND nonAC
data.filter(filterData({
filters: [
({ AC, nonAC }) => AC && nonAC,
],
}))
Note
If your data is in the form
"AC": "false"
Where the booleans are stringified then make the following adjustment to your condition checkers, compare to === 'true':
({ AC, nonAC }) => AC === "true" || nonAC === "true"
Edit
After discussion it seems some of these conditions are not mutually inclusive, or rather, they are only mutually inclusive in specific groups.
Still using a Higher Order Function, the logic is tweaked a bit to consume the array of key-value conditions.
Create a mapping function to map your user selected filters ([{ AC: "true" }, ...]) to a groups of "filterBy" values that are mutually inclusive. These will be logical OR'd (||) together while the sets will be exclusive by using logical AND'd (&&) together.
const mapfiltersToSets = (filters) => {
const filtersObject = filters.reduce((conditions, condition) => {
const [key, value] = Object.entries(condition).pop();
switch (key) {
case "AC":
case "nonAC":
return {
...conditions,
acConditions: [...(conditions.acConditions || []), { key, value }]
};
case "seater":
case "sleeper":
return {
...conditions,
seatSleeper: [...(conditions.seatSleeper || []), { key, value }]
};
// add new AND groups here
default:
return conditions;
}
}, {});
return Object.values(filtersObject);
};
const filterData = (filters = []) => (el) =>
mapfiltersToSets(filters).every((filterSet) =>
filterSet.some(({ key, value }) => el[key] === value)
);
Example:
// AC AND nonAC
data.filter(filterData([{ AC: "true" }, { nonAC: "true" }]))
Demo with extended dataset and unit tests. Good start to build from.
Maybe I misunderstood your question, but here is my attempt on creating some code that will check whether all conditions defined in filterCond are fulfilled.
I reformatted your filterCond into an easier to process array fc. After that the actual filtering becomes almost trivial: fc.every(([k,v])=>a[k]==v ). => every filter condition in the fc-array is checked whether it contains the same value as the corresponding property in one of the array arr's element a.
const arr = [
{"AC": "false","nonAC": "true","sleeper":"true","availCatCard": "false"},
{"AC": "true","nonAC": "false","sleeper":"false","availCatCard": "false"},
{"AC": "true","nonAC": "true","sleeper":"false","availCatCard": "false"} ],
filterCond = [{ AC: 'false'}, {nonAC: 'true'}, {sleeper: 'true'}],
// create an array with uniuqe filter conditions:
// [["AC","false"],["nonAC","true"],["sleeper":"true"]]
fc=Object.entries(filterCond.reduce((a,c)=>(Object.entries(c).forEach(([k,v])=>a[k]=v),a),{}));
console.log(arr.filter(a=>fc.every(([k,v])=>a[k]==v ) ) )
For the purpose of this snippet I changed your filter condition since the original conditions would result in an empty array.
const filterArray = (array, conditions) => {
const returnArr = []
array.forEach(item => {
let itemIsValid = true
conditions.forEach(cond => {
if(item[cond] !== "true") itemIsValid = false
})
if(itemIsValid)
returnArr.push(item)
else itemIsValid=true
})
return returnArr;
}
Then call: filterArray(array, ["AC","nonAC"]) for AC:true & nonAc:true, and others likewise

How can I check if an object has an empty value on a key?

I am working on a form validation and I need to check when there is an empty value.
So far the validation goes like this:
const areFieldsFilledOut = () => {
if (
(size(startupThirdStepForm) === 9 &&
!has(startupThirdStepForm, 'middleName')) ||
size(startupThirdStepForm) === 10
) {
stepThreeCardSelectedActionHandler(true);
return false;
}
if (
has(startupThirdStepForm.middleName) &&
!startupThirdStepForm.middleName.length
) {
stepThreeCardSelectedActionHandler(true);
return false;
}
return 'disabled';
};
That middle name thing is just something that is not required.
The object could have around 15 keys maximum. So all I want to do with lodash -hopefully- is one more validation like this:
(pseudo code)
if (
startupThirdStepForm has any key with an empty value
) {
stepThreeCardSelectedActionHandler(false);
return true;
}
startupThirdStepForm is the object containing what I need to check. It is an empty object but the keys/values are created dynamically.
So I need to return true like in the pseudo code above, when there is something like this:
startupThirdStepForm: { key1: 'I have a value', key2: '' }
And return false when every key has a proper value, not an empty one.
You can use _.some() to iterate the object's property, and check if a value is an empty string with _.isEqual().
const optional = ['middle']
const startupThirdStepForm = { key1: 'I have a value', key2: '', middle: '' }
const result = _.some(_.omit(startupThirdStepForm, optional), _.partial(_.isEqual, ''))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
And the same idea with lodash/fp:
const fn = optional => _.flow(
_.omit(optional),
_.some(_.isEqual(''))
)
const optional = ['middle']
const withoutOptional = fn(optional)
console.log(withoutOptional({ key1: 'I have a value', key2: '' })) // true
console.log(withoutOptional({ key1: 'I have a value', middle: '' })) // false
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
If it's only about own properties you can use Object.values to get every property value as an array and then use .some to check if any of them are empty:
if (Object.values(startupThirdStepForm).some(v => v === '')) {
}

How to reduce this if statement check in JavaScript

How can I reduce this if statement in JavaScript
if(obj.attributes && obj.attributes.email === 'test#test.com') { ... }
The line by it self is clear, however if you are looking a way to write less && operator inside you can always put things outside of the comparison such as.
var attributes = obj.attributes || {};
if ( attributes.email === 'test#test.com' ) {
}
This makes sense if you need to make multiple checks instead of a single one, however if is a single comparison it seems like the code you already have is okay as you are making sure attributes is defined before accessing an undefined property.
On the other hand if you have support for ES 2015 you can destruct stuff like:
const { attributes = {} } = obj;
if ( attributes.email === 'test#test.com' ) {
}
You can create a reusable get function using Array.reduce(). The function parameters are a path, the object, and a defaultValue (default defaultValue is undefined). It will iterate the path, and try to extract the value, if it fails, it will return the defaultValue:
const get = (path, obj, defaultValue) => obj ?
path.reduce((r, k) => r && typeof r === 'object' ? r[k] : defaultValue, obj)
:
defaultValue;
if(get(['attributes', 'email'], null) === 'test#test.com') { console.log(1) }
if(get(['attributes', 'email'], {}) === 'test#test.com') { console.log(2) }
if(get(['attributes', 'email'], { attributes: {} }) === 'test#test.com') { console.log(3) }
if(get(['attributes', 'email'], { attributes: { email: 'test#test.com' } }) === 'test#test.com') { console.log(4) }
There is a TC39 stage proposal called "Optional Chaining for JavaScript". If it will make it's way to the language, it will add an the optional chaining operator - ?. Now if attributes don't exist, it will return undefined.
Example: obj.attributes?.email
It's usable today via babel plugin.

Cannot read property 'length' of undefined react

TypeError: Cannot read property 'length' of undefined
That's what the compiler says when I run my react app. What I do need to do with this?
request = (start,end) => {
if(this.state.teams.length < 1){
axios.get(`${ URL }/teams`)
.then( response => {
this.setState({
teams:response.data
})
})
}
axios.get(`${ URL }/articles?_start=${start}&_end=${end}`)
.then( response => {
this.setState({
items:[...this.state.items,...response.data]
})
})
}
I would suggest to check first if the props is undefined or empty or even declared.
for example:-
const card = props && props.cards && props.cards.length > 0 ?
props.cards.map((card, i) => {
return (
<card >
)
}) : '';
And return your card.
I would suggest using a check to see if "teams" is undefined before trying to get the length.
if (value === undefined) {
// value is undefined
}
Be sure that the teams value of your component's state is initialized with an array value like this :
class MyComponent extends React.Component {
state: {
teams: [],
};
}
Probably because you're having a variable that is supposed to contain an array but is undefined when referencing the length property. Try searching for .length in your editor and create an empty array if it's not initialized:
if ((arr || []).length > 0) {
// do something
}
// or
if (arr?.length > 0) {
// do something
}

destructure object only if it's truthy

Suppose I want to destructure my function argument like this
const func = ({field: {subField}}) => subField;
How can I prevent this from throwing an error if field is undefined or null ?
You might use a default value:
const func = ({field: {subField} = {}}) => subField;
It works only with {field: undefined} though, not with null as a value. For that I'd just use
const func = ({field}) => field == null ? null : field.subField;
// or if you don't care about getting both null or undefined respectively
const func = ({field}) => field && field.subField;
See also javascript test for existence of nested object key for general solutions.
You could only part destruction and use for subField a parameter with a check.
var fn = ({ field }, subField = field && field.subField) => subField;
console.log(fn({ field: null }));
A good way to fix both the cases of null and undefined is the following
const func = ({field}) => {
let subField = null;
if(field) {
({subField} = field);
}
return subField
};
If you only want to handle the case when field is undefined, you could just to
const func = ({field: {subField} = {}}) => subField;
whereby if field is undefined the default empty object is used as its value

Categories