We use Backbone + ReactJS bundle to build a client-side app.
Heavily relying on notorious valueLink we propagate values directly to the model via own wrapper that supports ReactJS interface for two way binding.
Now we faced the problem:
We have jquery.mask.js plugin which formats input value programmatically thus it doesn't fire React events. All this leads to situation when model receives unformatted values from user input and misses formatted ones from plugin.
It seems that React has plenty of event handling strategies depending on browser. Is there any common way to trigger change event for particular DOM element so that React will hear it?
For React 16 and React >=15.6
Setter .value= is not working as we wanted because React library overrides input value setter but we can call the function directly on the input as context.
var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, 'react 16 value');
var ev2 = new Event('input', { bubbles: true});
input.dispatchEvent(ev2);
For textarea element you should use prototype of HTMLTextAreaElement class.
New codepen example.
All credits to this contributor and his solution
Outdated answer only for React <=15.5
With react-dom ^15.6.0 you can use simulated flag on the event object for the event to pass through
var ev = new Event('input', { bubbles: true});
ev.simulated = true;
element.value = 'Something new';
element.dispatchEvent(ev);
I made a codepen with an example
To understand why new flag is needed I found this comment very helpful:
The input logic in React now dedupe's change events so they don't fire
more than once per value. It listens for both browser onChange/onInput
events as well as sets on the DOM node value prop (when you update the
value via javascript). This has the side effect of meaning that if you
update the input's value manually input.value = 'foo' then dispatch a
ChangeEvent with { target: input } React will register both the set
and the event, see it's value is still `'foo', consider it a duplicate
event and swallow it.
This works fine in normal cases because a "real" browser initiated
event doesn't trigger sets on the element.value. You can bail out of
this logic secretly by tagging the event you trigger with a simulated
flag and react will always fire the event.
https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128
At least on text inputs, it appears that onChange is listening for input events:
var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
Expanding on the answer from Grin/Dan Abramov, this works across multiple input types. Tested in React >= 15.5
const inputTypes = [
window.HTMLInputElement,
window.HTMLSelectElement,
window.HTMLTextAreaElement,
];
export const triggerInputChange = (node, value = '') => {
// only process the change on elements we know have a value setter in their constructor
if ( inputTypes.indexOf(node.__proto__.constructor) >-1 ) {
const setValue = Object.getOwnPropertyDescriptor(node.__proto__, 'value').set;
const event = new Event('input', { bubbles: true });
setValue.call(node, value);
node.dispatchEvent(event);
}
};
I know this answer comes a little late but I recently faced a similar problem. I wanted to trigger an event on a nested component. I had a list with radio and check box type widgets (they were divs that behaved like checkboxes and/or radio buttons) and in some other place in the application, if someone closed a toolbox, I needed to uncheck one.
I found a pretty simple solution, not sure if this is best practice but it works.
var event = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': false
});
var node = document.getElementById('nodeMyComponentsEventIsConnectedTo');
node.dispatchEvent(event);
This triggered the click event on the domNode and my handler attached via react was indeed called so it behaves like I would expect if someone clicked on the element. I have not tested onChange but it should work, and not sure how this will fair in really old versions of IE but I believe the MouseEvent is supported in at least IE9 and up.
I eventually moved away from this for my particular use case because my component was very small (only a part of my application used react since i'm still learning it) and I could achieve the same thing another way without getting references to dom nodes.
UPDATE:
As others have stated in the comments, it is better to use this.refs.refname to get a reference to a dom node. In this case, refname is the ref you attached to your component via <MyComponent ref='refname' />.
You can simulate events using ReactTestUtils but that's designed for unit testing.
I'd recommend not using valueLink for this case and simply listening to change events fired by the plugin and updating the input's state in response. The two-way binding utils more as a demo than anything else; they're included in addons only to emphasize the fact that pure two-way binding isn't appropriate for most applications and that you usually need more application logic to describe the interactions in your app.
I stumbled upon the same issue today. While there is default support for the 'click', 'focus', 'blur' events out of the box in JavaScript, other useful events such as 'change', 'input' are not implemented (yet).
I came up with this generic solution and refactored the code based on the accepted answers.
export const triggerNativeEventFor = (elm, { event, ...valueObj }) => {
if (!(elm instanceof Element)) {
throw new Error(`Expected an Element but received ${elm} instead!`);
}
const [prop, value] = Object.entries(valueObj)[0] ?? [];
const desc = Object.getOwnPropertyDescriptor(elm.__proto__, prop);
desc?.set?.call(elm, value);
elm.dispatchEvent(new Event(event, { bubbles: true }));
};
How does it work?
triggerNativeEventFor(inputRef.current, { event: 'input', value: '' });
Any 2nd property you pass after the 'event' key-value pair, it will be taken into account and the rest will be ignored/discarded.
This is purposedfully written like this in order not to clutter arguments definition of the helper function.
The reason as to why not default to get descriptor for 'value' only is that for instance, if you have a native checkbox <input type="checkbox" />, than it doesn't have a value rather a 'checked' prop/attribute. Then you can pass your desired check state as follows:
triggerNativeEventFor(checkBoxRef.current, { event: 'input', checked: false });
I found this on React's Github issues: Works like a charm (v15.6.2)
Here is how I implemented to a Text input:
changeInputValue = newValue => {
const e = new Event('input', { bubbles: true })
const input = document.querySelector('input[name=' + this.props.name + ']')
console.log('input', input)
this.setNativeValue(input, newValue)
input.dispatchEvent(e)
}
setNativeValue (element, value) {
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set
const prototype = Object.getPrototypeOf(element)
const prototypeValueSetter = Object.getOwnPropertyDescriptor(
prototype,
'value'
).set
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value)
} else {
valueSetter.call(element, value)
}
}
For HTMLSelectElement, i.e. <select>
var element = document.getElementById("element-id");
var trigger = Object.getOwnPropertyDescriptor(
window.HTMLSelectElement.prototype,
"value"
).set;
trigger.call(element, 4); // 4 is the select option's value we want to set
var event = new Event("change", { bubbles: true });
element.dispatchEvent(event);
Triggering change events on arbitrary elements creates dependencies between components which are hard to reason about. It's better to stick with React's one-way data flow.
There is no simple snippet to trigger React's change event. The logic is implemented in ChangeEventPlugin.js and there are different code branches for different input types and browsers. Moreover, the implementation details vary across versions of React.
I have built react-trigger-change that does the thing, but it is intended to be used for testing, not as a production dependency:
let node;
ReactDOM.render(
<input
onChange={() => console.log('changed')}
ref={(input) => { node = input; }}
/>,
mountNode
);
reactTriggerChange(node); // 'changed' is logged
CodePen
well since we use functions to handle an onchange event, we can do it like this:
class Form extends Component {
constructor(props) {
super(props);
this.handlePasswordChange = this.handlePasswordChange.bind(this);
this.state = { password: '' }
}
aForceChange() {
// something happened and a passwordChange
// needs to be triggered!!
// simple, just call the onChange handler
this.handlePasswordChange('my password');
}
handlePasswordChange(value) {
// do something
}
render() {
return (
<input type="text" value={this.state.password} onChange={changeEvent => this.handlePasswordChange(changeEvent.target.value)} />
);
}
}
The Event type input did not work for me on <select> but changing it to change works
useEffect(() => {
var event = new Event('change', { bubbles: true });
selectRef.current.dispatchEvent(event); // ref to the select control
}, [props.items]);
This ugly solution is what worked for me:
let ev = new CustomEvent('change', { bubbles: true });
Object.defineProperty(ev, 'target', {writable: false, value: inpt });
Object.defineProperty(ev, 'currentTarget', {writable: false, value: inpt });
const rHandle = Object.keys(inpt).find(k => k.startsWith("__reactEventHandlers"))
inpt[rHandle].onChange(ev);
A working solution can depend a bit on the implementation of the onChange function you're trying to trigger. Something that worked for me was to reach into the react props attached to the DOM element and call the function directly.
I created a helper function to grab the react props since they're suffixed with a hash like .__reactProps$fdb7odfwyz
It's probably not the most robust but it's good to know it's an option.
function getReactProps(el) {
const keys = Object.keys(el);
const propKey = keys.find(key => key.includes('reactProps'));
return el[propKey];
}
const el = document.querySelector('XX');
getReactProps(el).onChange({ target: { value: id } });
Since the onChange function was only using target.value I could pass a simple object to onChange to trigger my change.
This method can also help with stubborn react owned DOM elements that are listing for onMouseDown and do not respond to .click() like you'd expect.
getReactProps(el).onMouseDown(new Event('click'));
If you are using Backbone and React, I'd recommend one of the following,
Backbone.React.Component
react.backbone
They both help integrate Backbone models and collections with React views. You can use Backbone events just like you do with Backbone views. I've dabbled in both and didn't see much of a difference except one is a mixin and the other changes React.createClass to React.createBackboneClass.
Related
In form.io form builder i am adding custom validation but the default is to trigger the validation on change i want to set it to on blur.
I have tried the following code on the custom validation tab:
let field = document.querySelector('input[name="data[nametest]"]');
field.addEventListener("blur", checkValidation);
function checkValidation() {
console.log('checking...');
valid = (input.length > 5) ? true : 'Test name must be at least 5 characters long' ;
}
the code is running on blur but it is not showing the error the valid global variable is set to the correct error message its just not showing on the form also i notice that the more characters on the textfield the more the event gets trigger on blur, I would be gratefull for any help.
Thanks!
When you are in the Form Builder view, validity checks are disabled. You may test simple validations in the component settings modal:
Now, in case you want to trigger an action on blur, you must consider that custom validation is not persistent and is calculated on every evaluation, meaning that whatever you do with it after it has been evaluated will not affect the component itself. You will need to attach the event directly into the component instance.
You can achieve this in two ways:
Using a Hidden Component
Create a hidden component in your form and set it to not persistent (you don't want to store a value, just run) and define a custom default script to run at form rendered.
const { root } = instance;
const comp = root ? root.getComponent('key') : null;
if (comp) {
// remove any listener to avoid duplicates
comp.off('blur');
// define the on blur listener
comp.on('blur', () => {
console.log('blur');
});
}
On Form Ready
Formio.createForm(document.getElementById('formio'), form).then((formio) => {
const component = formio.getComponent('key');
if (component) {
component.on('focus', () => {
console.log('focus');
});
component.on('blur', () => {
console.log('blur');
});
}
});
Here is a working example: https://jsfiddle.net/airarrazaval/ongcuwt2/
I don't know since which version this option exists, but at least with version 4.13 the form builder has a specific property to address your concern - Validate On:
This should work perfectly fine with your custom validation code, as well.
I have setup an application which exports web components from angular 7 using angular custom-elements package.
Everything works fine. I am able to bind anything, arrays, objects, strings from javascript using the element instance:
const table = document.createElement('my-table-element');
table.someInputProp = 'Works Great';
table.otherInput = ['my', 'working', 'array'];
The problem comes when I try to bind to a literal false value:
table.myBooleanInput = false
This doesnt change anything on the component #Input myBooleanInput = true
The value is still true for ever. No matter how many times it changes to false.
I'am able to bind it as a truthy value which it renders just fine. The problem is only when using literal false.
Here is a working reproduction of this issue:
https://stackblitz.com/edit/angular-elements-official-example-q4ueqr
Thanks in advance.
PS: If I use the web components from within another angular app, the binding works fine.
I'm actually having this same issue. Was debugging it and there seems to be some code the #angular/elements (I'm using version 7.2.15) package which skips over setting properties on initialize when they evaluate to false.
/** Set any stored initial inputs on the component's properties. */
ComponentNgElementStrategy.prototype.initializeInputs = function () {
var _this = this;
this.componentFactory.inputs.forEach(function (_a) {
var propName = _a.propName;
var initialValue = _this.initialInputValues.get(propName);
if (initialValue) {
_this.setInputValue(propName, initialValue);
}
else {
// Keep track of inputs that were not initialized in case we need to know this for
// calling ngOnChanges with SimpleChanges
_this.uninitializedInputs.add(propName);
}
});
this.initialInputValues.clear();
};
As a workaround you could convert the flag to a string, wrap it in an object, or look at the element attributes from within the component.
I'm using Element UI and things have changed since the release of Vue.js 2.3
I have a dialog that should be displayed only if the following condition is met private.userCanManageUsers && private.pendingUsers.length > 0 && private.pendingDialogVisible
I'm trying to use the new attribute visible.sync documentation here
It is working if the condition contains only one condition but does not work with several.
Working
<el-dialog
:visible.sync="private.pendingDialogVisible"
</el-dialog>
Not working
<el-dialog
:visible.sync="private.userCanManageUsers && private.pendingUsers.length > 0 && private.pendingDialogVisible"
</el-dialog>
What is the solution to use the el-dialog with visible.sync with
several condition?
If this is impossible what should I do to make it work ?
The issue is caused by a misunderstanding of what sync is actually doing.
In Vue 2.3 (unlike in Vue 1x), sync is nothing more than an event registration to facilitate two-way binding. Per the documentation:
In 2.3 we re-introduced the .sync modifier for props, but this time it
is just syntax sugar that automatically expands into an additional
v-on listener:
The following
<comp :foo.sync="bar"></comp>
is expanded into:
<comp :foo="bar" #update:foo="val => bar = val"></comp>
What does this mean in layman's terms? Since it is facilitating two-way binding to update the value being sync'd upon, you cannot use multiple properties (as a boolean expression), nor can you use the return value of a method since you must both read from and write to the same value.
In short, no, you cannot accomplish using sync in the way you are currently utilizing it and I personally disagree with the implementation that the library has chosen since it isn't particularly clear and forces complicated workarounds.
That said, you can use a single property for binding the visibility of :visible.sync and you can trigger that based on your state in the following example:
Template:
<div id="app">
<el-dialog title="Shipping address" :visible.sync="showDialog"
:before-close="beforeCloseHandler"
#close="cond1 = true; cond2 = false;">
</el-dialog>
<button #click="cond1 = true; cond2 = false; showDialog = true;">Open Dialog</button>
</div>
Javascript:
var Main = {
data() {
return {
showDialog: true,
cond1: true,
cond2: true,
};
},
methods: {
beforeCloseHandler: function (done) {
if (this.cond1 && this.cond2) {
console.log('hit close');
done();
} else {
console.log('rejected close');
}
}
}
};
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
We can bind the display to a single property and we can control dismissing with the :before-close handler and of course we can control our show conditions via a click event on a button. It isn't perfect, but it is a potential workaround.
I want to build an application which has two states; pause and active. For example I want to disable all children/owned components' events like onClick, onChange, onKeyDown, .etc.
I had thought to give isActive=false prop through all it's children/owned components and check for the property isActive on event handlers. If isActive property is falsy event handler will do nothing. I can make this mechanism even easier with a simple helper function. But my concern is when I changed the app state to paused, all children components needs to be re-rendered.
Im searching for a way to bypass all event handlers (not custom ones) without re render all components.
UPDATE: I watch rendering rectangles on chrome end it doesn't re render the children. But if there any better reacty way to do this I want to learn it.
One way to solve this is using a simple guard abstraction. It works like this:
var sayHi = guard("enabled", function(){
alert("hi");
});
guard.deactivate("enabled");
sayHi(); // nothing happens
guard.activate("enabled");
sayHi(); // shows the alert
Using this for event handlers is similar:
handleChange: guard("something", function(e){
// if 'something' is deactivated, the input won't change
this.setState({value: e.target.value});
})
// or
return <div onClick={guard("something", this.handleClick)}>click me!</div>
Here's an implementation of guard
var guard = function(key, fn){
return function(){
if (guard.flags[key]) {
return fn.apply(this, arguments);
}
};
};
guard.flags = {};
guard.activate = function(key){ guard.flags[key] = true };
guard.deactivate = function(key){ guard.flags[key] = false };
Set pointerEvents='none' in the styling of the container div. It'll disable all of the children. I know it from React Native, but it seems to work in React.js as well.
Say I have a ReactJS component that represents a "Document" containing "Paragraph"s, each containing "Sentences" which I want rendered into contenteditable spans.
var paragraphData = [{
id: 1,
sentenceData: [
'Paragraph 1, Sentence 1',
'Paragraph 1, Sentence 2'
]
},{
id: 2,
sentenceData: [
'Paragraph 2, Sentence 1',
'Paragraph 2, Sentence 2'
]
}];
var Sentence = React.createClass({
render: function () {
return (<span
contenteditable="true"
onKeyDown={this.props.onKeyDown}>
{this.props.value}
</span>);
}
});
var Paragraph = React.createClass({
render: function () {
var me = this;
var sentences = this.props.sentenceData.map(function (sentenceData) {
return <Sentence value={sentenceData} onKeyDown={me.props.onKeyDown} />;
});
return <div>{sentences}</div>;
});
var Document = React.createClass({
render: function () {
var me = this;
var paragraphs = this.props.paragraphData.map(function (paragraphData) {
return <Paragraph sentences={paragraphData.sentenceData} onKeyDown={me.onKeyDown}>;
});
return <div>{paragraphs}</div>;
},
onKeyDown: function (e) {
// If "Enter" is pressed, I want to split the sentence at
// getSelection().focusOffset, update the current sentence's
// value to currentValue.substr(0, focusOffset) and insert a
// new sentence with value currentValue.substr(focusOffset),
// but how do I know which paragraph/sentences I need to
// inspect/change? Is "e.target" the only thing I have to go by?
}
});
In ReactJS, the idea is for data to flow up and events to flow down. (Which one is referred to as "up" or "down" seems to change all the time, but hopefully you know what I mean.)
My Question:
In my onKeyDown handler, how do I know which models need to have changes applied?
I thought about using .bind() to bind the handler to each model as it was passed up, but it seems a bit... wrong:
Would that be considered tight coupling between model/view?
It would mean binding hundreds or thousands of times (potentially, on a large document), each time creating a new function - which would go against the best-practice "don't create functions in a loop" principle.
I get the feeling I'm heading in the wrong direction - any help much appreciated!
It is perfectly fine to bind the onKeyDown function.
If you implement shouldComponentUpdate in Sentence, when your app is rendered again, the binded functions won't be created again because these components will not render if they have not change.
I don't think the memory overhead of 1000 or 10000 functions has too much impact, and you should not try to optimize this unless you have perf problems. What you don't want is create all these functions everytime on each render, and this is why shouldComponentUpdate is here.
It won't couple more your components that they are already (they are, because they interact together already on a well defined business context). Basically you could create a generic, uncoupled component that will receive any piece of data and on key down on the rendering of that data, will inject that data to a callback. It is generic and does not add coupling, you can control the entire behavior outside of the component.
Notice that binding functions coming from props or in loops to dom event listeners is not something forbidden by React and actually you can find exemples where it is done.
In a loop (that may have many items!): http://facebook.github.io/react/tips/communicate-between-components.html
With binded function props: (can't find it, but I do use it myself...)
Notice that in React, it is forbidden to rebind a function of a component because React bind all functions to its component.
So if a function is in Document and is injected to Sentence, you can't bind it to this in Sentence because it does not make sens and is forbidden by React.
This is the code that does it: https://github.com/facebook/react/blob/95de877dceeaac08755cfe1142a853c467d91d58/src/core/ReactCompositeComponent.js#L1291
if (newThis !== component && newThis !== null) {
monitorCodeUse('react_bind_warning', { component: componentName });
console.warn(
'bind(): React component methods may only be bound to the ' +
'component instance. See ' + componentName
);
}
Note that a newThis !== null has been added. This was actually added to fix this problem when trying to bind props functions.
Now you can write
<Sentence value={sentenceData} onKeyDown={me.props.onKeyDown.bind(null,sentenceData} />
And on document you have a listener:
onKeyDown: function (sentenceData) { }
This is perfectly fine :)
Notice that there is another way this problem can be solved which may be a little bit more efficient:
var Sentence = React.createClass({
onKeyDown: function(e) {
this.props.onKeyDown(this.props.value)
},
render: function () {
return (<span
contenteditable="true"
onKeyDown={this.props.onKeyDown}>
{this.props.value}
</span>);
}
});
Instead of binding a function, you simply create a class that has this unique function that you would normally bind. In this case I think however it creates more coupling as you don't really control the behavior of the callback from outside the Sentence component.
You're concerning yourself with the wrong problem.
Wherever the relevant onKeyDown handler that you're referring to should be working (i.e. for sentences, or paragraphs), you need to handle the data change and then call:
this.setState({stateName: newdata})
This will cause react to re-render the component with the new data.
For a good example, look at facebook's tutorial for a comment widget.
You can see how the form handles the data change by adding new comments, and then calls setState() to re-render with the additional comments. React handles everything else.
In your example, if you change something within a paragraph, the paragraph should call setState with the new sentence data after you've done all the work you need, and then react will handle the rest.