simulate child component element click in parent component test file - javascript

I couldn't find a way to simulate the click of an element exists in child component from parent test file.
let card;
const displayCardSection = (cardName) => {
card = cardName;
};
describe('Parent component', () => {
it('simulate click event on child link', () => {
const component = mount(<Parent
/>);
const res = component.find(<Child
onLinkClick={displayCardSection(CM_CARD)}
/>);
expect(res).toBeDefined();
res.exists('#cm_link').simulate('click');
expect(card).toBe(CM_CARD);
})
})
This is the test file of a parent component.
Parent.jsx:
class Parent extends Component {
handleClick = () => {
console.log('link clicked!');
};
render() {
const linkText = 'Link'
return (
<Child
linkText={linkText}
onLinkClick={handleClick}
/>
);
}
}
Child.jsx:
function Child({linkText, onLinkClick}) {
return (
<Link id="cm_link" onClick={onLinkClick}>{linkText}</Link>
);
}
These are the components that I have.
I expect to simulate the link click of a child component from parent and the output should be the card displayed is 'CM_CARD'. I can write an assert statement but I'm not to simulate a click event of an element which exists in child but event handled in parent itself.

First I would suggest using shallow and the code will be the following:
const component = shallow(<Parent />);
// The following code should trigger the handler and then you will see the console output
component.find("Child").prop("onLinkClick")();
I hope this is useful and will solve your problem, Have a good day!

Related

Moving logic to child component from a parent

I am in situation where a function located in parent component handles onOK for Modal Component, the reason i have located it in parent is due to another local function that gets called once onOk button gets clicked
I would like to move it to child since Modal should be responsible for onOK logic , i can make two components hold visible state or create a stores but the question is how to go about it if another function is involved and that function is glued in parent
Parent
handleModalOk = () => {
this.onRadioButtonChange(2)
this.setState({visible: false,});
};
Child
<Modal
title={t('preferenceConfirmTitle')}
visible={this.props.visible}
onOk={this.props.onOk}
Thank you
You can pass the function located on the parent to the child. Here's an example:
// Parent.js
import React, {useState} from "react";
import Child from "./Child";
const Parent = () => {
const [myState, setMyState] = useState();
const funcOnlyInParent = () => {
console.log("I'm only in the parent");
}
const doSomething = (argsFromChild) => {
setMyState(argsFromChild);
funcOnlyInParent();
}
return (
<div>
<Child handleDoSomething={doStomething} />
</div>
)
}
// Child.js
import React, {useState} from "react";
const Child = (props) => {
return (
<div>
<button onClick={() => props.handleDoSomething("my arguments")}>Click</button>
</div>
)
}
The Child component will trigger the doSomething function in the parent and pass in any arguments that it needs to. Because the function is defined in the Parent component, then it can also perform actions on data that only the parent has access to - like setting the parent's state.
const Parent = () => {
const function1 = () => {
// your local code
}
return (
<Modal parentFunction={function1} />
)
}
const Modal = ({parentFunction}) => {
const onOK = () => {
// your local code
parentFunction()
}
return (
<button onClick={onOK}>Click</button>
)
}

React 16: Call children's function from parent when using hooks and functional component

I need to call children's function in their parent component. How should I do it? Previous in React 15, I can use refs to call children's function. But have no idea how to do it with hooks and functional component.
function Child(props) {
function validate() {
// to validate this component
}
return (
<>Some code here</>
)
}
function Parent(props) {
const refs = useRef([]);
const someData = [1,2,3,4,5];
function validateChildren() {
refs.current.forEach(child => {
child.current.validate(); // throw error!! can not get validate function
});
}
return (
<>
<button onClick={validateChildren}>Validate</button>
{
someData.map((data, index) => {
return <Child ref={ins => refs.current[index] = ins} />
})
}
</>
)
}
My actual requirement is:
1. There are multiple tabs which can be added, delete base on user's behaviour
2. Click a button in the parent of tabs to trigger validate
3. All the tabs need to validate itself, show error message in their tabs and return a validate message
You can use useImperativeHandle with forwardRef.
From the docs,
useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef
const Child = React.forwardRef((props,ref) => {
//validate will be available to parent component using ref
useImperativeHandle(ref, () => ({
validate() {
// to validate this component
console.log("I'm clicked");
}
}));
return (
<>Some code here</>
)
})
In the parent compoennt you can access child function as,
function validateChildren() {
refs.current.forEach(child => {
child.validate(); // you can access child method here
});
}
Demo

React: How to call function of new added child component?

I have a class component rendering multiple child components:
// inside my parent component
const allElements = arrayWithElements.map(elem => (
<ChildElement title={elem.title} />
));
// my child component
const ChildElement = ({title}) => {
// log the title to the console
const doSomething = () => {
console.log(title);
}
return (
<button type="button" onClick={doSomething}>{title}</button>
);
}
My problem is that when I add a new element to the array "allElements", I want to execute my function "doSomething" one time just for the newly added element. I thought that there might be a solution by using refs, but afaik refs are not allowed in combination with function components, so can I accomplish that?
Access your children's methods is anti pattern. To achieve the described effect you could configure each ChildComponent to log only once on mount
const ChildElement = ({title}) => {
//logging once in mount
useEffect(() => console.log(title), [])
const doSomething = () => {
console.log(title);
}
return (
<button type="button" onClick={doSomething}>{title}</button>
);
}

ReactJS fire an event into component when my global socket listener fires through socket

I am using ReactJS and i have connected socket as in the different file as global function.
There is a message event listener of socket and fires when any new message comes from backend.
Now what i want is that, i want to fire an event into component when my message event listener fires as it is defined as a global function.
Can anyone have an idea, how we do ?
Thanks in advance
I've made few (custom) event dispatching examples for you here: https://codesandbox.io/s/13287npq03. (Events will log to the console - expand it on Console click )
Basics:
If you want to get fired event inside of any React Component, you have to add the event listener, the best practice is to add it inside of componentDidMount:
componentDidMount = () => {
let element = document.getElementById("idOfEventListener");
element.addEventListener("eventname", this.eventListener);
};
Note that you must have that element inside of one of your Component render method.
E.G. render() { return <div id="idOfEventListener" /> }
You must have method which will be triggered when event occurs. Instead of element.addEventListener("eventname", this.eventListener); you can also create inline function element.addEventListener("eventname", e => console.log('event occured', e));, otherwise you will have to create event listener method inside of your Component:
eventListener = e => {
console.log("event occured in component", e, e.target);
};
Best practice is to clean up (remove event listener) on Component unmount:
componentWillUnmount = () => {
let element = document.getElementById("idOfEventListener");
element.removeEventListener(
"eventname",
EventHelper.dispatchCustomEvent,
false
);
};
Explanation of https://codesandbox.io/s/13287npq03:
EventHelper.js
I've created the generic dispatchCustomEvent method inside of separated file so you can use it (and dispatch event) from any component. It accepts target (DOM element, e.g. document.getElementById('idOfEventListener')), event name (it must be the same name you are listening for, in above addEventListener I specified I'm listening "eventname" only) and details -> custom data you want to provide, object/string/number, whatever you need.
export default {
dispatchCustomEvent: (target, name, details) => {
if (target) {
let event = new Event(name, {
details: details,
bubbles: true,
cancelable: true
});
target.dispatchEvent(event);
}
}
};
For event listener above I would have to call EventHelper.dispatchCustomEvent(document.getElementById("idOfEventListener"), "eventname", {test: "whatever data you need"}), and my listener would trigger this.eventListener method and console log event & event.target (that would be DOM element ).
You can call EventHelper.dispatchCustomEvent when your message event listener fire, or if you know what your message event looks like - you could just listen for that message event inside of the Component you need it. If message event is global, I suppose it is creating the event on the window or document - you would have to create the event listener on Component like this:
componentDidMount = () => {
let element = window || document;
element.addEventListener("messageEventName", this.eventListener);
};
eventListener = e => {
console.log("event inside second child component:", e, e.target);
};
componentWillUnMount = () => {
let element = window || document;
element.removeEventListener(
"messageEventName",
EventHelper.dispatchCustomEvent,
false
);
};
Other Components:
index.js
Main/Parent Component which has 2 child components FirstChildComponent and SecondChildComponent. I've created 2 buttons inside of the render - first button will fire event which FirstChildComponent is listening for, and the second button will fire event "eventname" which is listened by parent (index.js) and SecondChildComponent, and it will trigger both parent and second child event listeners. Note that instead of binding that event to window or document, I've created <div id="idOfEventListener"> which is the wrapper of the entire application, and both SecondChildComponent and parent can use it as event listener target and get events that are targeted to it.
import React, { Component } from "react";
import FirstChildComponent from "./FirstChildComponent";
import SecondChildComponent from "./SecondChildComponent";
import ReactDOM from "react-dom";
import EventHelper from "./EventHelper";
class App extends Component {
componentDidMount = () => {
let element = document.getElementById("idOfEventListener");
element.addEventListener("eventname", this.eventListener);
};
eventListener = e => {
console.log("event in parent component", e, e.target);
};
componentWillUnMount = () => {
let element = document.getElementById("idOfEventListener");
element.removeEventListener(
"eventname",
EventHelper.dispatchCustomEvent,
false
);
};
fireEvent = () => {
let additionalData = { loremIpsum: "dolor sit amet" };
let target = document.getElementById("FirstChildEventListener");
EventHelper.dispatchCustomEvent(
target,
"firstChildEventName",
additionalData
);
};
fireMultipleListenedEvent = () => {
console.log("fire multiple");
let additionalData = { test: "blabla" };
let target = document.getElementById("idOfEventListener");
EventHelper.dispatchCustomEvent(target, "eventname", additionalData);
};
render() {
return (
<div id="idOfEventListener" className="App">
<h1>Parent Component</h1>
<div id="GettingEventInParentComponent">
Box in parent component listening for event
</div>
<button onClick={this.fireEvent}>
Test Button that will fire event which FirstComponent will receive
</button>
<FirstChildComponent />
<SecondChildComponent />
<hr />
<button onClick={this.fireMultipleListenedEvent}>
Test Button that will fire event which is listened inside of Second
Child Component and parent component both
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
FirstChildComponent
Listens for the event on target inside of FirstChildComponent render (<p id="FirstChildEventListener">), and that event is triggered inside of another component (parent, but it could be easily triggered from the SecondChildComponent, and listener would still get it):
import React, { Component } from "react";
import EventHelper from "./EventHelper";
export default class FirstChildComponent extends Component {
componentDidMount = () => {
var element = document.getElementById("FirstChildEventListener");
element.addEventListener("firstChildEventName", this.eventListener);
};
eventListener = e => {
console.log("event inside first child component:", e, e.target);
};
componentWillUnMount = () => {
var element = document.getElementById("FirstChildEventListener");
element.removeEventListener(
"firstChildEventName",
EventHelper.dispatchCustomEvent,
false
);
};
render() {
return (
<div className="App">
<h1>First Child Component</h1>
<p id="FirstChildEventListener">
This element is inside of FirstChildComponent, and it only listens
when event will be dispatched
</p>
</div>
);
}
}
SecondChildComponent.js
Listens for the event on (parent) DOM element, outside of its render.
import React, { Component } from "react";
import EventHelper from "./EventHelper";
export default class FirstChildComponent extends Component {
componentDidMount = () => {
let element = document.getElementById("idOfEventListener");
element.addEventListener("eventname", this.eventListener);
};
eventListener = e => {
console.log("event inside second child component:", e, e.target);
};
componentWillUnMount = () => {
let element = document.getElementById("idOfEventListener");
element.removeEventListener(
"eventname",
EventHelper.dispatchCustomEvent,
false
);
};
render() {
return (
<div className="App">
<h1>Second Child Component</h1>
<span>Listening event on upper level</span>
</div>
);
}
}
I'm firing events from parent Component (on buttons click), but you can just fire event inside of message event listener, and then create the event listener for that exact event inside of the Components you need it.
EDIT:
I've added GlobalFile.js to codesandbox with example of websocket:
import EventHelper from "./EventHelper";
// or just copy dispatchCustomEvent function
/*function dispatchCustomEvent (target, name, details) {
if (target) {
let event = new Event(name, {
details: details,
bubbles: true,
cancelable: true
});
target.dispatchEvent(event);
}
}*/
let jsWebSocket = null;
function initSocket(user_id) {
jsWebSocket = new WebSocket("wss://demos.kaazing.com/echo");
jsWebSocket.addEventListener("open", function(event) {
jsWebSocket.addEventListener("message", function(event) {
console.log("message listener fired");
let elementInsideOfComponent = document.getElementById(
"elementInsideOfComponnet"
);
let someData = { id: "message1" };
// if element target exists dispatch event to component
console.log(elementInsideOfComponent);
if (elementInsideOfComponent)
EventHelper.dispatchCustomEvent(
elementInsideOfComponent,
"websocketmessage",
someData
);
});
});
}
function sendMessage() {
jsWebSocket.send("Here's some text that the server is urgently awaiting!");
}
export default { initSocket, sendMessage, jsWebSocket };
Added ComponentListeningForGlobalEvent.js with DOM element <div id="elementInsideOfComponnet">Listening for global message</div> which will be target of dispatched event from websocket message event:
import React, { Component } from "react";
import EventHelper from "./EventHelper";
export default class ComponentListeningForGlobalEvent extends Component {
componentDidMount = () => {
let element = document.getElementById("elementInsideOfComponnet");
element.addEventListener("websocketmessage", this.eventListener);
};
eventListener = e => {
console.log("websocketmessage event inside component:", e, e.target);
};
componentWillUnMount = () => {
let element = document.getElementById("elementInsideOfComponnet");
element.removeEventListener(
"websocketmessage",
EventHelper.dispatchCustomEvent,
false
);
};
render() {
return (
<div className="App">
<h1>Component that will receive event from websocket</h1>
<div id="elementInsideOfComponnet">Listening for global message</div>
</div>
);
}
}
Also added:
constructor(props) {
super(props);
GlobalFile.initSocket("id");
}
to index.js (parent component) to initialize websocket connection. And to the render of the index.js:
<ComponentListeningForGlobalEvent />
<button onClick={GlobalFile.sendMessage}>Send websocket message</button>
Inserted Component that will receive event from websocket message, and button triggering webscket send message, so you can test (and see) that websocket message event is propagated all the way to the ComponentListeningForGlobalEvent.
I really hope this helps! :)

Test if a react component's child is in fact the provided child with jest

I have a react "wrapper" component that is supposed to wrap its child. Here is the relevant part:
export class Wrapper extends Component {
render(){
return (<div>{ this.props.children }</div>);
}
}
I am trying to use jest to test if the rendered child is indeed what has been provided to this wrapper.
Here is what I tried;
describe('SwapWrapper', () => {
it('contains its child', () => {
const potentialChild = (<AMockedComponent/>);
const wrapper = TestUtils.renderIntoDocument(
<Wrapper>{potentialChild}</Wrapper>
);
const realChild = TestUtils.findRenderedComponentWithType(wrapper, AMockedComponent);
expect(realChild).toBe(potentialChild); // Obviously does not work.
});
});
It obviously does not work. realChild is a component instance while potentialChild is a component element.
Currently, the only things I have been able to do is to create potentialChild with a property and to check that realChild does contain this property.
Is there a more valid way to check if realChild corresponds in fact to the potentialChild that has been provided?
I found a solution using ref.
The ref property of a react element will be called back with the created instance when it is instantiated.
describe('SwapWrapper', () => {
it('contains its child', () => {
const ref = jest.fn();
const potentialChild = (<AMockedComponent ref={ref}/>);
const wrapper = TestUtils.renderIntoDocument(
<Wrapper>{potentialChild}</Wrapper>
);
const realChild = TestUtils.findRenderedComponentWithType(wrapper, AMockedComponent);
expect(ref).toBeCalledWith(realChild);
});
});

Categories