Dynamic attribute in ReactJS - reactjs

I want to dynamically include/omit the disabled attribute on a button element. I have seen plenty of examples of dynamic attribute values, but not of attributes themselves. I have the following render function:
render: function() {
var maybeDisabled = AppStore.cartIsEmpty() ? "disabled" : "";
return <button {maybeDisabled}>Clear cart</button>
}
This throws a parse error because of the "{" character. How can I include/omit the disabled attribute based on the (boolean) result of AppStore.cartIsEmpty()?

The cleanest way to add optional attributes (including disabled and others you might want to use) is currently to use JSX spread attributes:
var Hello = React.createClass({
render: function() {
var opts = {};
if (this.props.disabled) {
opts['disabled'] = 'disabled';
}
return <button {...opts}>Hello {this.props.name}</button>;
}
});
React.render((<div><Hello name="Disabled" disabled='true' />
<Hello name="Enabled" />
</div>)
, document.getElementById('container'));
By using spread attributes, you can dynamically add (or override) whatever attributes you'd like by using a javascript object instance. In my example above, the code creates a disabled key (with a disabled value in this case) when the disabled property is passed to the Hello component instance.
If you only want to use disabled though, this answer works well.

I'm using React 16 and this works for me (where bool is your test):
<fieldset {...(bool && {disabled:true})}>
Basically, based on the test (bool) you return an object with the conditional attributes or you don't.
Also, if you need to add or omit multiple attributes you can do this:
<fieldset {...(bool && {disabled:true, something:'123'})}>
For more elaborate attribute managed I suggest you prefab the object with (or without) the attributes outside of JSX.

You can pass a boolean to the disabled attribute.
render: function() {
return <button disabled={AppStore.cartIsEmpty()}>Clear cart</button>
}
function Test() {
return (
<div>
<button disabled={false}>Clear cart</button>
<button disabled={true}>Clear cart</button>
</div>
);
}
ReactDOM.render(<Test />, document.querySelector("#test-container"));
console.log(Array.from(document.querySelectorAll("button")));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="test-container"></div>

Far cleaner than the accepted solution is the solution which AnilRedshift mentioned, but which I'll expand on.
Simply put, HTML attributes have a name and a value. As a shorthand, you can use the name only for "disabled", "multiple", etc. But the longhand version still works, and allows for React to work in it's preferred way.
disabled={disabled ? 'disabled' : undefined} is the most legible solution.

The version I used was:
<button disabled={disabled ? 'disabled' : undefined}>
Click me (or dont)
</button>

More cleaner way of doing dynamic attributes which works for any attributes is
function dynamicAttributes(attribute, value){
var opts = {};
if(typeof value !== 'undefined' && value !== null) {
opts['"'+attribute+'"'] = value;
return opts;
}
return false;
};
Call in your react component like following
<ReactComponent {...dynamicAttributes("disabled",false)}
{...dynamicAttributes("data-url",dataURL)}
{...dynamicAttributes("data-modal",true)} />
Tips :
You could have dynamicAttributes function in a common place/utilities and
import it to use it across all components
you could pass value as null to not render dynamic attribute

A simple and clean way of doing it
<button {...disabled && {disabled: true}}>Clear cart</button>
disabled should come from props like this
<MyComponent disabled />

You can find something similar to this at the documentation.
https://facebook.github.io/react/docs/transferring-props.html
In your case could be something like this
function MyComponent(props) {
let { disabled, ...attrs } = props;
if (disabled) {
// thus, it will has the disabled attribute only if it
// is disabled
attrs = {
...attrs,
disabled: true
}
};
return (
<button {...attrs}>MyLabel</button>
)
}
This code is using ES6, but I thing you got the idea.
This is cool because you can pass many others attributes to this component and it will still work.

First you can simply check
<button disabled={true}>Button 1</button>
<button disabled={false}>Button 2</button>
Note: **disabled value is not String, it should be Boolean.
Now for dynamic. You can simply write
<button type="button" disabled={disable}
onClick={()=>this.setSelectValue('sms')} >{this.state.sms}</button>
As you can see I am using disabled property and in curly brackets it can be local variable/state var.
The variable disable contains values true/false.

In case others come here for attributes other than disabled, e.g., custom attributes like data-attr, one can assign empty string "" as the value in the object to be spread to eliminate the value of the attribute. With the attribute name only available on the resulted HTML.
For example:
<div {...(trueOrFalse && { [`data-attr`]: "" })}></div>
Furthermore, if you wish the name of the attribute being dynamic too. Due to template strings support string interpolation, you can put state into it to make the name of attribute dynamic.
<div {...(trueOrFalse && { [`${state}`]: "" })}></div>

This could work, problem with disabled is one could not simply set boolean for it.
if(AppStore.cartIsEmpty()){
return "<button disabled>Clear cart</button>"
}
else
{
return "<button>Clear cart</button>"
}

Related

How to conditionally set HTML attributes in JSX using reason-react?

I want to render an HTML checkbox whose checked state is controlled by data.
Give a stateless component that receives an item type { label: string, checked: bool},
Like so:
let component = ReasonReact.statelessComponent("TodoItem");
let make = (~item, _children) => {
render: _self => {
<li> <input type_="checkbox" {/*looking for something like this*/ item.checked ? "checked" : "" /* doesn't compile */}/> {ReasonReact.string(item.label)} </li>
}
}
How do I add the presence of the attribute checked to the input tag based on the item.checked == true condition?
As #wegry said in a comment, it seems to fit your use case better to just pass the value directly since item.checked already is a boolean, and checked takes a boolean.
But to answer more generally, since JSX attributes are just optional function arguments under the hood, you can use a neat syntactic trick to be able to explicitly pass an option to it: Just precede the value with ?. With your example:
let component = ReasonReact.statelessComponent("TodoItem");
let make = (~item, _children) => {
render: _self => {
<li> <input type_="checkbox" checked=?(item.checked ? Some(true) : None) /> {ReasonReact.string(item.label)} </li>
}
}
Or, to give an example where you already have an option:
let link = (~url=?, label) =>
<a href=?url> {ReasonReact.string(label)} </a>
This is documented in the section titled Explicitly Passed Optional on the Function page in the Reason docs.

React JSX - dynamic html attribute

In JSX, we can indicate attribute value dynamically like:
<div className={this.state.className}>This is a div.</div>
Is it possible to indicate attribute (including attribute name and attribute value) dynamically? Like:
const value = emptyValue ? "" : "value='test'";
<input {value} />
That means, once emptyValue is true, "input" tag should not include "value" attribute (value="" is different from no value attribute, as one is show empty in input field, another is show existing text in input field).
ES6 object expansion only works for objects. Therefore to generate a dynamic attribute, try something like this:
const value = emptyValue ? {} : { value: 'test' }
<a {...value} ></a>
Note value will always be an object.
you can insert whole element in if statement in render function, but before return like this:
render() {
var input = (<input />);
if (!emptyValue) {
input = (<input value='test'/>)
}
return (
<div>
{input}
</div>
)
}

React Component passes Proxy object instead of Event object to the handler function

I have prepared the following React Component (React version 1.5.2):
var QuickSearch = React.createClass({
searchHandler: function(){
this.props.parent.props.dataSource.search = this.refs.SearchInput.value;
this.props.parent.props.dataSource.setPage(1);
this.props.parent.getData();
},
refreshHandler: function(){
this.props.parent.props.dataSource.search = this.refs.SearchInput.value;
this.props.parent.getData();
},
myEventHandler: function(evt){
console.log(evt);
if(evt.keyCode === 13) {
evt.stopPropagation();
this.searchHandler();
}
},
render: function(){
/* Translation function from table model */
_ = this.props.parent.props.table_model.gettrans;
return(
<div className="reactable-quicksearch-wrapper">
<input ref="SearchInput" type="text" onKeyPress={this.myEventHandler} placeholder={_('Search phrase...')} />
<button ref="SearchButton" type="button" onClick={this.searchHandler}>{_('Search')}</button>
<button ref="RefreshButton" type="button" onClick={this.refreshHandler}>{_('Refresh')}</button>
</div>
);
}
});
myEventHandler function as "evt" passes Proxy object that contain "target" (basically an input) and handler:
Proxy { <target>: Object, <handler>: Object }
I am no sure why, but it seems to behave like "submit" (??) Anyways, from what I've read react should pass standard event object, but it doesn't.
What can cause this kind of behaviour?
This is the expected behaviour. React doesn't use native events to work out browser inconsistencies and uses SyntheticEvents. Something looks weird though. IIRC classname is SyntheticEvent, not Proxy.

How to use parent function inside react's map function

I'm passing the following functions to the following component...
<Main css={this.props.css} select={this.selectedCSS.bind(this)} save={this.saveCSS.bind(this)} />
Then inside the Main component I am using these functions...
<h1>Select the stylesheet you wish to clean</h1>
{
this.props.css.map(function(style){
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
})
}
</div>
<button className="cleanBtn" onClick={this.props.save}>Clean!</button>
Notice in my map function, I am passing this.props.select(style). This is a function from the parent, and I am trying to pass to it a parameter. But when I do this, I get an error...
Error in event handler for runtime.onMessage: TypeError: Cannot read property 'props' of undefined
Every other function I pass works. I've already tested them. In fact, the code works, the only issue is when I try to pass a function inside map. What is the reason for this? How can I fix it?
I tried to add .bind(this) but it runs in an infinite loop when I do so.
The problem is that Array.prototype.map doesn't bind a this context unless explicitly told to.
this.props.css.map(function(style) {
...
}, this) // binding this explicitly
OR
this.props.css.map((style) => { // arrow function
...
})
OR
const self = this;
this.props.css.map((style) => {
... // access 'self.props.select'
})
I also see another problem with your code. Inside map you're doing this
if (style) {
return (
<div className="inputWrap">
<input type="radio" name="style_name" onClick={this.props.select(style)}/>
<span>something</span>
<a key={style}>{style}</a>
</div>
);
}
Here input element is expecting a function for its onClick, but you're actually evaluating the function by calling this.props.select(style) and passing its return value (if at all it returns something) to onClick. Instead you may need to do this:
this.props.css.map((style) => {
if (style) {
return (
<div className="inputWrap">
<input type="radio" name="style_name" onClick={() => this.props.select(style)}/>
<span>something</span>
<a key={style}>{style}</a>
</div>
);
}
})
In your mapping function, this does no longer point to the react component.
Bind the context manually to resolve this:
{
this.props.css.map((function(style) {
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
}).bind(this))
}
Alternatively, use an ES6 arrow function, which preserves the surrounding context:
{
this.props.css.map(style => {
if (style) {
return (<div className="inputWrap"><input type="radio" name="style_name" onClick={this.props.select(style)}/><span></span><a key={style}>{style}</a></div>)
}
})
}
You mentioned passing a parameter when calling a parent's function? As onClick wants a reference to a function (but realising that you need to pass a parameter), you could try the following:
onClick={() => { this.props.select(style) }}

how to toggle medium editor option on click using angularjs

I am trying to toggle the medium editor option (disableEditing) on button click. On the click the value for the medium editor option is changed but the medium editor does not use 'updated' value.
AngularJS Controller
angular.module('myApp').controller('MyCtrl',
function MyCtrl($scope) {
$scope.isDisableEdit = false;
});
Html Template
<div ng-app='myApp' ng-controller="MyCtrl">
<span class='position-left' medium-editor ng-model='editModel' bind-options="{'disableEditing': isDisableEdit, 'placeholder': {'text': 'type here'}}"></span>
<button class='position-right' ng-click='isDisableEdit = !isDisableEdit'>
Click to Toggle Editing
</button>
<span class='position-right'>
toggle value - {{isDisableEdit}}
</span>
</div>
I have created a jsfiddle demo.
I think initialising medium editor on 'click' could solve the issue, but i am not sure how to do that either.
using thijsw angular medium editor and yabwe medium editor
For this specific use case, you could try just disabling/enabling the editor when the button is clicked:
var editor = new MediumEditor(iElement);
function onClick(event) {
if (editor.isActive) {
editor.destroy();
} else {
editor.setup();
}
}
In the above example, the onClick function is a handler for that toggle button you defined.
If you're just trying to enable/disable the user's ability to edit, I think those helpers should work for you.
MediumEditor does not currently support changing configuration options on an already existing instance. So, if you were actually trying to change a value for a MediumEditor option (ie disableEditing) you would need to .destroy() the previous instance, and create a new instance of the editor:
var editor = new MediumEditor(iElement),
editingAllowed = true;
function onClick(event) {
editor.destroy();
if (editingAllowed) {
editor = new MediumEditor(iElement, { disableEditing: true });
} else {
editor = new MediumEditor(iElement);
}
editingAllowed = !editingAllowed;
}
Once instantiated, you can use .setup() and .destroy() helper methods to tear-down and re-initialize the editor respectively. However, you cannot pass new options unless you create a new instance of the editor itself.
One last note, you were calling the init() method above. This method is not officially supported or documented and it may be going away in future releases, so I would definitely avoid calling that method if you can.
Or you could just use this dirty hack : duplicate the medium-editor element (one with disableEditing enabled, the other with disableEditing disabled), and show only one at a time with ng-show / ng-hide :)
<span ng-show='isDisableEdit' class='position-left' medium-editor ng-model='editModel' bind-options="{'disableEditing': true ,'disableReturn': isDisableEdit, 'placeholder': {'text': 'type here'}}"></span>
<span ng-hide='isDisableEdit' class='position-left' medium-editor ng-model='editModel' bind-options="{'disableEditing':false ,'disableReturn': isDisableEdit, 'placeholder': {'text': 'type here'}}"></span>
You can see jsfiddle.

Resources