jest input on change not triggered/simulated - reactjs

I have a React component that looks like this. It's a simple form with an input element of type email. As usual, when the user types some text, I fire a callback for the onChange event. This is what the code looks like.
import React, { PureComponent, Fragment } from "react";
import CheckCircleOutline from "mdi-react/CheckCircleOutlineIcon";
import AccountOutlineIcon from "mdi-react/AccountOutlineIcon";
import styles from "./ForgotPassword.module.scss";
class ForgotPasswordFrom extends PureComponent {
constructor() {
super();
this.state = {
email: ""
};
}
updateEmailField = e => {
this.setState({ email: e.target.value });
};
resetPassword = async e => {
e.preventDefault();
const { email } = this.state;
this.props.onSubmit(email);
};
render() {
const { showResetMessage, email } = this.props;
return (
<Fragment>
<form className="form aim-form">
{!showResetMessage ? (
<Fragment>
<div className="form__form-group">
<div className="form__form-group-field">
<div className="form__form-group-icon">
<AccountOutlineIcon />
</div>
<input
name="email"
type="email"
onChange={this.updateEmailField}
placeholder="Enter Registered Email Address"
className="email-input"
data-testid="forgot_password_input"
/>
</div>
</div>
<button
type="button"
className="btn btn-primary account__btn account__btn--small login-btn"
onClick={this.resetPassword}
data-testid="forgot_password"
>
Submit
</button>
</Fragment>
) : (
<div className={styles.messageContainer}>
<CheckCircleOutline size={50} />
<div className={styles.emailMessage}>
<div>We have sent an email to {email}.</div>
<div>Click the link in the email to reset your password</div>
</div>
</div>
)}
</form>
</Fragment>
);
}
}
export default ForgotPasswordFrom;
I am trying to write a test for when the input field's change event is simulated. This test should basically ensure that the updateEmailField function is triggered. However, no matter what I try, I cannot get the test to pass. The error I get is that the mock function is not called. Any idea what I'm doing wrong?
it("should have called the function", () => {
const wrapper = mount(<ForgotPasswordForm />);
const instance = wrapper.instance();
instance.updateEmailField = jest.fn()
const input = wrapper.find(`[data-testid='forgot_password_input']`);
input.simulate('change', { target: { value: 'test ' } });
expect(instance.updateEmailField).toHaveBeenCalled();
})

Try this code.
it("should have called the function", () => {
jest.spyOn(ForgotPasswordForm.prototype, 'updateEmailField');
const wrapper = mount(<ForgotPasswordForm />);
afterAll(() => {
ForgotPasswordForm.prototype.updateEmailField.mockRestore();
});
const input = wrapper.find(`[data-testid='forgot_password_input']`);
input.simulate('change', { target: { value: 'test ' } });
expect(wrapper.instance().updateEmailField).toHaveBeenCalled();
})

Related

Password show/hide using Eye/EyeSlash in React

I am trying to implement eye/eyeslash in on my Register form in React.
This is a function that's is responsible for changing visibility type and eye icon changing.
import React, { useState } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
export const usePasswordToggle = () => {
const [visible, setVisibility] = useState();
const Icon = <FontAwesomeIcon icon={visible ? "eye-slash" : "eye"} />;
const InputType = visible ? "text" : "password";
return [InputType, Icon];
};
I am trying to implement it in component responsible for registering.
import React, { Component, createRef } from "react";
import { usePasswordToggle } from "./usePasswordToggle";
class Register1 extends React.Component {
EmailR = createRef();
UsernameR = createRef();
PasswordR = createRef();
PasswordConfirmR = createRef();
constructor(props) {
super();
this.state = {
message: "",
password: "",
confirmPassword: "",
};
}
handleSubmit = (event) => {
// alert(this.PasswordR.current.value);
// alert(this.PasswordConfirmR.current.value);
if (this.PasswordR.current.value !== this.PasswordConfirmR.current.value) {
alert("The passwords doesn't match");
return false; // The form won't submit
} else {
alert("The passwords do match");
return true; // The form will submit
}
};
onCreateAccount = () => {
let loginInfo = {
Username: this.UsernameR.current.value,
Email: this.EmailR.current.value,
Password: this.PasswordR.current.value,
};
fetch("http://localhost:5000/api/authenticate/register", {
method: "POST",
headers: { "Content-type": "application/json" },
body: JSON.stringify(loginInfo),
})
.then((r) => r.json())
.then((res) => {
if (res) {
this.setState({
message:
"New Account is Created Successfully. Check your email to verify Account.",
});
}
});
};
render() {
return (
<div>
<h2 className="FormDescription">
{" "}
Please enter Account details for registration
</h2>
<div className="Form">
<p>
<label>
Email: <input type="text" ref={this.EmailR} />
</label>
</p>
<p>
<label>
Username: <input type="text" ref={this.UsernameR} />
</label>
</p>
<div>
<label>
Password:{" "}
<input type={usePasswordToggle.InputType} ref={this.PasswordR} />
</label>
<span className="password-toogle-icon">
{usePasswordToggle.Icon}
</span>
</div>
<p>
<label>
ReenterPassword:{" "}
<input type="password" ref={this.PasswordConfirmR} />{" "}
</label>
</p>
<button onClick={this.handleSubmit}> Create </button>
<p>{this.state.message}</p>
</div>
</div>
);
}
}
export default Register1;
My password is always visible, and eye icon is even not visible on the form (it should be inside my input field, but it is not).
Focus on this code snippet:
<div>
<label>
Password: <input type={usePasswordToggle.InputType} ref={this.PasswordR} />
</label>
<span className="password-toogle-icon">{usePasswordToggle.Icon}</span>
</div>
Any suggestion what is the problem?
Change this
const [visible, setVisibility] = useState();
to this
const [visible, setVisible] = useState(true);
as the official documentation here
First, add a default value to your useState, either true or false depending on which icon you want to render first.
Then, you should add a onClick method to your icon which will toggle the visibility state. You're setting the icon based on visible value, but you never toggle the value.
onClick={() => setVisibility(!visible)}
UPDATE
You also need to execute your Hook inside your main component (because yes, you wrote what React call a Hook), like so :
const [inputType, icon] = usePasswordToggle();
But doing so, you'll get an error from React that say you cannot use a Hook within a class component due to how they work.
Basically you need to change your Register1 component to be a functional component, and not a class anymore. Look here for a quick overview on how to : https://reactjs.org/docs/components-and-props.html

Deleting function not working and no errors appear

Good day so I have a question about firebase and perhaps my code as well I wrote some code in JSX and React linked to Firebase and the Button that I'm using to delete is not working properly.
I'm using Parent Child props to pass the function into the page that is needed to be deleted but there is no functionality. I need help thanks!
this is the parent where the function is located :
import React from 'react';
import fire from '../config/firebase';
import Modal from 'react-modal';
// import "firebase/database";
// import 'firebase/auth';
import NotesCard from './note-card';
Modal.setAppElement('#root');
export default class Notes extends React.Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
notes: [],
showModal: false,
loggedin: false
};
this.handleOpenModal = this.handleOpenModal.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
this.handleAddNote = this.handleAddNote.bind(this);
this.handleRemoveNote = this.handleRemoveNote.bind(this);
}
componentDidMount() {
this._isMounted = true;
fire.auth().onAuthStateChanged((user) => {
if(user){
// call firebase from import fire
// grab userData and push it to the dataArray
fire.database().ref(`users/${user.uid}/notes`).on('value', (res) => {
const userData = res.val()
const dataArray = []
for(let objKey in userData) {
userData[objKey].key = objKey
dataArray.push(userData[objKey])
}
// set in the state
if(this._isMounted){
this.setState({
notes: dataArray,
loggedin: true
})
}
});
}else {
this.setState({loggedin: false})
}
});
};
componentWillUnmount() {
this._isMounted = false;
}
handleAddNote (e) {
e.preventDefault()
const note = {
title: this.noteTitle.value,
text: this.noteText.value
}
// reference where we can push it
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes`);
dbRef.push(note)
this.noteTitle.value = ''
this.noteText.value = ''
this.handleCloseModal()
}
handleRemoveNote(key) {
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes/${key}`);
dbRef.remove();
}
handleOpenModal (e) {
e.preventDefault();
this.setState({
showModal: true
});
}
handleCloseModal () {
this.setState({
showModal: false
});
}
render() {
return (
<div>
<button onClick={this.handleOpenModal}>create Note</button>
<section className='notes'>
{
this.state.notes.map((note, indx) => {
return (
<NotesCard
note={note}
key={`note-${indx}`}
handleRemoveNote={this.handleRemoveNote}
/>
)
}).reverse()
}
</section>
<Modal
isOpen={this.state.showModal}
onRequestClose={this.handleCloseModal}
shouldCloseOnOverlayClick={false}
style={
{
overlay: {
backgroundColor: '#9494b8'
},
content: {
color: '#669999'
}
}
}
>
<form onSubmit={this.handleAddNote}>
<h3>Add New Note</h3>
<label htmlFor='note-title'>Title:</label>
<input type='text' name='note-title' ref={ref => this.noteTitle = ref} />
<label htmlFor='note-text'>Note</label>
<textarea name='note-text' ref={ref => this.noteText = ref} placeholder='type notes here...' />
<input type='submit' onClick={this.handleAddNote} />
<button onClick={this.handleCloseModal}>close</button>
</form>
</Modal>
</div>
)
}
}
and this is where the function is being called :
import React from 'react';
import fire from '../config/firebase';
export default class NotesCard extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
note: {}
}
this.handleEditNote = this.handleEditNote.bind(this);
this.handleSaveNote = this.handleSaveNote.bind(this);
}
handleEditNote() {
this.setState({
editing: true
})
}
handleSaveNote(e) {
e.preventDefault()
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes/${this.props.note.key}`);
dbRef.update({
title: this.noteTitle.value,
text: this.noteText.value
})
this.setState({
editing: false
})
}
render() {
let editingTemp = (
<span>
<h4>{this.props.note.title}</h4>
<p>{this.props.note.text}</p>
</span>
)
if(this.state.editing) {
editingTemp = (
<form onSubmit={this.handleSaveNote}>
<div>
<input
type='text'
defaultValue={this.props.note.title}
name='title'
ref={ref => this.noteTitle = ref}
/>
</div>
<div>
<input
type='text'
defaultValue={this.props.note.text}
name='text'
ref ={ref => this.noteText = ref}
/>
</div>
<input type='submit' value='done editing' />
</form>
)
}
return (
<div>
<button onClick={this.handleEditNote}>edit</button>
<button onClick={this.props.handleRemoveNote(this.state.note.key)}>delete</button>
{editingTemp}
</div>
)
}
}
Thank you in advance for taking a look at this code.
Second iteration answer
Working sandbox
Problem
looking at https://codesandbox.io/s/trusting-knuth-2og8e?file=/src/components/note-card.js:1621-1708
I see that you have this line
<button onClick={()=> this.props.handleRemoveNote(this.state.note.key)}>delete
Yet your state.note declared as an empty map in the constructor:
this.state = {
editing: false,
note: {}
}
But never assigned a value using this.setState in the component
Solution
Change it to:
<button onClick={()=> this.props.handleRemoveNote(**this.props.note.key**)}>delete</button>
First iteration answer
NotesCard's buttons is firing the onClick callback on render instead on click event.
This is because you have executed the function instead of passing a callback to the onClick handler
Change
<button onClick={this.props.handleRemoveNote(this.state.note.key)}>delete</button>
To
<button onClick={()=> this.props.handleRemoveNote(this.state.note.key)}>delete</button>

Cannot update state with file uploader in reactJS

I created a simple image uploader component that is rendered in a new-image-form that extends a form. but when handling a change(an image upload), the state is updated with an empty opbject (and not with the file object). I tried to debug it but anything goes right until this.setState. Can anyone helps?
NewImagesForm :
class NewImagesForm extends Form {
state = {
data: {
_id: 0,
name: "",
file: null,
},
errors: {},
apiEndpoint: "",
};
render() {
return (
<div className="new-menu-item-form">
<form onSubmit={this.handleSubmit}>
{this.renderInput("name", "Name")}
{this.renderUploadFile("file", "Upload")}
{this.renderButton("Add")}
</form>
</div>
);
}
Form
class Form extends Component {
state = { data: {}, errors: {} };
handleFileUpload = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.target.files[0];
this.setState({ data });
};
renderUploadFile(name, label) {
const { data } = this.state;
return (
<UploadImage
name={name}
label={label}
value={data[name]}
onChange={this.handleFileUpload}
></UploadImage>
);
}
}
export default Form;
File Upload Component
const UploadImage = ({ name, label, error, onChange }) => {
return (
<div className="input-group">
<div className="custom-file">
<input
type="file"
onChange={onChange}
className="custom-file-input"
id={name}
name={name}
></input>
{label && (
<label className="custom-file-label" htmlFor={name}>
{label}
</label>
)}
</div>
<div className="input-group-append">
<button className="btn btn-outline-secondary" type="button">
Button
</button>
</div>
</div>
);
};
export default UploadImage;
First of all, react does not recommend the use of extends, because of the combination of extends.
In the Form component, the setState() trigger the render() , but there is no render(). setState does not allow renderUploadFile to be re-executed.
It would be a good idea to use the UploadImage component in the NewImagesForm component. Don't let NewImagesForm extends Form.
But the exactly same way works for me with custom input text and custom select boxes, see the differences:
handleChange = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.currentTarget.value;
this.setState({ data });
};
handleFileUpload = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.target.files[0];
this.setState({ data });
};

How to Dynamically set the attributes of a component, from users input?

Set the attributes of a input field or any component by taking input from the user dynamically?
I would like to know if there is any way, where I would give user an option to choose a component from the list of components i would mention, and allow him to customize the components attributes. For example if the user chooses a Input component, he must be able to set the attributes of that particular component, like "required", "type", "placeholder".
You can achieve it by passing all attributes you want as props to the child component.
You should also add them to state of parent component with change handler.
Each time the user change something of the attributes, the state should update.
As the state updates, the new state will pass as props to child Component and it'll update.
I made a simple example to input: You can change its placeholder, minLength, and requierd.
Check This Example
in the render, method you can do something like this
render() {
// change the name and values base on your user input
userInputtedAttribName = "placeholder";
userInputtedAttribValue = "the placeholder";
// the object to contain your user defined attribute name and values
const dynamicAttributes = {
[userInputtedAttribName]: userInputtedAttribValue
};
return (
<div>
<input type="text" {...dynamicAttributes}></input>
</div>
)
}
the spread operator, {...dynamicAttributes}, will build the attributes and their values dynamically
Probably not even what you're looking for, but I made a medium-sized prototype that can show you how to create Components (input, button, textarea), dynamically.
It's like filling out a form. Choose a type of component you want to make from the select-list. Then define the attributes you want in the proceeding textboxes. Once you're done adding all the attributes, hit Generate to render your customized component.
Sandbox: https://codesandbox.io/s/dynamic-component-generator-mhuh5
Working code:
import React from "react";
import ReactDOM from "react-dom";
import Input from "./Input";
import Button from "./Button";
import TextArea from "./TextArea";
import "./styles.css";
class ComponentGenerator extends React.Component {
state = {
componentInProgress: null,
customizeMode: false,
attributeName: "",
attributeSetting: "",
boolean: false,
builtComponents: []
};
handleSelection = e => {
this.setState({
componentInProgress: { componentType: e.target.value },
customizeMode: true
});
};
createOptions = () => {
const { customizeMode, componentInProgress } = this.state;
return (
<div>
<h4>Choose a Component:</h4>
<select
onChange={this.handleSelection}
value={!customizeMode ? "Select" : componentInProgress.componentType}
>
<option>Select</option>
<option>Input</option>
<option>TextArea</option>
<option>Button</option>
</select>
</div>
);
};
handleOnChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleOnSubmit = e => {
const {
attributeName,
attributeSetting,
boolean,
componentInProgress
} = this.state;
e.preventDefault();
let componentCopy = JSON.parse(JSON.stringify(componentInProgress));
componentCopy.props = {
...componentCopy.props,
[attributeName]: boolean ? boolean : attributeSetting
};
this.setState({
componentInProgress: componentCopy,
attributeName: "",
attributeSetting: "",
boolean: false
});
};
setBoolean = boolean => {
this.setState({
boolean: boolean
});
};
generateComponent = () => {
const { componentInProgress, builtComponents } = this.state;
this.setState({
componentInProgress: null,
customizeMode: false,
builtComponents: [...builtComponents, componentInProgress]
});
};
defineComponentAttributes = () => {
const {
componentInProgress,
attributeName,
attributeSetting,
boolean
} = this.state;
return (
<div>
<h4>
Customizing:{" "}
<span className="highlight">{componentInProgress.componentType}</span>
</h4>
{/*Render form */}
<form onSubmit={this.handleOnSubmit}>
<label>Attribute: </label>
<input
className="form-group"
onChange={this.handleOnChange}
value={attributeName}
name="attributeName"
placeholder="Choose attribute (type)"
/>
<label>Definition: </label>
<input
className="form-group"
onChange={this.handleOnChange}
value={attributeSetting}
name="attributeSetting"
placeholder="Define attribute (text)"
/>
<label>This is a Boolean type: </label>
<input
type="radio"
name="boolean"
onChange={() => this.setBoolean(true)}
/>
True
<input
type="radio"
name="boolean"
checked={boolean === false}
onChange={() => this.setBoolean(false)}
/>
False
<button className="form-group" type="submit">
Add
</button>
</form>
{/*Create List of attributes */}
{componentInProgress.props && (
<div>
<h4>Defined Attributes:</h4>
{Object.entries(componentInProgress.props).map(
([propName, propValue]) => {
return (
<div key={propName}>
<span>{propName}: </span>
<span>{propValue + ""}</span>
</div>
);
}
)}
</div>
)}
<div>
<h4>Click to finish and generate:</h4>
<button onClick={this.generateComponent}>Generate</button>
</div>
</div>
);
};
renderComponents = () => {
const { builtComponents } = this.state;
return builtComponents.map((component, index) => {
let renderedComponent = () => {
switch (component.componentType) {
case "Input":
return <Input {...component.props} />;
case "Button":
return <Button {...component.props} />;
case "TextArea":
return <TextArea {...component.props} />;
default:
return null;
}
};
return (
<div key={index} className="componentSection">
<h4>{component.componentType}</h4>
{renderedComponent()}
<div>
<p>Attributes: </p>
{Object.entries(component.props).map(([propName, propValue]) => {
return (
<div key={propName}>
<span>{propName}: </span>
<span>{propValue + ""}</span>
</div>
);
})}
</div>
</div>
);
});
};
render() {
const { customizeMode } = this.state;
return (
<div>
{this.createOptions()}
{customizeMode && this.defineComponentAttributes()}
<div className="components">{this.renderComponents()}</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<ComponentGenerator />, rootElement);

Access class function from rendered class object

I'm building a web application to manage some inputs from the user where I want to execute a function on every object in list that is rendered in react. The rendered objects are a different class than the one it is executed in.
import React, { Component } from "react";
import UserInput from "./UserInput";
class Layout extends Component {
objList = []
state = {
update: ""
}
anotherOne = async () => {
this.objList.push(<UserInput key={this.objList.length} />);
this.setState({update: ""});
}
submitCase = async () => {
for (var testCase in this.objList){
this.objList[0].submitInfo();
}
}
removeLatest = async () => {
this.list.pop();
this.setState({update: ""});
}
render(){
return(
<div id="container">
<div>
{ this.objList }
</div>
<div>
<button onClick={ this.anotherOne }>Another One</button>
<button onClick={ this.submitCase }>Submit</button>
</div>
</div>
);
}
}
export default Layout;
import React, { Component } from "react";
class UserInput extends Component {
state = {
name: "",
hairColor: "",
age: ""
}
submitInfo = async () => {
let path = '/dbmanager';
let apiName = "myApi"
let myInit = {
body: {categoryId: "Person", type: this.state.hairColor, data: JSON.stringify(this.state)},
contentType: 'application/json',
}
await API.post(apiName, path, myInit)
.then(response => {
// Add your code here
console.log(response);
})
.catch(error => {
console.log(error.response);
})
}
handleStateUpdate = (event) => {
var eName = event.target.name;
var eValue = event.target.value;
this.setState({[eName]: eValue});
console.log(event.target.value, event.target.name);
}
render(){
return(
<div id="container">
<div>
<label>Name: </label>
</div>
<textarea
type="text"
id="name"
name="name"
value={this.state.name}
onChange={this.handleStateUpdate}/>
<div>
<label>Hair Color: </label>
</div>
<textarea
type="text"
id="hairColor"
name="hairColor"
value={this.state.hairColor}
onChange={this.handleStateUpdate}/>
<div>
<label>Age: </label>
</div>
<textarea
type="text"
id="age"
name="age"
value={this.state.age}
onChange={this.handleStateUpdate}/>
</div>
);
}
}
export default UserInput;
I want the access to the class functions of UserInput so that I could submit the data from all of them on the same button press. Instead the objects are considered functions and are not executable in any means.

Resources