How to programmatically fill input elements built with React? - javascript

I'm tasked with crawling website built with React. I'm trying to fill in input fields and submitting the form using javascript injects to the page (either selenium or webview in mobile). This works like a charm on every other site + technology but React seems to be a real pain.
so here is a sample code
var email = document.getElementById( 'email' );
email.value = 'example#mail.com';
I the value changes on the DOM input element, but the React does not trigger the change event.
I've been trying plethora of different ways to get the React to update the state.
var event = new Event('change', { bubbles: true });
email.dispatchEvent( event );
no avail
var event = new Event('input', { bubbles: true });
email.dispatchEvent( event );
not working
email.onChange( event );
not working
I cannot believe interacting with React has been made so difficult. I would greatly appreciate any help.
Thank you

This accepted solution appears not to work in React > 15.6 (including React 16) as a result of changes to de-dupe input and change events.
You can see the React discussion here: https://github.com/facebook/react/issues/10135
And the suggested workaround here:
https://github.com/facebook/react/issues/10135#issuecomment-314441175
Reproduced here for convenience:
Instead of
input.value = 'foo';
input.dispatchEvent(new Event('input', {bubbles: true}));
You would use
function 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);
}
}
and then
setNativeValue(input, 'foo');
input.dispatchEvent(new Event('input', { bubbles: true }));

React is listening for the input event of text fields.
You can change the value and manually trigger an input event, and react's onChange handler will trigger:
class Form extends React.Component {
constructor(props) {
super(props)
this.state = {value: ''}
}
handleChange(e) {
this.setState({value: e.target.value})
console.log('State updated to ', e.target.value);
}
render() {
return (
<div>
<input
id='textfield'
value={this.state.value}
onChange={this.handleChange.bind(this)}
/>
<p>{this.state.value}</p>
</div>
)
}
}
ReactDOM.render(
<Form />,
document.getElementById('app')
)
document.getElementById('textfield').value = 'foo'
const event = new Event('input', { bubbles: true })
document.getElementById('textfield').dispatchEvent(event)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'></div>

Here is the cleanest possible solution for inputs, selects, checkboxes, etc. (works not only for react inputs)
/**
* See [Modify React Component's State using jQuery/Plain Javascript from Chrome Extension](https://stackoverflow.com/q/41166005)
* See https://github.com/facebook/react/issues/11488#issuecomment-347775628
* See [How to programmatically fill input elements built with React?](https://stackoverflow.com/q/40894637)
* See https://github.com/facebook/react/issues/10135#issuecomment-401496776
*
* #param {HTMLInputElement | HTMLSelectElement} el
* #param {string} value
*/
function setNativeValue(el, value) {
const previousValue = el.value;
if (el.type === 'checkbox' || el.type === 'radio') {
if ((!!value && !el.checked) || (!!!value && el.checked)) {
el.click();
}
} else el.value = value;
const tracker = el._valueTracker;
if (tracker) {
tracker.setValue(previousValue);
}
// 'change' instead of 'input', see https://github.com/facebook/react/issues/11488#issuecomment-381590324
el.dispatchEvent(new Event('change', { bubbles: true }));
}
Usage:
setNativeValue(document.getElementById('name'), 'Your name');
document.getElementById('radio').click(); // or setNativeValue(document.getElementById('radio'), true)
document.getElementById('checkbox').click(); // or setNativeValue(document.getElementById('checkbox'), true)

I noticed the input element had some property with a name along the lines of __reactEventHandlers$..., which had some functions including an onChange.
This worked for finding that function and triggering it
let getReactEventHandlers = (element) => {
// the name of the attribute changes, so we find it using a match.
// It's something like `element.__reactEventHandlers$...`
let reactEventHandlersName = Object.keys(element)
.filter(key => key.match('reactEventHandler'));
return element[reactEventHandlersName];
}
let triggerReactOnChangeEvent = (element) => {
let ev = new Event('change');
// workaround to set the event target, because `ev.target = element` doesn't work
Object.defineProperty(ev, 'target', {writable: false, value: element});
getReactEventHandlers(element).onChange(ev);
}
input.value = "some value";
triggerReactOnChangeEvent(input);

Without element ids:
export default function SomeComponent() {
const inputRef = useRef();
const [address, setAddress] = useState("");
const onAddressChange = (e) => {
setAddress(e.target.value);
}
const setAddressProgrammatically = (newValue) => {
const event = new Event('change', { bubbles: true });
const input = inputRef.current;
if (input) {
setAddress(newValue);
input.value = newValue;
input.dispatchEvent(event);
}
}
return (
...
<input ref={inputRef} type="text" value={address} onChange={onAddressChange}/>
...
);
}

React 17 works with fibers:
function findReact(dom) {
let key = Object.keys(dom).find(key => key.startsWith("__reactFiber$"));
let internalInstance = dom[key];
if (internalInstance == null) return "internalInstance is null: " + key;
if (internalInstance.return) { // react 16+
return internalInstance._debugOwner
? internalInstance._debugOwner.stateNode
: internalInstance.return.stateNode;
} else { // react <16
return internalInstance._currentElement._owner._instance;
}
}
then:
findReact(domElement).onChangeWrapper("New value");
the domElement in this is the tr with the data-param-name of the field you are trying to change:
var domElement = ?.querySelectorAll('tr[data-param-name="<my field name>"]')

Related

HTML input not updating on paste

I'm trying to capture the value of whatever is pasted into a text input, but it's not showing up in the text input for some reason
import React from "react";
export default function App() {
handleOnPaste = e => {
e.stopPropagation();
e.preventDefault();
const clipboardData = e.clipboardData || window.clipboardData;
const pastedData = clipboardData.getData("Text");
console.log(pastedData);
};
return (
<div className="App">
<input
type="text"
onPaste={e => this.handleOnPaste(e)}
/>
</div>
);
}
It is working, just declare the function in a proper way like,
const handleOnPaste = e => {
e.stopPropagation();
e.preventDefault();
const clipboardData = e.clipboardData || window.clipboardData;
const pastedData = clipboardData.getData("Text");
console.log(pastedData);
};
I. declare your function with const
const handleOnPaste = e => {
// Implementation
}
II. pass your function without this because this is a function component, not a class component.
return (
<div className="App">
<input
type="text"
onPaste={e => handleOnPaste(e)}
/>
</div>
);
You can also pass your function like:
onPaste={handleOnPaste}
As mentioned in other answers, you are mixing class based component's concepts in functional component. So use const to define handlers and don't use this.
See Working demo.
FYI - You can also use navigator.clipboard
Code snippet
export default function App() {
const handleOnPaste = e => {
e.stopPropagation();
// First way
const clipboardData = e.clipboardData || window.clipboardData;
const pastedData = clipboardData.getData("Text");
console.log(pastedData);
// Another way -- navigator.clipboard
navigator.clipboard.readText().then(copiedText => console.log('copiedText', copiedText));
};
return (
<div className="App">
<input
type="text"
onPaste={handleOnPaste}
/>
</div>
);
}
Your e.preventDefault() is what's stopping the input from updating. It's unnecessary if you're happy for the field to update

Creating a custom input field with Web Components without outside accessability

I want to create a custom input with the Shadow DOM
class TextBox extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({ mode: 'open' });
let textbox = document.createElement("input");
shadow.appendChild(textbox);
textbox.addEventListener("change", validate);
function validate(event) {
console.log("input can be validated");
}
}
get value() {
console.log("get");
let textbox = this.shadowRoot.querySelector("input");
return textbox.value;
}
set value(newValue) {
console.log("set");
let textbox = this.shadowRoot.querySelector("input");
textbox.value = newValue;
}
}
customElements.define('test-textbox', TextBox);
It should be possible to change the value of the displayed textbox via js. If I change the .value property of the textbox the setter of value don't get called? Am i missing something?
Later on I want to include the textbox via a template in my solution and be able to set the value of the textbox via textbox.value ="Peter"
The internal <input> field dispatches the input event every time its value changes. This event can be captured either in your component or by the code that uses your component.
The change event only happens in certain situations so the input event is a better choice.
The code below shows how the component listens for the input event and so does the external code.
function validate(event) {
console.log("input can be validated");
}
class TextBox extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
input {
width: 300px;
}
</style>
`;
const textbox = document.createElement("input");
shadow.appendChild(textbox);
textbox.addEventListener("input", validate);
textbox.focus();
}
get value() {
console.log("get");
let textbox = this.shadowRoot.querySelector("input");
return textbox.value;
}
set value(newValue) {
console.log("set");
let textbox = this.shadowRoot.querySelector("input");
textbox.value = newValue;
}
}
customElements.define('test-textbox', TextBox);
const el = document.querySelector('test-textbox');
el.addEventListener("input", (evt) => {
console.log('input event from the outside.');
});
<test-textbox></test-textbox>

Mobile browser issue with textarea resize

I am working in React.js and have textarea elements that dynamically expand and contract based on the size of the user's input. The intended functionality is as follows:
This works correctly in a desktop context. However, on any mobile or tablet in a modern browser (tested Safari, Chrome and Firefox) the textarea element only expands, it does not contract when content is deleted.
At first I thought it might have something to do with the onChange handler I was employing, however, the same issue remains when swapping it out with an onInput handler. So I believe the issue resides in the resize() method.
Does anyone have an idea of why I'm experiencing this issue?
I have created a style-free fiddle to share with you the basic functionality. Interestingly, the bug doesn't occur in the JSFiddle simulator on a mobile device, but if you take the same code and put it in another react environment, the bug occurs on a mobile device in modern browsers.
class Application extends React.Component {
render() {
return (
<div>
<Textarea value="This is a test" maxLength={500}/>
</div>
);
}
}
class Textarea extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.value
? this.props.maxLength && this.props.maxLength > 0
? this.props.value.length < this.props.maxLength
? this.props.value
: this.props.value.substring(0, this.props.maxLength)
: this.props.value
: '',
remaining: this.props.value
? this.props.value.length < this.props.maxLength
? this.props.maxLength - this.props.value.length
: 0
: this.props.maxLength
};
this.textAreaRef = React.createRef();
this.textAreaHeight = null;
this.textAreaoffSetHeight = null;
}
componentDidMount() {
window.addEventListener('resize', this.resize);
this.resize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
}
handleChange = event => {
const target = event.target || event.srcElement;
this.setState({
value: target.value,
remaining: target.value
? target.value.length < this.props.maxLength
? this.props.maxLength - target.value.length
: 0
: this.props.maxLength
});
this.resize();
};
resize = () => {
const node = this.textAreaRef.current;
node.style.height = '';
const style = window.getComputedStyle(node, null);
let heightOffset =
parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
this.textAreaoffSetHeight = node.offsetTop;
this.textAreaHeight = node.scrollHeight + heightOffset;
node.style.height = this.textAreaHeight + 'px';
this.resizeBorder();
this.resizeParentNode();
};
resizeBorder = () => {
const textAreaSize = this.textAreaHeight;
const node = this.textAreaRef.current;
const borderNode = node.parentNode.querySelector(
'.textarea__border'
);
if (borderNode !== null) {
borderNode.style.top =
this.textAreaoffSetHeight + textAreaSize - 1 + 'px';
}
};
resizeParentNode = () => {
const node = this.textAreaRef.current;
const parentNode = node.parentNode;
if (parentNode !== null) {
parentNode.style.height = this.textAreaHeight + 40 + 'px';
}
};
render() {
return (
<div className={'textarea'}>
<textarea
ref={this.textAreaRef}
className={
!this.state.value
? 'textarea__input'
: 'textarea__input active'
}
value={this.state.value}
maxLength={
this.props.maxLength && this.props.maxLength > 0 ? this.props.maxLength : null
}
onChange={this.handleChange}
/>
<div className={'textarea__message'}>
{this.state.remaining <= 0
? `You've reached ${this.props.maxLength} characters`
: `${this.state.remaining} characters remaining`}
</div>
</div>
);
}
}
ReactDOM.render(
<Application />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<main id="app">
<!-- This element's contents will be replaced with your component. -->
</main>
The issue is that you're modifying the DOM directly (or trying to) instead of modifying state and allowing React to flow properly. You modify the DOM elements properties in resize() then any input change will immediate call handleChange(e) and re-flow your DOM overwriting the modifications.
NEVER MIX REACT WITH DOM TOUCHING!!!
Change your resize function to behave like your handleChange(e) function and set variables within the state which control those properties during the render() of the mark-up.

Change the cursor position in a textarea with React

I have a textarea in React that I want to turn into a "notepad". Which means I want the "tab" key to indent instead of unfocus. I looked at this answer, but I can't get it to work with React. Here is my code:
handleKeyDown(event) {
if (event.keyCode === 9) { // tab was pressed
event.preventDefault();
var val = this.state.scriptString,
start = event.target.selectionStart,
end = event.target.selectionEnd;
this.setState({"scriptString": val.substring(0, start) + '\t' + val.substring(end)});
// This line doesn't work. The caret position is always at the end of the line
this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1;
}
}
onScriptChange(event) {
this.setState({scriptString: event.target.value});
}
render() {
return (
<textarea rows="30" cols="100"
ref="input"
onKeyDown={this.handleKeyDown.bind(this)}
onChange={this.onScriptChange.bind(this)}
value={this.state.scriptString}/>
)
}
When I run this code, even if I press the "tab" key in the middle of the string, my cursor always appears at the end of the string instead. Anyone knows how to correctly set the cursor position?
You have to change the cursor position after the state has been updated(setState() does not immediately mutate this.state)
In order to do that, you have to wrap this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1; in a function and pass it as the second argument to setState (callback).
handleKeyDown(event) {
if (event.keyCode === 9) { // tab was pressed
event.preventDefault();
var val = this.state.scriptString,
start = event.target.selectionStart,
end = event.target.selectionEnd;
this.setState(
{
"scriptString": val.substring(0, start) + '\t' + val.substring(end)
},
() => {
this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1
});
}
}
jsfiddle
For anyone looking for a quick React Hooks (16.8+) cursor position example:
import React, { useRef } from 'react';
export default () => {
const textareaRef = useRef();
const cursorPosition = 0;
return <textarea
ref={textareaRef}
onBlur={() => textareaRef.current.setSelectionRange(cursorPosition, cursorPosition)}
/>
}
In this example, setSelectionRange is used to set the cursor position to the value of cursorPosition when the input is no longer focused.
For more information about useRef, you can refer to React's official doc's Hook Part.
Here's a solution in a hooks-style architecture. My recommendation is to change the textarea value and selectionStart immediately on tab insertion.
import React, { useRef } from "react"
const CodeTextArea = ({ onChange, value, error }) => {
const textArea = useRef()
return (
<textarea
ref={textArea}
onKeyDown={e => {
if (e.key === "Tab") {
e.preventDefault()
const { selectionStart, selectionEnd } = e.target
const newValue =
value.substring(0, selectionStart) +
" " +
value.substring(selectionEnd)
onChange(newValue)
if (textArea.current) {
textArea.current.value = newValue
textArea.current.selectionStart = textArea.current.selectionEnd =
selectionStart + 2
}
}
}}
onChange={e => onChange(e.target.value)}
value={value}
/>
)
}
In React 15 best option is something like that:
class CursorForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
handleChange = event => {
// Custom set cursor on zero text position in input text field
event.target.selectionStart = 0
event.target.selectionEnd = 0
this.setState({value: event.target.value})
}
render () {
return (
<form>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</form>
)
}
}
You can get full control of cursor position by event.target.selectionStart and event.target.selectionEnd values without any access to real DOM tree.

Two way data binding using div

I am working with reactjs with redux.
i have created an editable div instead of input textfield but unable to receive the value.
So, in input textfield. There is a event named onChange which let you access the value type in input field.
For example -
handlechange(e){
console.log(e.target.value); //get the value of textbox then i further save it in state
}
render()
{
return (
<input
onChange={this.handleChange.bind(this)}
value={this.state.msgText}
</>
)}
But I am using the editable div for same like this
<div
role="textbox"
ref={function(e){if(e != null) e.contentEditable=true;}}
title="Type the text"
//onChange={this.handleChange.bind(this)}
onKeyUp={this.handleKeyUp.bind(this)}
>
{this.state.msgText}
</div>
So , in handleKeyUp function
handleKeyUp(e){
var t = this;
console.log('test');
console.log(e);
console.log(e.target.value); // I have read ,i can only receive the keycode here,cannot receive value
console.log(this.state.msgText); //So i should receive the value here but i am not
if(e.which == 13) {
e.preventDefault();
//reset the state for clear the div
t.setState({
msgText: ""
});
}
}
Once way of doing this is adding id on div like this -
<div
id={"fc-"+ this.props.thread.uid + "-textbox"}
role="textbox"
className="ifc-chat-window-textbox-input"
ref={function(e){if(e != null) e.contentEditable=true;}}
title="Type the text"
//onChange={this.handleChange.bind(this)}
//onKeyUp={this.handleKeyUp.bind(this)}
>
{this.state.msgText}
</div>
Then in componentDidMount function
componentDidMount(){
var t = this;
var node = document.getElementById("fc-"+ this.props.thread.uid + "-textbox");
var value = node.textContent; // I receive the value here
node.onkeypress = function(event){
t.setState({
msgText: node.textContent
}); });
if(event.which == 13) {
event.preventDefault();
t.sendMsgObject(value , t.props.thread.uid, t.props.thread.name, t.props.thread.color, t.props.actions, t.props.user);
//reset the state for clear input field
t.setState({
msgText: ""
});
}
All this works fine, but i dont think that is how things works in react. I am looking do to this without using id to div.
have u tried something like that ?
var handleChange = function(event){
this.setState({html: event.target.value});
}.bind(this);
return (<ContentEditable html={this.state.html} onChange={handleChange} />);
ContentEditable class
var ContentEditable = React.createClass({
render: function(){
return <div
onInput={this.emitChange}
onBlur={this.emitChange}
contentEditable
dangerouslySetInnerHTML={{__html: this.props.html}}></div>;
},
shouldComponentUpdate: function(nextProps){
return nextProps.html !== this.getDOMNode().innerHTML;
},
emitChange: function(){
var html = this.getDOMNode().innerHTML;
if (this.props.onChange && html !== this.lastHtml) {
this.props.onChange({
target: {
value: html
}
});
}
this.lastHtml = html;
}
});

Categories

Resources