Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting prop using Boolean (to trigger prop over attribute) always sets true #426

Open
trevorparscal opened this issue Sep 13, 2019 · 7 comments

Comments

@trevorparscal
Copy link

Using an Object form to trigger prop over attribute setting doesn't work with Boolean objects, because they always resolve to true when applied to boolean DOM properties expecting native booleans, such as checkbox.checked.

a = new Boolean( false );
console.log( !!a ); // outputs true
console.log( a.valueOf() ); // outputs false

By adding something like this to the inside of setProp, this can be easily resolved.

// Convert Boolean objects to native boolean values
if ( value instanceof Boolean ) {
  value = value.valueOf();
}
@iteriani
Copy link
Collaborator

It's kind of costly to always check for boolean. Is it really necessary to create Boolean objects?

@sparhami
Copy link
Contributor

I'm not sure if the cost of the check is that great, but it is additional code to compile and ship. There is a cost to allocating the Boolean object however when using new, which applies to every diff regardless of whether or not there is a change.

I think a more promising approach would be to customize the behavior as described here: http://google.github.io/incremental-dom/#setting-values

@ewinslow
Copy link

ewinslow commented Sep 14, 2019 via email

@trevorparscal
Copy link
Author

Thank you all for the ideas. I ended up making typeof boolean always use setProp, the same way typeof object and typeof function already do. This introduces a typeof check, but it's less costly than constructing the Boolean during the render plus doing an instanceof on the other side.

The customizing behavior seems like a nice idea, but I worry about bugs. If the library behaves differently from project to project in sneaky ways depending on if the same hooks are setup I suspect there will be tricky to diagnose problems from sharing code or practices.

@trevorparscal
Copy link
Author

trevorparscal commented Oct 23, 2019

I've been playing around with this a bit more. The patch I'm using (based on ewinslow's comment) adds some code to applyAttributeTyped, changing...

if (type === 'object' || type === 'function') {

to...

if (type === 'object' || type === 'function' || type === 'boolean') {

This check is very inexpensive, requires no object construction on the calling side and solves the general case of elegantly setting a boolean as a property. It's also easy to document this behavior - just as we already document the same behavior for objects and functions.

The alternative suggested is something like this:

attributes.checked = applyProp;
attributes.selected = applyProp;
attributes.disabled = applyProp;
// and so on for readOnly, multiple, isMap, defer, declare,
// noResize, noWrap, noShade, compact, etc.

This is a bit complex just to get standard HTML elements to behave as expected. Another problem with hooks is that it introduces potential conflicts when used with other types of elements, such as SVG or shadow-dom, which either have non-boolean attributes or properties with similar names or have boolean attributes or properties not explicitly listed - or both.

Are we sure it's not worth adding the boolean typeof check to the core library to resolve this problem in a simple, general and consistent way?

@sparhami
Copy link
Contributor

I've been playing around with this a bit more. The patch I'm using (based on ewinslow's comment) adds some code to applyAttributeTyped, changing...

if (type === 'object' || type === 'function') {

to...

if (type === 'object' || type === 'function' || type === 'boolean') {

This check is very inexpensive, requires no object construction on the calling side and solves the general case of elegantly setting a boolean as a property. It's also easy to document this behavior - just as we already document the same behavior for objects and functions.

The alternative suggested is something like this:

attributes.checked = applyProp;
attributes.selected = applyProp;
attributes.disabled = applyProp;
// and so on for readOnly, multiple, isMap, defer, declare,
// noResize, noWrap, noShade, compact, etc.

This is a bit complex just to get standard HTML elements to behave as expected. Another problem with hooks is that it introduces potential conflicts when used with other types of elements, such as SVG or shadow-dom, which either have non-boolean attributes or properties with similar names or have boolean attributes or properties not explicitly listed - or both.

Are we sure it's not worth adding the boolean typeof check to the core library to resolve this problem in a simple, general and consistent way?

We cannot change to use type === 'boolean'. Take for example:

let expanded = false; // This variable is set to a boolean value at some point.
...
// It now gets set as an attribute, and  converted to a string.
elementOpen('div', null, null, 'aria-expanded', expanded);

This cannot be set as a property and changing this will break existing users in a way that may not be easy to detect. I think for some uses this might not be a big concern, or they might want to have another way around like:

let expanded = false;
...
elementOpen('div', null, null, 'aria-expanded', toString(expanded));

or automatically do that a higher level construct that wraps elementOpen, but I think it all comes back to not using Incremental DOM directly, but rather configured in some sort of way for higher level usage.

@ewinslow
Copy link

@sparhami It seems like the main reason this can't be changed is for backwards compatibility reasons. There is a very clear and cheap (and I'd argue, more correct) solution, which is to use the string value:

elementOpen('div', null, null, 'aria-expanded', 'true');

Or another case, where you just need attribute presence:

elementOpen('button', null, null, 'disabled', '');

But setting-boolean-properties have much more painful workarounds.

I really think setting properties for booleans is the more intuitive behavior here. All code that assumes otherwise can be trivially changed to use strings today and still work fine. But if it's just too much trouble, then I totally get that...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants