Test a button of a child component in the parent component - javascript

I'm new in tests and search since 3 days how to resolve my problem. Hope you could help me..
I have a parent component :
import React from 'react';
import './Subscribe.scss';
import Button from '../../Components/Button/Button';
class Subscribe extends React.Component {
state = {
user: {
firstName: '',
pseudo:'',
email: '',
password:''
}
}
handleChange = (e) => {
this.setState({
user: {...this.state.user, [e.target.name]: e.target.value}
}, () => console.log(this.state))
}
onSubmit = (e) => {
// e.preventDefault()
console.log("you've clicked")
//todo
}
render() {
return(
<form className='subscribe' id='subscriptionForm'>
<label htmlFor='firstName'>Prénom :</label>
<input
data-testid='inputString'
type='text'
name='firstName'
onChange={this.handleChange}
value={this.state.user.firstName}
/>
<label htmlFor='pseudo'>Pseudo :</label>
<input
data-testid='inputString'
type='text'
name='pseudo'
onChange={this.handleChange}
value={this.state.user.pseudo}
/>
<label htmlFor='email'>Email :</label>
<input
data-testid='inputString'
type='email'
name='email'
onChange={this.handleChange}
value={this.state.user.email}
/>
<label htmlFor='password'>
password :
</label>
<Button id='submitSubscription' text='Go go !' onSubmit={this.onSubmit}/>
<Button text='Annuler'/>
</form>
)
}
}
export default Subscribe;
A child component :
import React from "react";
const Button = (props) => {
return (
<button type="button" onClick={props.onClick}>{props.text}</button>
)
}
Button.displayName = 'Button'
export default Button
i wanna test it but nothing works...
My test :
import React from 'react';
import { shallow, mount } from 'enzyme';
import Subscribe from './Subscribe.js';
import Button from "./../../Components/Button/button.js"
describe('<LogIn />', () => {
it('Should call onSubmit on subscribe component when button component is clicked and allow user to subscribe ', () => {
// Rend le composant et les enfants et renvoie un wrapper Enzyme
const wrapper = mount(<Subscribe />);
// Trouve la première balise bouton
const button = wrapper.find("#submitSubscription");
// Récupère l'instance React du composant
const instance = wrapper.instance();
// Ecoute les appels à la fonction on Submit
jest.spyOn(instance, "onSubmit");
button.simulate('click');
expect(instance.onSubmit).toHaveBeenCalled();
})
Comments are what I tried.
The answer is still Expected number of calls: >= 1 Received number of calls: 0
I'm open to try by react test too, I begin so any help would be a pleasure.
Thanks in advance !

Your form setup has some mistakes. Mainly, you'll want to put a handleSubmit on the form's onSubmit prop and change one of the buttons to have a type prop of submit. Please see the codesandbox for a working version:
Working example (you can run the tests by clicking on the Tests tab next to the Browser tab):
This example uses some es6 syntax, so if you're unfamilar, please read the following:
Object Destructuring
Spread syntax
Arrow Function Expressions (Fat Arrow/Lamba Functions) - Implict Returns
components/Button/Button.js
import React from "react";
const Button = props => (
<button
style={{ marginRight: 10 }}
type={props.type || "button"}
onClick={props.onClick}
>
{props.text}
</button>
);
Button.displayName = "Button";
export default Button;
components/Subscribe/Subcribe.js
import React, { Component } from "react";
import Button from "../Button/Button";
// import './Subscribe.scss';
const initialState = {
user: {
firstName: "",
pseudo: "",
email: "",
password: ""
}
};
class Subscribe extends Component {
state = initialState;
handleChange = ({ target: { name, value } }) => {
// when spreading out previous state, use a callback function
// as the first parameter to setState
// this ensures state is in sync since setState is an asynchronous function
this.setState(
prevState => ({
...prevState,
user: { ...prevState.user, [name]: value }
}),
() => console.log(this.state)
);
};
handleCancel = () => {
this.setState(initialState);
};
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit(this.state);
};
render = () => (
<form
onSubmit={this.handleSubmit}
className="subscribe"
id="subscriptionForm"
>
<label htmlFor="firstName">Prénom :</label>
<input
data-testid="inputString"
type="text"
name="firstName"
onChange={this.handleChange}
value={this.state.user.firstName}
/>
<br />
<label htmlFor="pseudo">Pseudo :</label>
<input
data-testid="inputString"
type="text"
name="pseudo"
onChange={this.handleChange}
value={this.state.user.pseudo}
/>
<br />
<label htmlFor="email">Email :</label>
<input
data-testid="inputString"
type="email"
name="email"
onChange={this.handleChange}
value={this.state.user.email}
/>
<br />
<label htmlFor="password">password :</label>
<input
data-testid="inputString"
type="password"
name="password"
onChange={this.handleChange}
value={this.state.user.password}
/>
<br />
<br />
<Button type="button" text="Annuler" onClick={this.handleCancel} />
<Button id="submitSubscription" type="submit" text="Soumettre" />
</form>
);
}
export default Subscribe;
components/Subscribe/__tests__/Subscribe.test.js (I'm passing in a onSubmit prop to mock it and I expect that to be called. This is a more common test case versus testing against React's event callback implementation which forces you to unnecessarily spy on the class field. By testing against the prop (or a state change or some secondary action), we already cover whether or not the callback works!)
import React from "react";
import { mount } from "enzyme";
import Subscribe from "../Subscribe.js";
const onSubmit = jest.fn();
const initProps = {
onSubmit
};
describe("<LogIn />", () => {
it("Should call onSubmit on subscribe component when button component is clicked and allow user to subscribe ", () => {
const wrapper = mount(<Subscribe {...initProps} />);
const spy = jest.spyOn(wrapper.instance(), "handleSubmit"); // not necessary
wrapper.instance().forceUpdate(); // not necessary
wrapper.find("button[type='submit']").simulate("submit");
// alternatively, you could simply use:
// wrapper.find("form").simulate("submit");
expect(spy).toHaveBeenCalledTimes(1); // not necessary
expect(onSubmit).toHaveBeenCalledTimes(1);
});
});
index.js
import React from "react";
import { render } from "react-dom";
import Subscribe from "./components/Subscribe/Subscribe";
import "./styles.css";
const onSubmit = formProps => alert(JSON.stringify(formProps, null, 4));
render(
<Subscribe onSubmit={onSubmit} />,
document.getElementById("root")
);

Related

ReactJS Error input is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`

I have a super simple React page connecting to NodeJS endpoints. I'm getting this error "Uncaught Error: input is a void element tag and must neither have children nor use dangerouslySetInnerHTML."
I have tried multiple solutions posted in SOF(put a label outside the input tag, use self close input tags, etc.) and all around but nothing helped.
EmailFaxDetails.js
import React, { useState } from 'react'
import FetchOrderDetails from './FetchOrderDetails';
import '../App.css';
const EmailFaxDetails = () => {
const [message, setMessage] = useState('');
const [isShown, setIsShown] = useState(false);
const handleChange = event => {
setMessage(event.target.value);
console.log(event.target.value);
};
const handleClick = event => {
event.preventDefault();
setIsShown(true);
console.log(message);
}
return(
<div>
<br></br>
<br></br>
Order Number: <input placeholder="Order Number" type="text" id="message" name="message" onChange={handleChange} value={message} autoComplete="off" />
<button onClick={handleClick}>Search</button>
{isShown && <FetchOrderDetails ord_no={message}/>}
</div>
)
}
export default EmailFaxDetails;
FetchOrderDetails.js
import React, { useEffect, useState } from 'react'
import axios from 'axios'
import '../App.css';
const FetchOrderDetails = ({ord_no}) => {
const [data, setData] = useState([]);
const url = `http://localhost:5000/api/customerOrder/${ord_no}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log(response.data)
setData(response.data)
})
.catch((err) => console.log(err));
}, [url]);
if(data) {
return(
<div>
{data.map((order) => (
<div key={order.ID}>
<br></br>
<br></br>
Sales Ack Email: <input placeholder="Sales Ack Email" id="salesAck">{order.cmt[0]}</input>
<br></br>
Invoice Email: <input placeholder="Invoice Email" id="salesInv">{order.cmt[1]}</input>
<br></br>
<br></br>
<div>
<button>Update</button>
</div>
</div>
))}
</div>
)
}
return (
<h1>Something went wrong, please contact IT!</h1>
)
}
export default FetchOrderDetails;
App.js
import React from 'react';
import EmailFaxDetails from './components/EmailFaxDetails';
import './App.css';
function App() {
return (
<>
<EmailFaxDetails />
</>
);
}
export default App;
In the FetchOrderDetails.js
Sales Ack Email: <input placeholder="Sales Ack Email" id="salesAck">{order.cmt[0]}</input>
<br></br>
Invoice Email: <input placeholder="Invoice Email" id="salesInv">{order.cmt[1]}</input>
input element is a self-closing tag and can't contain children elements or text.
If you want to add a default value for the input you can add defaultValue property.
Sales Ack Email: <input defaultValue={order.cmt[0]} placeholder="Sales Ack Email" id="salesAck" />
<br></br>
Invoice Email: <input defaultValue={order.cmt[1]} placeholder="Invoice Email" id="salesInv" />
Or add a value property and onChange event to update the value.

Need to Pass props to other components in hook

i want to pass a prop from one(App.jsx) component to other component(form.jsx) in state hooks
App.jsx
import React, {useEffect, useState} from 'react';
import Form from './components/Form';
import Table from './components/Table';
import axios from 'axios';
const App = () => {
const [data, setData] = useState({data:[]});
const [editData, setEditData] = useState([]);
const create = (data) => {
axios.post('http://localhost:5000/info',data).then((res) =>{
getAll();
})
}
useEffect(() =>{
getAll();
},[])
const getAll = () =>{
axios.get("http://localhost:5000/info").then((response) =>{
setData({
data:response.data
})
})
}
const update = event =>{
setEditData(data)
console.log(data); // THIS "data" is the prop that i need to pass to Form.jsx component
}
return (
<div>
<div>
<Form myData={create} editForm={editData} />
</div>
<div>
<Table getData={data} edit={update} />
</div>
</div>
);
};
export default App;
i want that "data" value form App.jsx component as props in this Form.jsx component
Form.jsx
import React, {useState} from 'react';
const Form = (props) => {
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
...formData,
[name]: value,
})
}
const infoSubmit = e =>{
e.preventDefault();
let data={
Name:formData.Name,
Age:formData.Age,
City:formData.City
}
props.myData(data);
}
const componentWillReceive = (props) => { // i want the props data here
console.log(props.data); // in class component they use componentWillReceiveRrops ,
} // is there any alternative for function based component to receive props?
return (
<div>
<form onSubmit={infoSubmit} autoComplete="off">
<div>
<label>Name:</label>
<input type="text" onChange={infoChange} name="Name" value={formData.Name} placeholder="Enter Name" />
</div>
<div>
<label>City:</label>
<input type="text" onChange={infoChange} name="City" value={formData.City}
placeholder="Enter City" />
</div>
<div>
<label>Age:</label>
<input type="text" onChange={infoChange} name="Age" value={formData.Age} placeholder="Enter Age" />
</div>
<button type="submit">Submit</button>
</form>
</div>
);
};
export default Form;
i have commented the area of problem within the code , you can ignore the return () block of code.
Sorry for silly questions but THANKYOU Very Much !!! in advance
Use the following code in Form.jsx, the useEffect will listen the change of props.data and update the value
useEffect(() => {
setFormData(props.data);
},
[props.data]);
For more information, you may check the following answer
https://stackoverflow.com/a/65842783/14674139

Setting State From props Values To Make An UPDATE Request

I have Project objects:
{
id: "",
projectName: "" ,
projectIdentifier: "" ,
description:"" ,
startDate: Date ,
endDate: Date ,
}
I have a component that gets called when a user clicks on an "Update Project" button. The specific Project that they clicked on gets passed into the UpdateProject component via props.
When I try to console.log(props.project) right below the const updateProject = (props) => { line, I can see the object coming in. Great. :-)
{
id: 3,
projectName: "Name" ,
projectIdentifier: "1234" ,
description:"Project description" ,
startDate: "02-11-2022" ,
endDate: "02-11-2022" ,
}
However, I am having trouble setting the state in this component. I need to be able to update the details of the Project object and send that UPDATE request to the server.
Any help is greatly appreciated.
import React, { useState, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { useNavigate, useParams } from "react-router";
import { getProject, createProject } from "../../actions/projectActions";
const UpdateProject = (props) => {
console.log(props.project); //the Project object is coming in!!!
let navigate = useNavigate();
const { id } = useParams();
const [projectName, setProjectName] = useState(props.project.projectName);
console.log(props.project.projectName); <---- this is defined in console.log :-)
console.log(projectName); <---- this is "undefined"
const [projectIdentifier, setProjectIdentifier] = useState(
props.project.projectIdentifier
);
const [description, setDescription] = useState(props.project.description);
const [startDate, setStartDate] = useState(props.project.startDate);
const [endDate, setEndDate] = useState(props.project.endDate);
const [errors, setErrors] = useState(props.project.errors);
const [state, setState] = useState({
id,
projectName: props.project.projectName,
projectIdentifier: props.project.projectIdentifier,
description: props.project.description,
startDate: props.project.startDate,
endDate: props.project.endDate,
});
console.log("state before useEffect: ", state); // <--- here, id is the only thing console logging. Every other key is returning 'undefined'
useEffect(() => {
if (props.errors) {
setErrors(props.errors);
}
props.onGetProject(id, navigate);
console.log("STATE: ", state); <--- again, id is the only thing console logging. Every other key is returning 'undefined'
}, [props.errors]);
const onSubmit = (e) => {
e.preventDefault();
const updateProject = {
id: this.state.id,
projectName: this.state.projectName,
projectIdentifier: this.state.projectIdentifier,
description: this.state.description,
startDate: this.state.startDate,
endDate: this.state.endDate,
errors: {},
};
props.onCreateProject(updateProject, navigate);
};
For the rest of the component, my inputs in the return() method look like this, for example:
<form onSubmit={onSubmit}>
<div className="form-group">
<input
type="text"
className={classNames("form-control form-control-lg ", {
"is-invalid": errors.projectName,
})}
placeholder="Project Name"
name="projectName"
defaultValue={props.project.projectName}
onChange={(e) => setProjectName(e.target.value)}
/>
{errors.projectName && (
<div className="invalid-feedback">{errors.projectName}</div>
)}
</div>
It's anti-pattern to store passed props into local component state, just consume the prop value directly. You are also incorrectly referencing a this in the submit handler. this is OFC simply undefined in function components.
From what I can tell you are wanting to initialize the projectName from props, update this value in a form, and upon submitting the form, use this projectName state along with the project object passed as props to update a project.
Use an useEffect hook to keep the local state synchronized with the project object if/when the props.project updates.
const [projectName, setProjectName] = useState(props.project.projectName);
useEffect(() => {
setProjectName(props.project.projectName);
}, [props.project.projectName]);
Merge this projectName state with the props.project object in the submit handler.
const onSubmit = (e) => {
e.preventDefault();
const updateProject = {
...props.project, // <-- shallow copy props.project object
id, // <-- id from params
projectName, // <-- projectName state
errors: {},
};
props.onCreateProject(updateProject, navigate);
};
The form should use a controlled input, using the projectName state as the value.
<form onSubmit={onSubmit}>
<div className="form-group">
<input
type="text"
className={classNames(
"form-control form-control-lg ",
{ "is-invalid": errors.projectName }
)}
placeholder="Project Name"
name="projectName"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
/>
{errors.projectName && (
<div className="invalid-feedback">{errors.projectName}</div>
)}
</div>
</form>
Update
Since you are attempting to convert a class component to a function component, here are some basic steps:
Convert class to function and body, change render method to be the function return.
Convert component state into a useState hook.
Convert the componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods into one or more useEffect hooks with appropriate dependency array.
Convert all references to this.state to the new state variables, and this.props to props or any variables destructured from props.
Class component being converted
Function component version:
const UpdateProject = ({ createProject, getProject, project }) => {
const navigate = useNavigate();
const { id } = useParams();
const [state, setState] = useState({
id: "",
projectName: "",
projectIdentifier: "",
description: "",
startDate: "",
endDate: ""
});
useEffect(() => {
setState(project); // update project state when project prop updates
}, [project]);
useEffect(() => {
getProject(id, navigate); // fetch project when id updates
}, [id]);
const onChange = (e) => {
setState((state) => ({
...state,
[e.target.name]: e.target.value
}));
};
const onSubmit = (e) => {
e.preventDefault();
const updateProject = {
...state
};
createProject(updateProject, navigate);
};
return (
<div className="project">
<div className="container">
<div className="row">
<div className="col-md-8 m-auto">
<h5 className="display-4 text-center">Update Project form</h5>
<hr />
<form onSubmit={onSubmit}>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg "
placeholder="Project Name"
name="projectName"
value={state.projectName}
onChange={onChange}
/>
</div>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Unique Project ID"
name="projectIdentifier"
value={state.projectIdentifier}
onChange={onChange}
disabled
/>
</div>
<div className="form-group">
<textarea
className="form-control form-control-lg"
placeholder="Project Description"
name="description"
onChange={onChange}
value={state.description}
/>
</div>
<h6>Start Date</h6>
<div className="form-group">
<input
type="date"
className="form-control form-control-lg"
name="startDate"
value={state.startDate}
onChange={onChange}
/>
</div>
<h6>Estimated End Date</h6>
<div className="form-group">
<input
type="date"
className="form-control form-control-lg"
name="endDate"
value={state.endDate}
onChange={onChange}
/>
</div>
<input type="submit" className="btn btn-primary btn-block mt-4" />
</form>
</div>
</div>
</div>
</div>
);
};
Adding Redux
While you could still use the connect Higher Order Component from react-redux it's more common now to use the useDispatch and useSelector hooks.
import { useDispatch, useSelector } from 'react-redux';
const UpdateProject = ({ createProject, getProject, project }) => {
...
const dispatch = useDispatch();
const project = useSelector(state => state.project.project);
...
useEffect(() => {
dispatch(getProject(id, navigate)); // fetch project when id updates
}, [id]);
...
const onSubmit = (e) => {
e.preventDefault();
const updateProject = {
...state
};
dispatch(createProject(updateProject, navigate));
};
...

How to display the state on the same page when clicked Submit button in react

I have made a form in react which takes input from the user and stores it in the state. Now, I want to display the state values when the user clicks Submit button in an input field just below the submit button in React.
Im new to react.
You have to make an object (E.g. Credentials) and when someone clicks the button, credential takes the props of the state like this:
App.js
//import code....
import Form from './Form.js'
//class app code.....
//in the render method:
render() {
return (
<Form />
)
}
Form.js
// import code ....
state = {
firstName: '', // or what you want
lastName: '', // or what you want
email: '', // or what you want
send: false,
}
//handleChange function
const handleChange = (event) => {
const {name, value} = event.target
this.setState({
[name]: value
})
}
//handleClick function
const handleClick = () => {
this.setState({send: true})
}
In the Render method
render() {
return (
<div>
<input name='firstName' onChange={handleChange} />
<input name='lastName' onChange={handleChange} />
<input name='email' onChange={handleChange}/>
<button onClick={handleClick}>Send</button>
{send &&
<Credentials
firstName={this.state.firstName}
lastName={this.state.lastName}
email={this.state.email}
/>
}
</div>
)
}
export default Form // or your file's name
In the Credential.js
//import code...
const Credentials = ({firstName, lastName, email}) => {
return (
<h2>firstName is: {firstName}</h2>
<h4>lastName is: {lastName}</h4>
<p>email is: {email}</p>
)
}
export default Credentials
In React you can use 'useState' to initiate a number or any kind of input. Then set that number when the user clicks on a button.
import React, { useState } from "react";
function App() {
const [number, setNumber] = useState();
let typedNumber = 0;
const btnHandler = (e) => {
e.preventDefault();
setNumber(typedNumber);
};
return (
<div>
<form onSubmit={btnHandler}>
<input type="text" onChange={(e) => (typedNumber = e.target.value)} />
<input type="submit" />
</form>
<p>{number}</p>
</div>
);
}
export default App;

Simple form transmission with react & axios

I have a simple form rendering with reactjs and I want to pass a param from the form to complete a route to a test endpoint.
Here is the endpoint: https://jsonplaceholder.typicode.com/comments?postId=1
Here is the component:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import axios from 'axios'
import MenuCombo from './menucombo'
const heading = "Enter a price cap here for recommendations"
class App extends Component {
handleSubmit = (e) => {
e.preventDefault()
axios.get('https://jsonplaceholder.typicode.com/comments?postId=PriceCap')
.then(response =>{
console.log("FOUND", response)
})
.catch(err => {
console.log("NOT FOUND",err)
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">{heading}</h1>
</header>
<div>
<form onSubmit={this.handleSubmit}>
<label>Enter a price</label>
<input name = 'PriceCap'
type = 'number'
min = '1'
max ='20'/>
<button>Generate Suggestions</button>
</form>
</div>
</div>
);
}
}
export default App;
As you can see I cam passing the form element with the name PriceCap ideally the user would set this to 1 to log the data. And if it is set to any other value than 1 it logs an error. But I can't seem to get the parameter to pass properly.
I feel like this would be easier with POST but I also feel like POST is overkill given that I am only sending one param.
Set a reference to your input by
<input
name = 'PriceCap'
ref = {node => {this.input = node}}
type = 'number'
min = '1'
max ='20'
/>
Then you can access the value in your submit handler by
handleSubmit = event => {
let PriceCap = this.input.value;
axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${PriceCap}`)
.then(...).catch(...)
}
You want something like this:
import React from "react";
import ReactDOM from "react-dom";
import axios from "axios";
class App extends React.Component {
state = {
val: ""
};
handleSubmit = e => {
e.preventDefault();
axios
.get(
`https://jsonplaceholder.typicode.com/comments?postId=${this.state.val}`
)
.then(response => {
console.log("FOUND", response);
})
.catch(err => {
console.log("NOT FOUND", err);
});
};
render() {
return (
<div className="App">
<div>
<form onSubmit={this.handleSubmit}>
<label>Enter a price</label>
<input
name="PriceCap"
type="number"
min="1"
max="20"
onChange={e => this.setState({ val: e.target.value })}
/>
<button>Generate Suggestions</button>
</form>
</div>
</div>
);
}
}
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working example here.
Here, you store the input value in state, and then use that in your get() call.
Notice we added the state, and also an onChange in the input.

Categories

Resources