How to simulate text input onchange when testing - javascript

I have the following code where I am only enabling a button if relevant data exists.
I am trying to test this by faking a change in the input field so that the relevant data is available.
But my simulation does not seem to work thus the data remains empty which in turn means my button remains disabled. Can I please get some help on what I am doing wrong please? Thank you.
Note that following code works where events fires as expected and updates. Just issue in writing test.
const [code1, setCode1] = useState('');
const cannotPress = () => !(code1);
const handleFieldChange = (event, updater) => {
updater(event.target.value);
};
// in test, trying to simulate a value entered in this input so that the event fires and
// updates code1's value thus making cannotPress=false. This would enable the button and pass test.
<input id="code1" value={code1} onChange={event => handleFieldChange(event, setCode1)} />
<button id='btn type="submit" disabled={cannotPress()} onClick={() => doSomething()}>Submit</button>
Test
describe('tests', () => {
const wrapper = shallow(<MyApp info={info} />);
it('simple test', () => {
const btn = wrapper.find('button#btn'); // able to find button
const input = wrapper.find('input#code1'); // able to find input (tested by placing in default values)
console.log(input.props().value); // prints nothing as expected
input.instance().props.onChange(({ target: { value: 'some fake data' } })); // expecting the event to trigger and change value for code1.
console.log(input.props().value); // ISSUE: expected to print 'some fake data' but still empty.
expect(btn.prop('disabled')).toBe(false); // fails cos input has no value thus disabled is still true
});
});

I think you will need to simulate the event (using enzyme) rather then trying to manipulate the onChange() property directly.
Seems like a similar issue to:
https://stackoverflow.com/a/37452043/10610784
Try the suggested solution
input.simulate('change', { target: { value: 'Hello' } })

Related

Function firing twice on click in React component

TL/DR: My simple toggle function fires twice when button is clicked.
I'm using useEffect in a React (w/ Next.js) component so that I can target the :root <html> tag for which I need the class to be toggled. The code is the following:
useEffect(() => {
const toggleMode = () => {
const root = document.documentElement;
root.classList.toggle("dark");
console.log("click");
};
const toggleBtn = document.querySelector("#toggle-btn");
toggleBtn.addEventListener("click", toggleMode);
I have the necessary imports, the code is placed inside the main component function before the return, and there's no errors in the console at all.
The only issue is that the function is fired twice every time the button is clicked and I cannot find any reason why or solutions online.
Would really appreciate your help and please let me know if I'm missing any information.
Cheers!
Your problem is coming from registering the event listener in a non-react way.
By registering the listener via
const toggleBtn = document.querySelector("#toggle-btn");
toggleBtn.addEventListener("click", toggleMode);
you are setting up a new listener each time the function is run, even if the DOM is not updated. This could result in multiple listeners being registered and firing simultaneously.
You need to add your listener the react way.
function Component ( props ){
const [ isFirst, setIsFirst ] = useState( true );
const [ toggle, setToggle ] = useState( false );
useEffect(() => {
if( isFirst ) {
setIsFirst( false );
return;
}
document.documentElement.classList.toggle("dark");
}, [ toggle ] );
return <div>
<button id="toggle-btn" onClick = { e => setToggle( !toggle ) } />
</div>
}
I resolved a similar problem in this post: Why does my NextJS Code run multiple times, and how can this be avoided?
Your code should only run once if you disable react strict mode.

Reusable Copy To Clipboard Function

I created a reusable copy function that works on any input field with a specific class, but the only way I've been able to pass the value in the input field is by tacking an onclick event directly onto the input field onclick="copyIt(this)", which is obviously not ideal.
When I try to pass the value of the text field to the function is when I get an error: copyIt(e.target.value); I should be able to figure this one out, but I'm having no luck. There's obviously/apparently something that I don't understand about how the value needs to be formatted to make this work, but that's the holdup. Any insight would be much appreciated!
const copyIt = function (text) {
text.select();
console.log("Copied to clipboard");
document.execCommand("copy");
}
document.querySelectorAll(".copier").forEach((copied) => {
copied.addEventListener("click", (e) => {
console.log(e.target.value, "event listener attached");
copyIt(e.target.value);
});
});
https://codepen.io/NoahBoddy/pen/MWJmgQz
Your issue is that text.select() is expecting to select a NodeElement and you're passing in the text itself. You cannot do that so I have adjusted your code to work.
Kudos on forEach across the NodeElements... I have for the longest time done Array.from(NodeList) and this approach brought some alternative insight. Thanks.
const copyIt = function (nodeElement) {
nodeElement.select();
document.execCommand("copy");
}
document.querySelectorAll(".copier").forEach((nodeElement) => {
nodeElement.addEventListener("click", () => {
copyIt(nodeElement)
});
});

(React.js + Hooks) How to reset input field after button click?

What I want to do is have an input that a user can type into. Upon clicking a button, take that input and process it and perform some backend operations with the database connected to the input. I'm trying to accomplish this with a state hook: onBlur, save input into state. On button click, do two things: 1) take the state variable and pass it to the backend resolvers, and 2) clear the input field so it's empty, and only the placeholder text exists.
I have this code:
const [inputVal, setInputVal] = useState("");
updateInput = (e) => {
const val = e.target.value;
setInputVal(val);
}
sendData = async () => {
//handle async backend processing with inputVal
setInputVal("");
//rerender
}
<Button className="input-button" onClick={sendData}>Send Data</Button>
<input className="input-field" placeHolder="Input name." defaultValue={inputVal} onBlur=(updateInput)></input>
However, I have two issues.
On button click, I want to first updateInput, and then handle the button click, so it always uses the new value. However, it seems as if there are some issues with that, possibly due to the asynchronous nature of sendData?
While inputVal may be an empty String, for some reason, the value in the input box doesn't reset to nothing, it stays exactly as it was (although the internal data would still have inputVal = 0). And onBlur it reupdates the inputVal.
for a controlled state input the most common approach is to use onChange rather than onBlur. This would also avoid your conflicts with blur and click events. Also you would pass inputVal to value input's property.
const [inputVal, setInputVal] = useState("");
const updateInput = (e) => {
const val = e.target.value;
setInputVal(val);
}
const sendData = async () => {
//handle async backend processing with inputVal
setInputVal("");
//rerender
}
return (
<>
<button className="input-button" onClick={sendData}>Send Data</button>
<input className="input-field" onChange={updateInput} placeHolder="Input name." value={inputVal}/>
</>
);
First, change defaultValue to value={inputVal} since it's a controlled input.
Secondly, please elaborate on what issues you have in 1.

Simulate click event on react element

The bounty expires in 7 days. Answers to this question are eligible for a +50 reputation bounty.
ajaykools wants to reward an existing answer:
Worth bounty, only way simulate clicks on dynamic elements like svg, g, circle, etc which are generated on page load.
I'm trying to simulate a .click() event on a React element but I can't figure out why it is not working (It's not reacting when I'm firing the event).
I would like to post a Facebook comment using only JavaScript but I'm stuck at the first step (do a .click() on div[class="UFIInputContainer"] element).
My code is:
document.querySelector('div[class="UFIInputContainer"]').click();
And here's the URL where I'm trying to do it: https://www.facebook.com/plugins/feedback.php...
P.S. I'm not experienced with React and I don't know really if this is technically possible. It's possible?
EDIT: I'm trying to do this from Chrome DevTools Console.
React tracks the mousedown and mouseup events for detecting mouse clicks, instead of the click event like most everything else. So instead of calling the click method directly or dispatching the click event, you have to dispatch the down and up events. For good measure I'm also sending the click event but I think that's unnecessary for React:
const mouseClickEvents = ['mousedown', 'click', 'mouseup'];
function simulateMouseClick(element){
mouseClickEvents.forEach(mouseEventType =>
element.dispatchEvent(
new MouseEvent(mouseEventType, {
view: window,
bubbles: true,
cancelable: true,
buttons: 1
})
)
);
}
var element = document.querySelector('div[class="UFIInputContainer"]');
simulateMouseClick(element);
This answer was inspired by Selenium Webdriver code.
With react 16.8 I would do it like this :
const Example = () => {
const inputRef = React.useRef(null)
return (
<div ref={inputRef} onClick={()=> console.log('clicked')}>
hello
</div>
)
}
And simply call
inputRef.current.click()
Use refs to get the element in the callback function and trigger a click using click() function.
class Example extends React.Component{
simulateClick(e) {
e.click()
}
render(){
return <div className="UFIInputContainer"
ref={this.simulateClick} onClick={()=> console.log('clicked')}>
hello
</div>
}
}
ReactDOM.render(<Example/>, document.getElementById('app'))
<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>
If you don't define a class in your component, and instead you only declare:
function App() { ... }
In this case you only need to set up the useRef hook and use it to point/refer to any html element and then use the reference to trigger regular dom-events.
import React, { useRef } from 'react';
function App() {
const inputNameRef = useRef()
const buttonNameRef = useRef()
function handleKeyDown(event) {
// This function runs when typing within the input text,
// but will advance as desired only when Enter is pressed
if (event.key === 'Enter') {
// Here's exactly how you reference the button and trigger click() event,
// using ref "buttonNameRef", even manipulate innerHTML attribute
// (see the use of "current" property)
buttonNameRef.current.click()
buttonNameRef.current.innerHTML = ">>> I was forced to click!!"
}
}
function handleButtonClick() {
console.log('button click event triggered')
}
return (
<div>
<input ref={inputNameRef} type="text" onKeyDown={handleKeyDown} autoFocus />
<button ref={buttonNameRef} onClick={handleButtonClick}>
Click me</button>
</div>
)
}
export default App;
A slight adjustment to #carlin.scott's great answer which simulates a mousedown, mouseup and click, just as happens during a real mouse click (otherwise React doesn't detect it).
This answer adds a slight pause between the mousedown and mouseup events for extra realism, and puts the events in the correct order (click fires last). The pause makes it asynchronous, which may be undesirable (hence why I didn't just suggest an edit to #carlin.scott's answer).
async function simulateMouseClick(el) {
let opts = {view: window, bubbles: true, cancelable: true, buttons: 1};
el.dispatchEvent(new MouseEvent("mousedown", opts));
await new Promise(r => setTimeout(r, 50));
el.dispatchEvent(new MouseEvent("mouseup", opts));
el.dispatchEvent(new MouseEvent("click", opts));
}
Usage example:
let btn = document.querySelector("div[aria-label=start]");
await simulateMouseClick(btn);
console.log("The button has been clicked.");
Note that it may require page focus to work, so executing in console might not work unless you open the Rendering tab of Chrome DevTools and check the box to "emulate page focus while DevTools is open".
Inspired from previous solution and using some javascript code injection it is also possibile to first inject React into the page, and then to fire a click event on that page elements.
let injc=(src,cbk) => { let script = document.createElement('script');script.src = src;document.getElementsByTagName('head')[0].appendChild(script);script.onload=()=>cbk() }
injc("https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js",() => injc("https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js",() => {
class ReactInjected extends React.Component{
simulateClick(e) {
e.click()
}
render(){
return <div className="UFIInputContainer"
ref={this.simulateClick} onClick={()=> console.log('click injection')}>
hello
</div>
}
}
ReactDOM.render(<ReactInjected/>, document.getElementById('app'))
} ))
<div id="app"></div>
Kind of a dirty hack, but this one works well for me whereas previous suggestions from this post have failed. You'd have to find the element that has the onClick defined on it in the source code (I had to run the website on mobile mode for that). That element would have a __reactEventHandlerXXXXXXX prop allowing you to access the react events.
let elem = document.querySelector('YOUR SELECTOR');
//Grab mouseEvent by firing "click" which wouldn't work, but will give the event
let event;
likeBtn.onclick = e => {
event = Object.assign({}, e);
event.isTrusted = true; //This is key - React will terminate the event if !isTrusted
};
elem.click();
setTimeout(() => {
for (key in elem) {
if (key.startsWith("__reactEventHandlers")) {
elem[key].onClick(event);
}
}
}, 1000);
Using React useRef Hooks you can trigger a click event on any button like this:
export default const () => {
// Defining the ref constant variable
const inputRef = React.useRef(null);
// example use
const keyboardEvent = () => {
inputRef.current.handleClick(); //Trigger click
}
// registering the ref
return (
<div ref={inputRef} onClick={()=> console.log('clicked')}>
hello
</div>
)
}
This answer was inspired by carlin.scott code.
However, it works only with focusin event in my case.
const element = document.querySelector('element')
const events = ['mousedown', 'focusin']
events.forEach(eventType =>
element.dispatchEvent(
new MouseEvent(eventType, { bubbles: true })
)
)

SugarCRM - Trigger Javascript function when relate field is populated

I want to trigger some JavaScript when a relate field is populated
I was hoping to find a way of Jquery monitoring the hidden field contact_id and when it changes calling the Javascript function but I'm not sure this is possible
At the moment I changed editviewdefs so that the event triggers onblur
Code:
array (
'name' => 'contact_name',
'label' => 'LBL_CONTACT_NAME',
'customLabel' => '{$MOD.LBL_CONTACT_NAME}: <span class="required">*</span>',
'displayParams' =>
array (
'initial_filter' => '&account_name_advanced={$fields.account_name.value}&status_advanced=Live',
'field' =>
array (
'onchange' => 'getOpenCases()',
),
'javascript' =>
array (
'btn' => ' onblur="getOpenCases()" ',
'btn_clear' => ' onblur="getOpenCases()" ',
),
),
'tabindex' => '11',
),
But this means the user has to click away from the field before the event is triggered
I want it to happen as soon as contact_id is populated
Is this possible? If so how do I achieve it?
I have a rather convoluted method to check for a change in the hidden input.
I trigger a setInterval function when someone either clicks the button or starts typing into the text field. I then use jQuery data() to check for any change in the hidden input. When I change is noticed I run my custom js and then I clear the interval.
All the code is in an external js file.
// watch for a change in account ID
$('#btn_account_name').click(function(){
checkAccountIdChange();
});
$('#account_name').blur(function(){
checkAccountIdChange();
});
function checkAccountIdChange() {
var theInterval = setInterval(function(){
var oldId = $('#account_id').data('oldId');
var newId = $('#account_id').val();
if (oldId != newId && newId != '') {
getAccountData(newId, theInterval);
$('#account_id').data('oldId', newId);
}
}, 500);
}
function getAccountData(id, theInterval) {
// do your custom js
clearInterval(theInterval);
}
This isn't particularly clean or glamorous but does work. I'd be keen to see how others get around this.

Categories

Resources