I am trying to do a enzyme/jest unit test for a component. I need to simulate a change event of a specific child component (as there are two of them).
const wrapper = shallow(<CreateAccount />)
wrapper.find({ name: 'username' }).simulate('change', { target: { value: 'Username' } })
wrapper.find({ password: 'password' }).simulate('change', { target: { value: 'Password' } })
const state = wrapper.instance().state
expect(state).toEqual({ username: 'Username', password: 'Password' })
But this is not the correct way to find both Input components...
This is how my render() function of my component looks like:
render () {
return (
<Form onSubmit={this._onSubmit.bind(this)}>
<Input
value={this.state.description}
onChange={this._onChange.bind(this)}
name='username'
type='text'
placeholder='Username'
/>
<Input
value={this.state.url}
onChange={this._onChange.bind(this)}
name='password'
type='password'
placeholder='Password'
/>
<Button type='submit'>
Submit
</Button>
</Form>
)
In general find() returns an array so you have to use at(index) or first() to access specific element:
http://airbnb.io/enzyme/docs/api/ShallowWrapper/at.html
http://airbnb.io/enzyme/docs/api/ShallowWrapper/first.html
In your case you can also import Input component and find them like this:
import Input from 'input-component-path'
...
wrapper.find(Input).at(0).simulate('change', { target: { value: 'Username' } })
wrapper.find(Input).at(1).simulate('change', { target: { value: 'Password' } })
Related
I have seven different input fields and updating the state with the entered value. After that, I am concatenating all the state values and updating the contractNum state but it is not being updated correctly. It is missing the first state (this.state.contact.sys) value. I am not sure how to get the right concatenated value. Any help is much appreciated.
export default class test extends Component {
state = {
contact: {
sys: '',
co: '',
lgr: '',
mgr: '',
sub: '',
serial: '',
num: ''
},
contractNum: ''
};
test = testValue => {
this.setState({
contractNum: testValue
});
};
handleChangeFor = propertyName => event => {
const { contact } = this.state;
const newContact = {
...contact,
[propertyName]: event.target.value
};
this.setState({ contact: newContact });
let newValue =
contact.sub +
contact.co +
contact.mgr +
contact.lgr +
contact.sub +
contact.serial +
contact.num;
this.test(newValue);
};
render() {
return (
<div className="wrapper">
<div className="container">
<form>
<input
type="text"
onChange={this.handleChangeFor('sys')}
value={this.state.contact.sys}
maxLength={2}
/>
<input
type="text"
onChange={this.handleChangeFor('co')}
value={this.state.contact.co}
maxLength={1}
/>
<input
type="text"
onChange={this.handleChangeFor('mgr')}
value={this.state.contact.mgr}
maxLength={1}
/>
<input
type="text"
onChange={this.handleChangeFor('lgr')}
value={this.state.contact.lgr}
maxLength={1}
/>
<input
type="text"
onChange={this.handleChangeFor('serial')}
value={this.state.contact.serial}
maxLength={6}
/>
<input
type="text"
onChange={this.handleChangeFor('num')}
value={this.state.contact.num}
maxLength={2}
/>
<input
type="text"
onChange={this.handleChangeFor('sub')}
value={this.state.contact.sub}
maxLength={1}
/>
</form>
</div>
</div>
);
}
}
You used contact.sub instead of contact.sys when setting newValue.
Getting this error when trying to add input from the web-application to the database. Struggeling to understad what the problem is. The error come when the button to insert to the database is used. Here's some of the code:
import React, { Component } from 'react';
import ProductService from './ProductService';
class Input extends Component {
constructor(props) {
super(props);
this.onChangeName = this.onChangeName.bind(this);
this.onChangeQuantity = this.onChangeQuantity.bind(this);
this.onChangePrice = this.onChangePrice.bind(this);
this.state = {
id: -1,
name: null,
quantity: null,
price: null
};
}
onChangeName(e) {
this.setState({
name: e.target.value
});
}
onChangeQuantity(e) {
this.setState({
quantity: e.target.value
});
}
onChangePrice(e) {
this.setState({
price: e.target.value
});
}
saveProduct() {
var data = {
name: this.state.name, //this is the line witch get the error
quantity: this.state.quantity,
price: this.state.price
};
ProductService.addProduct(data)
.then(response => {
this.setState({
id: response.data.id,
name: response.data.name,
quantity: response.data.quantity,
price: response.data.price
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
newProduct() {
this.setState({
name: "",
quantity: null,
price: null
});
}
render() {
return (
<div id="parent">
<form>
<p>Enter name:</p>
<input
type="text"
id="name"
required
value={this.state.name}
onChange={this.onChangeName}
name="name"
/>
</form>
<form>
<p>Enter price:</p>
<input
type="text"
id="price"
required
value={this.state.price}
onChange={this.onChangePrice}
name="price"
/>
</form>
<form>
<p>Enter quantity:</p>
<input
type="text"
id="quantity"
required
value={this.state.quantity}
onChange={this.onChangeQuantity}
name="quantity"
/>
</form>
<button onClick={this.saveProduct}>Enter in database</button>
</div>
);
}
}
export default Input;
When i change to for example:
saveProduct() {
var data = {
name: "test",
quantity: 123,
price: 456
};
Everything works. Ive tried diffrent things, but to me the error dont quite make sense. Anyone see where my mistake(s) are? Thank you in advance.
The problem is regarding binding this. As the ReactJS docs state:
You have to be careful about the meaning of this in JSX callbacks. In
JavaScript, class methods are not bound by default. If you forget to
bind this.handleClick and pass it to onClick, this will be undefined
when the function is actually called.
In the constructor just add following line and your code will work:
constructor(props) {
...
// This binding is necessary to make `this` work in the callback
this.saveProduct = this.saveProduct.bind(this);
}
Do this for all event handlers. Also read the doc: https://reactjs.org/docs/handling-events.html
The properties of 'this' are not always the same.
instead of using this.property. Make a global object (_this) for example and save your data in it, this way you can have your variables and properties in all functions.
for example:
import React, { Component } from 'react';
import ProductService from './ProductService';
class Input extends Component {
let _this = {};
constructor(props) {
super(props);
_this.onChangeName = _this.onChangeName.bind(_this);
_this.onChangeQuantity = _this.onChangeQuantity.bind(_this);
_this.onChangePrice = _this.onChangePrice.bind(_this);
_this.state = {
id: -1,
name: null,
quantity: null,
price: null
};
}
onChangeName(e) {
_this.setState({
name: e.target.value
});
}
onChangeQuantity(e) {
_this.setState({
quantity: e.target.value
});
}
onChangePrice(e) {
_this.setState({
price: e.target.value
});
}
saveProduct() {
var data = {
name: _this.state.name, //this is the line witch get the error
quantity: _this.state.quantity,
price: _this.state.price
};
ProductService.addProduct(data)
.then(response => {
_this.setState({
id: response.data.id,
name: response.data.name,
quantity: response.data.quantity,
price: response.data.price
});
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
newProduct() {
_this.setState({
name: "",
quantity: null,
price: null
});
}
render() {
return (
<div id="parent">
<form>
<p>Enter name:</p>
<input
type="text"
id="name"
required
value={_this.state.name}
onChange={_this.onChangeName}
name="name"
/>
</form>
<form>
<p>Enter price:</p>
<input
type="text"
id="price"
required
value={_this.state.price}
onChange={_this.onChangePrice}
name="price"
/>
</form>
<form>
<p>Enter quantity:</p>
<input
type="text"
id="quantity"
required
value={_this.state.quantity}
onChange={_this.onChangeQuantity}
name="quantity"
/>
</form>
<button onClick={_this.saveProduct}>Enter in database</button>
</div>
);
}
}
export default Input;
You forgot to put a bind for saveProduct().
Checkout my minimal working sample: https://codesandbox.io/s/laughing-wright-jnptu?file=/src/App.js
I agree with #tretechs arrow function don't have their own 'this' so they use this context which is the closest non-arrow parent function.
That is why we need to bind this and pass it on to the function. It is good practice to bind callback functions in the contructor function
constructor(props) {
super(props);
this.onChangeName = this.onChangeName.bind(this);
this.onChangeQuantity = this.onChangeQuantity.bind(this);
this.onChangePrice = this.onChangePrice.bind(this);
// similarly bind saveProduct
this.saveProduct = this.saveProduct.bind(this); }
I just started working with React and JSON and require some help. There is a textarea field in which a user enters some data. How to read row-wise the entered text as an array into a JSON variable of the request? Any assistance would be greatly appreciated.
The result I want is
{
id: 3,
name: 'Monika',
birthDay: '1999/01/01',
countryDTO: 'USA',
films: [
'Leon:The Professional',
'Star wars',
'Django Unchained',
],
} ```
My code:
import React from 'react';
import { Form, FormGroup, Label } from 'reactstrap';
import '../app.css';
export class EditActor extends React.Component {
state = {
id: '',
name: '',
birthDay: '',
countryDTO: '',
films: [],
}
componentDidMount() {
if (this.props.actor) {
const { name, birthDay, countryDTO, films } = this.props.actor
this.setState({ name, birthDay, countryDTO, films });
}
}
submitNew = e => {
alert("Actor added"),
e.preventDefault();
fetch('api/Actors', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: this.state.name,
birthDay: this.state.birthDay,
countryDTO: {
title: this.state.countryDTO
},
films: [{ title: this.state.films }]
})
})
.then(() => {
this.props.toggle();
})
.catch(err => console.log(err));
this.setState({
id: '',
name: '',
birthDay: '',
countryDTO: '',
films: ''
});
}
onChange = e => {
this.setState({ [e.target.name]: e.target.value })
}
render() {
return <div>
<table>
<tr>
<td colspan="2">
<h3> <b>Add actor</b></h3>
<FormGroup>
<Label for="id">Id: </Label>
<input type="text" name="id" onChange={this.onChange} value={this.state.id} /><p />
<Label for="name">Name:</Label>
<input type="text" name="name" onChange={this.onChange} value={this.state.name} /><p />
<Label for="birthDay">Birth day:</Label>
<input type="text" name="birthDay" onChange={this.onChange} value={this.state.birthDay} placeholder="1990/12/31" /><p />
<Label for="country">Country:</Label>
<input type="text" name="countryDTO" onChange={this.onChange} value={this.state.countryDTO} /><p />
<Label for="Films">Films:</Label>
<textarea name="films" value={this.state.films} onChange={this.onChange} /><p />
</FormGroup>
</td>
</tr>
<tr>
<td>
<Form onSubmit={this.submitNew}>
<button class="editButtn">Enter</button>
</Form>
</td>
</tr>
</table >
</div>;
}
}
export default EditActor;
If you change the below code it will work automatically.
State declaration
this.state = {
name: 'React',
films:["Palash","Kanti"]
};
Change in onechange function
onChange = e => {
console.log("values: ", e.target.value)
this.setState({ [e.target.name]: e.target.value.split(",") })
}
change in textarea
<textarea name="films" value={this.state.films.map(r=>r).join(",")} onChange={this.onChange} />
Code is here:
https://stackblitz.com/edit/react-3hrkme
You have to close textarea tag and the following code is :
<textarea name="films" value={this.state.films} onChange={this.onChange} >{this.state.films}</textarea>
My understanding of your problem is that you would like to have each line in the text area dynamically added as an entry in the films array. This can be achieved as follows:
import React, { Component } from "react";
export default class textAreaRowsInState extends Component {
constructor(props) {
super(props);
this.state = {
currentTextareaValue: "",
films: []
};
}
handleChange = e => {
const { films } = this.state;
const text = e.target.value;
if (e.key === "Enter") {
// Get last line of textarea and push into films array
const lastEl = text.split("\n").slice(-1)[0];
films.push(lastEl);
this.setState({ films });
} else {
this.setState({ currentTextareaValue: text });
}
};
render() {
const { currentTextareaValue } = this.state;
return (
<textarea
defaultValue={currentTextareaValue}
onKeyPress={this.handleChange}
/>
);
}
}
Keep in mind that this method is not perfect. For example, it will fail if you add a new line anywhere other than at the end of the textarea. You can view this solution in action here:
https://codesandbox.io/s/infallible-cdn-135du?fontsize=14&hidenavigation=1&theme=dark
change textarea() tag
to
<textarea name="films" value={this.state.films} onChange={this.onChange} >{this.state.films}</textarea>
You can use split() :
films: {this.state.films.split(",")}
I am sending my state as a stringified variable from a form to a POST request through a lamda server which then parses it and sends it to sendgrid which there I am using send grids templating feature. Which requires me to have json Formatted like this in order to loop over one particular part (multiple checkboxes) which all should have the same key but a different value, set by a reason="Weight Loss" in the form. Here is how the eventual json needs to be formed.
{
"name" :"anders",
"message" : "winfsdafasfdsfsadfsadnipeg",
"package" : "silver",
"email" : "email#email.com",
"subject" : "fdsafas",
"data":{
"reasonArray":[
{
"reason":"weightLoss"
},
{
"reason":"Sport"
}
]
}
}
Then I can do some magic and loop over the reason's that were checked in the checkbox
<ol>
{{#each data.reasonArray}}
<li>{{this.reason}} </li>
{{/each}}
</ol>
Now I had it working if I left the state with single key value pairs and don't have the data portion.
Here is what my initial state looked like working.
const [formState, setFormState] = React.useState({
name: "",
package: `${data.datoCmsPricing.title}`,
email: "",
subject: "",
weightLoss:"",
strength:"",
sport:"",
message: "",
})
I then had the following onChange event that set the state with the name of the input field as the key and the value or checked state as the value. Seen here
const onChange = (e) => {
if (e.target.type === 'checkbox' && !e.target.checked) {
setFormState({...formState, [e.target.name]: e.target.checked});
} else {
setFormState({...formState, [e.target.name]: e.target.value });
}
}
and here is my form
<form onSubmit={submitForm}>
{/* <input type="text" name="package" value={data.datoCmsPricing.title} /> */}
<label>
Name
<input
type="text"
name="name"
value={formState.name}
onChange={onChange}
/>
</label>
<label>
Email
<input
type="email"
name="email"
value={formState.email}
onChange={onChange}
/>
</label>
<label>
Subject
<input
type="text"
name="subject"
value={formState.subject}
onChange={onChange}
/>
</label>
<div>
<h3>Reasons for wanting to train</h3>
<label>
Weight Loss
<input
type="checkbox"
name="weightLoss"
checked={formState.weightLoss}
onChange={onChange}
/>
</label>
<label>
Strength
<input
type="checkbox"
name="strength"
checked={formState.strength}
onChange={onChange}
/>
</label>
<label>
Sport
<input
type="checkbox"
name="sport"
checked={formState.sport}
onChange={onChange}
/>
</label>
</div>
<label>
message
<textarea
name="message"
value={formState.message}
onChange={onChange}
/>
</label>
<button type="submit">Submit</button>
</form>
I then send it off to my lamdba function
const response = await fetch("/.netlify/functions/sendmail", {
method: "POST",
body: JSON.stringify(formState),
})
Now my state looks like the following in json after being sent to lamdbda function and being parsed
{
name: 'Anders',
package: 'silver',
email: 'email#email.com',
subject: 'fdsafa',
weightLoss: 'on',
strength: 'on',
sport: 'on',
message: 'fdsafasf'
}
Now I want to have my initial state to look like the format that sendgird wants it in, so this is what I attempted with my state setup.
const [formState, setFormState] = React.useState({
name: "",
package: `${data.datoCmsPricing.title}`,
email: "",
subject: "",
weightLoss:"",
strength:"",
sport:"",
message: "",
data:{
reasonArray:[
{
reason:""
},
{
reason:""
}
]
}
})
Then I tried to update the onChange event for the checked values with the following, I also update my form so it grabs a user friendly name for the reason. Seen below this code
const onChange = (e) => {
if (e.target.type === 'checkbox' && !e.target.checked) {
setFormState({...formState, data:{ reasonArray:[ { reason:e.target.reason}, ]}});
}
...
}
Form Changes
...
<label>
Weight Loss
<input
type="checkbox"
name="weightLoss"
reason="weightLoss"
checked={formState.weightLoss}
onChange={onChange}
/>
</label>
<label>
Strength
<input
type="checkbox"
name="strength"
reason="strength"
checked={formState.strength}
onChange={onChange}
/>
</label>
<label>
Sport
<input
type="checkbox"
name="sport"
reason="sport"
checked={formState.sport}
onChange={onChange}
/>
</label>
...
The resulting Json I get after the Post request is this, with my attempt. It does not update the data part. So the resulting Json is in the right format, but it doesn't have the reason's attached. Thanks ahead of time for any help.
{
"name":"Anders",
"package":"Silver",
"email":"email#email.com",
"subject":"fdsaf",
"weightLoss":"on",
"strength":"on",
"sport":"on",
"message":"fdsafas",
"data":{
"reasonArray":[
{
"reason":""
},
{
"reason":""
}
]
}
}
Attempting Rabi's answer
...
const prepareDataForApi = (formData) => {
const newFormData = Object.assign({}, formData); // optional if passed cloned copy of formData object or you can also use lodash cloneDeep
newFormData.data = {
reasonArray:[]
};
Object.keys(newFormData.reasons).forEach(key => {
if(newFormData.reasons[key]){
newFormData.data.reasonArray.push({reason: key})
}
});
delete newFormData.reasons;
return newFormData;
}
const submitForm = async (e) => {
e.preventDefault();
setForm(false);
// const newFormData = prepareDataForApi(formData);
const newFormData = prepareDataForApi(formState);
console.log(newFormData);
...
1.Keep your initial state like this :
{
"name":"Anders",
"package":"Silver",
"email":"email#email.com",
"subject":"fdsaf",
"message":"fdsafas",
"reasons": {
"weightLoss": true,
"strength": true,
"sport": true,
}
}
Modify onChange():
const onChange = (e) => {
if (e.target.type === 'checkbox') {
const changedReason = e.target.getAttribute('name');
setFormState({...formState, reasons:{...formState.reasons, [changedReason]: !formState.reasons[changedReason]}});
}
...
}
Change form's onSubmit():
Before calling api , call converter function which will convert formState to JSON format required by your lambda function
const prepareDataForApi = (formData) => {
const newFormData = Object.assign({}, formData); // optional if passed cloned copy of formData object or you can also use lodash cloneDeep
newFormData.data = {
reasonArray:[]
};
Object.keys(newFormData.reasons).forEach(key => {
if(newFormData.reasons[key]){
newFormData.data.reasonArray.push({reason: key})
}
});
delete newFormData.reasons;
return newFormData;
}
It looks like your new onChange is not replacing all of your nested values when you update a key. Try this instead:
setFormState({
...formState,
data:{
...formState.data, // keep keys from previous data object (not necessary if it only contains the key you are specifying though)
reasonArray:[
...formState.data.reasonArray, // keep previous entries from reasonArray
{ reason:e.target.reason},
]
}
});
An alternative would be to use an effect.
const [formState, setFormState] = React.useState({...}):
// Runs every time weightLoss is changed
React.useEffect(() => {
let newReasonArray
if (formState.weightLoss) {
newReasonArray = [...formState.reasonArray]; // So we dont mutate state
newReasonArray.push({reason: 'weightLoss'});
} else {
// If you want to remove it from the array if its unchecked
newReasonArray = [...formState.reasonArray];
newReasonArray.filter((reason) => (reason.reason != 'weightLoss'));
}
console.log(newReasonArray) // Test if it is updated correctly
// Set the state with the new array
setFormState({...formState, data: { reasonArray: newReasonArray }});
}, [formState.weightLoss]);
What I have here is a function where I call the codigo, and the nombre, in the DB
table registrations. What I want to achieve is that the digital code that is like an autocomplete to fill in the name when you select the code.
enter image description here
class Matriculas extends Component {
state = {
status: "initial",
data: []
}
componentDidMount = () => {
this. getInfo()
}
getInfo= async () => {
try {
const response = await getAll('matriculas')
console.log(response.data)
this.setState({
status: "done",
data: response.data
});
} catch (error) {
this.setState({
status: "error"
});
}
};
render() {
const data = [...this.state.data];
return (
<Container>
<RowContainer margin="1px" >
<ColumnContainer margin="10px">
<h3>Info</h3>
<label>Codigo</label>
<Input
width='150px'
type="text"
placeholder="Digite el codigo"
value={data.codigo } ref="codigo" />
<label>Nombre</label>
<Input
width='150px'
type="text"
placeholder="Nombre completo"
value={data.nombre} />
</ColumnContainer>
</RowContainer>
</Container>
)
}
};
export default Matriculas;
What you most likely want to use is react-select
You can pass options to the select (which would be your names) and it will return values that match whatever you type in the search bar.
import Select from 'react-select'
const options = [
{ value: 'mike', label: 'Mike' },
{ value: 'john', label: 'John' },
{ value: 'vanessa', label: 'Vanessa' }
]
const MyComponent = () => (
<Select options={options} />
)
So you can take that example, and the examples in the link, and put it in your code:
import Select from 'react-select'
<Container>
<RowContainer margin="1px" >
<ColumnContainer margin="10px">
<h3>Info</h3>
<label>Codigo</label>
<Input
width='150px'
type="text"
placeholder="Digite el codigo"
value={data.codigo } ref="codigo" />
<label>Nombre</label>
<Select
value={this.state.nameValue}
onChange={event => {this.setState({nameValue: e.value})}
options={options} />
</ColumnContainer>
</RowContainer>
</Container>
When using onChage, it returns an event, which has the value of the selected name. You can use that to set the state's nameValue, and then use that name value in the rest of your component as well
Once you get this up and running, it also worth looking at the async select, which allows you to give an async function that returns values (your getInfo function, for example)
-- edit --
If you want to define the onChange event elsewhere, it would look like this:
handleChange = event => {
// event.value will be the value of the select
this.setState({optionSelected: event.value});
}
and then in your onChange, tell it that is the function you want but do not invoke it (don't write it with parentheses):
<Select
value={this.state.optionSelected}
onChange={this.handleChange}
options={options} />