Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Form created with React doesn't update the backing state object

I am trying to automate the login form of Instagram web app:

https://instagram.com/accounts/login/

With the following code (you can run it on Chrome console):

var frm = window.frames[1].document.forms[0];
frm.elements[0].value = 'qacitester';
frm.elements[1].value = 'qatester';
frm.elements[2].click();

Even though the inputs are populated, when I monitor the XHR request, I see this is posted:

username=&password=&intent=

rather than this:

username=qacitester&password=qatester&intent=

And causes web app to not authenticate. Do you have any idea why the input values are not transferred to the backing state (model?) object of React?

like image 431
huseyint Avatar asked Jan 02 '15 13:01

huseyint


People also ask

Why is React not updating on state change?

State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass.

How do you update state with objects in React?

State can hold any kind of JavaScript value, including objects. But you shouldn't change objects that you hold in the React state directly. Instead, when you want to update an object, you need to create a new one (or make a copy of an existing one), and then set the state to use that copy.

Does React useState hook update immediately?

React do not update immediately, although it seems immediate at first glance.

What should getDerivedStateFromProps return to update a state in React?

getDerivedStateFromProps(props, state) is a static method that is called just before render() method in both mounting and updating phase in React. It takes updated props and the current state as arguments. We have to return an object to update state or null to indicate that nothing has changed.


Video Answer


3 Answers

Instagram is using the linkState method from ReactLink:

http://facebook.github.io/react/docs/two-way-binding-helpers.html

The login page links to a bundles/Login.js that contains compressed code like this:

o.createElement("input", {
    className:"lfFieldInput",
    type:"text",
    name:"username",
    maxLength:30,
    autoCapitalize:!1,
    autoCorrect:!1,
    valueLink:this.linkState("username")
}))

If you look at the documentation for ReactLink, this means that the input field value only gets sent back to state when the change event fires on the input fields. However, it's not as simple as just firing the change event on the node in question - I think due to the way that React normalises browser events. The React docs specifically say that onChange in React isn't exactly the same as onChange in the browser for various reasons:

http://facebook.github.io/react/docs/dom-differences.html

Fortunately in your case Instagram are using React with Addons included which gives you access to React TestUtils, so you can do:

var frm = window.frames[1].document.forms[0];
var fReact = window.frames[1].React;
fReact.addons.TestUtils.Simulate.change(frm.elements[0], {target: {value: 'qacitester' }});
fReact.addons.TestUtils.Simulate.change(frm.elements[1], {target: {value: 'qatester' }});
frm.elements[2].click();

More documentation here:

http://facebook.github.io/react/docs/test-utils.html

Notice that since the login form itself is in an iframe, you must use the instance of React from that iframe otherwise the TestUtils won't be able to find the nodes correctly.

This will only work in situations where React Addons are included on the page. The normal non-Addons build of React will require another solution. However, if you're specifically talking about ReactLink then that's an addon anyway, so it's not an issue.

like image 173
Colin Ramsay Avatar answered Oct 01 '22 15:10

Colin Ramsay


Answering my own question, the required event is input event it seems and this code works:

var doc = window.frames[1].document;
var frm = doc.forms[0];
frm.elements[0].value = 'qacitester';
var evt = doc.createEvent('UIEvent');
evt.initUIEvent('input', true, false);
frm.elements[0].dispatchEvent(evt);
frm.elements[1].value = 'qatester';
frm.elements[1].dispatchEvent(evt);
frm.elements[2].click();
like image 35
huseyint Avatar answered Oct 01 '22 15:10

huseyint


The login form is actually inside an iFrame, which makes things a wee bit more complicated. I was able to successfully submit a login running the following code from the chrome console. I'm using React's TestUtils, but you may be fine with AutoType:

// run in Chrome console
var frame = document.querySelector('iframe[src*="login"]');
var s = document.createElement('script');
var username = frame.contentDocument.querySelector('input[name="username"]');
var password = frame.contentDocument.querySelector('input[name="password"]');
s.src = "https://fb.me/react-with-addons-0.12.0.js";
frame.contentDocument.body.appendChild(s);

// wait for the script request to resolve and use:
change = frame.contentWindow.React.addons.TestUtils.Simulate.change;
change(username, {target: {value: 'My Great Username'}});
change(password, {target: {value: 'securepassword'}});

Background

React uses its own virtual event system, and typically uses controlled components, which essentially means the input value is set by react and trickles down to DOM element.

With that in mind, I believe the issue you are experiencing is because react looks for a key event on the input, and uses the value of the input at the time of the event to set the value of the DOM element. Since you are just setting the value property of the DOM element, you are breaking out of the controlled component cycle.

I did a quick spike attempting to dispatch a KeyboardEvent on the input, but it was, alas, unsuccessful. I'll update my answer if indeed I do succeed.

Non satisfactory workaround:

Use casper/phantomjs to automate form filling. This means you don't have to worry about dispatching events yourself and you can just let the "browser" do it for you. It's also better than working in the console manually.

like image 36
Nick Tomlin Avatar answered Oct 01 '22 13:10

Nick Tomlin