test cases when accessing function using refs - javascript

I am having a react-app where i am rendering button in one component and model in another component . The button access the function openModal for opening the modal through refs. i am writing test cases for my application but could not figure out a way to write a test case for checking the modal is opening on button click
Buttons component
<div>
<button type='primary' onClick={() => this.handleClick.showModal()}>
ADD
</button>
<AddConceptModal ref={(instance) => this.handleClick = instance}/>
</div>
Modal component:
class ModalComp extends React.Component {
state = {
visible: false,
}
// show modal handles the logic of opening the modal
showModal = () => {
this.setState({
visible: true,
})
}
render() {
const { visible } = this.state
return (
<div>
<Modal
visible={visible}
>
modal
</Modal>
</div>
)
}
}
export default ModalComp
i tried creating instance like this:
let component = mount(<ModalComp />)
const instance = component.instance()
i even tried spyOn method in jest could do it exactly. how can i write test that simulate the button in button component which calls the showModal() and
i want to check if modal is receiving prop as true after button click simulation

Related

Enzyme - Testing onClick of child component

I very new to testing and I am currently testing React components with jest/enzyme.
I have a parent component
ParentComp.jsx
export class ParentComp extends React.Component {
super(props);
this.state = {
selectedTemplate: "",
disabled: true
};
return <Modal
header={<h2>Header</h2>}
visible={true}
footer={<span>
<Button
disabled={false}
onClick={ () => {
sessionStorage.setItem('id', this.state.id);
}}
>
Continue
</Button>
}>
<h1> My modal </h1>
</Modal>
}
How would I go about testing the onClick and making sure the sessionStorage is tested?
I've already tried:
ParentComp.spec.jsx
const wrapper = shallow(<ParentComp/>);
wrapper.find(Modal).first().props().footer.find(Button).simulate('click')
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();
// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
expect(global.sessionStorage.getItem).toBecalledWith('id',1)
}
I thankfully don't get any actual errors, however, my sessionStorage line in ParentComp is apparently not being covered. How would I go about covering this line?
I have an idea to call directly prop onClick directly in this case instead of simulating of click. As long as you can find your <Button /> in your footer then call its prop of onClick. Let's try:
// You can console.log to see what is the correct to select the right one
// I'm not sure below route is correct :)
const yourButton = wrapper.find(Modal).first().props().footer.props.children;
yourButton.props.onClick();

How to prevent modal from re-rendering when I update the state?

I have a react-bootstrap modal with two inputs inside.
Modal is displayed when showModal property in the state changes to true.
I update the fieldOne property (also inside the state) when input value changes.
So, whenever I enter something in the input, modal flashes (re-renders) as well.
How to I prevent Modal from re-rendering when I update the state?
Maybe you should split your modal with the inputs into two seperate components. That should fix your rerender issues.
If you don't want a re-render use a variable other than state to hold your data:
constructor (props) {
super(props);
this.state = {
input: ''
};
this.holder = '';
}
handleInput(input) {
this.holder = input;
}
submitInput() {
this.setState({input: this.holder})
}
render () {
return (
<input type="text" onChange={(e) => this.handleInput(e.target.value)} onBlur={() => this.submitInput()} />
)
}
The purpose of state is for React to evaluate if the DOM needs to change, and if it does it re-renders.
I hit the same problem - putting a form in a modal resulted in the modal rerendering on each keypress.
I could probably get around this by splitting out the form from the modal, BUT I wanted the modal and the form in the same component because the modal buttons trigger the form save. Yes there's other ways to handle that too like passing the save function between the split modal and the form, but now it's getting messy.
So my solution was to make the form in the modal uncontrolled. This means that changing the field values does not modify state and therefore the modal does not rerender.
Set the Modal's view condition with separate states solves this issue.
Herewith a demo example, I used two seperate states i.e, firstView & secondView
import Modal from 'react-bootstrap/Modal'
import ModalBody from 'react-bootstrap/ModalBody'
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
demoModal: true,
firstView: true,
secondView: false
};
};
render() {
return (
<div>
<Modal scrollable={true} show={this.state.demoModal} fade={false} style={{ display: "block"}}>
<ModalBody>
<div>
{
this.state.firstView ?
<div>
..code
</div>
:
<></>
}
{
this.state.secondView ?
<div>
..code
</div>
:
<></>
}
</div>
</ModalBody>
</Modal>
</div>
);
}
}

how to call functional component in class based component using onClick event?

i want to show my functional component in class base component but it is not working. i made simpletable component which is function based and it is showing only table with some values but i want to show it when i clicked on Show user button.
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.userList = this.userList.bind(this);
}
userList = () => {
//console.log('You just clicked a recipe name.');
<SimpleTable/>
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList} >
Show User List
</Button>
</div>
);
}
}
export default ShowUser;
Why your code is not working
SimpleTable has to be rendered, so you need to place it inside the render method. Anything that needs to be rendered inside your component has to be placed there
On Click can just contain SimpleTable, it should be used to change the value of the state variable that controls if or not your component will be shown. How do you expect this to work, you are not rendering the table.
Below is how your code should look like to accomplish what you want :
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.state = { showUserList : false }
this.userList = this.userList.bind(this);
}
showUserList = () => {
this.setState({ showUserList : true });
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.showUserList} >
Show User List
</Button>
{this.state.showUserList ? <SimpleTable/> : null}
</div>
);
}
}
export default ShowUser;
You can also add a hideUserList method for some other click.
Or even better a toggleUserList
this.setState({ showUserList : !this.state.showUserList});
If you're referring to the method userList then it appears that you're assuming there is an implicit return value. Because you're using curly braces you need to explicitly return from the function meaning:
const explicitReturn = () => { 134 };
explicitReturn(); <-- returns undefined
const implicitReturn = () => (134);
implicitReturn(); <-- returns 134
The problem lies with how you are trying to display the SimpleTable component. You are using it inside the userList function, but this is incorrect. Only use React elements inside the render method.
What you can do instead is use a state, to toggle the display of the component. Like this:
const SimpleTable = () => (
<p>SimpleTable</p>
);
class ShowUser extends React.Component {
constructor(props) {
super(props);
this.state = {showSimpleTable: false};
this.toggle= this.toggle.bind(this);
}
toggle = () => {
this.setState(prev => ({showSimpleTable: !prev.showSimpleTable}));
}
render() {
return (
<div>
<button variant = "contained" color = "primary" onClick={this.toggle}>
Show User List
</button>
{this.state.showSimpleTable && <SimpleTable />}
</div>
);
}
}
ReactDOM.render(<ShowUser />, 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>
<div id="app"></div>
The functionality you are looking for is called Conditional Rendering. The onClick prop function is an event handler and events in react may be used to change the state of a component. That state then may be used to render the components. In normal vanilla javascript or jQuery we call a function and modify the actual DOM to manipulate the UI. But React works with a virtual DOM. You can achieve the functionality you are looking for as follows:
class ShowUser extends Component {
constructor(props) {
super(props)
// This state will control whether the simple table renders or not
this.state = {
showTable: false
}
this.userList.bind(this)
}
// Now when this function is called it will set the state showTable to true
// Setting the state in react re-renders the component (calls the render method again)
userList() {
this.setState({ showTable: true })
}
render() {
const { showTable } = this.state
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList}>
Show User List
</Button>
{/* if showTable is true then <SimpleTable /> is rendered if falls nothing is rendered */}
{showTable && <SimpleTable />}
</div>
)
}
}

React handling outside clicks to close custom button menu component

I have a button menu component I've created that acts as a simple "action" menu to use in table on a per row basis. I'm having an issue handling outside clicks to close the menu when it is visible. I currently have a listener that gets attached when the button is clicked and it works fine when only a single button-menu component is being rendered. However when I have multiple being rendered (like in a table), they all react to the same events - ie. the user clicks outside and they all open/close together at the same time. How can I make it so that they all respond individually?
Sample code of what I have below:
export default class MenuButton extends React.Component {
constructor(props) {
super(props);
this.node = React.createRef();
this.state = {
showMenu: false,
}
}
handleOutsideClick = (e) => {
// Ignore clicks on the component itself
if (this.node.current.contains(e.target)) {
return;
}
this.handleButtonClick();
}
handleButtonClick = () => {
if (!this.state.show) {
// Attach/remove event handler depending on state of component
document.addEventListener('click', this.handleOutsideClick, false);
} else {
document.removeEventListener('click', this.handleOutsideClick, false);
}
this.setState({
showMenu: !this.state.showMenu,
});
}
render() {
return (
<div>
<Button
text="Actions"
onClick={this.handleButtonClick}
ref={this.node}
menuTrigger
/>
<Menu anchor={this.node.current} visible={this.state.showMenu}>
<Menu.Group title={this.props.groupTitle}>
{this.props.children}
</Menu.Group>
</Menu>
</div>
)
}
}
So my problem was due to a typo in my code that I noticed. I have a showMenu state, but in my handleButtonClick I was checking this.state.show instead of this.state.showMenu.

Call react component from outside

Is that possible to call react component from outside?
For example
HTML
<div id='react-app'></div>
<button onClick=callReactModal()>PressME</button>
My component where i want call method
let callReactModal = function () {
console.log('clicked');
//Navigation.sayHello();
}
class Navigation extends React.Component<any, any> {
constructor(props:any){
super(props);
this.state = {
language: {
lang: cookie.load('lang') || ''
}
};
this.click = this.click.bind(this);
}
sayHello = () => {
alert("Hello");
}
}
I have to call Modal from another component but i don't know how to achieve that.
Trying to call method which update state in class and getting Warning: setState(...): Can only update a mounted or mounting component. It needs to open modal ( Using semantic-ui )
method which uses state
handleOpen = (e) => this.setState({
modalOpen: true,
})
modalPart
<Modal size='small'
open={this.state.modalOpen}
onClose={this.handleClose}
trigger={<a className="btn btn-base" onClick={this.handleOpen}>Login</a>}
closeIcon='close'>
Thanks for help!
Although not the best practice. You have to set callReactModal function in the window
window.callReactModal = function () {
console.log('clicked');
//Navigation.sayHello();
}
A better way to implement it, is to create an event listener that opens the modal when triggered.
No, it's not possible. All your "custom tags"(React Components) should be inside your JSXs. But you can render multiple React apps per page if you want:
<div id='react-app'></div>
<div id='react-button'></div>

Categories