Im using Material-Ui & Typescript.
I`m testing the 'onChange' input component and it does not working with value.
Form component wraps the input component and it holds the input state.
const Form: React.FC = () => {
const [state, setState] = useState({ name: '', email: '' });
const { name, email } = state;
const onChange = ({ target: { name, value }}:IOnChange) => setState({ ...state, [name]: value });
const onSubmit = (e: any) => {
e.preventDefault();
};
return (
<Grid xs={12} container item component='form' onSubmit={onSubmit} noValidate>
<Input
label={'Name'}
name='name'
value={name}
onChange={onChange}
type='text'
/>
<Input
label={'Email'}
name='email'
value={email}
onChange={onChange}
type='email'
/>
</Grid>
);
};
export default Form;
this is the Input component:
const Input: React.FC<IProps> = ({ label, name, value, onChange, type }) => (
<TextField
label={label}
name={name}
value={value}
onChange={e => onChange(e)}
type={type}
variant="outlined"
inputProps={{
"data-testid": `input-${name}`,
}}
/>
);
interface IProps {
label: string
name: string
value: string
onChange: (e:React.InputHTMLAttributes<HTMLInputElement> & { target: { name: string, value: string }}) => void
type: string
};
export default Input;
And this is my test:
import { cleanup, fireEvent, render } from "#testing-library/react";
const props = { label: 'label', name: 'name', value: '', onChange: jest.fn(), type:'text' };
const { name, onChange } = props
describe("<Input />", () => {
describe("onChange", () => {
test("function call", () => {
const { getByTestId } = render(<Input {...props} />);
const e = expect.objectContaining({ target: expect.objectContaining({ name, value: 'value here' })});
fireEvent.change(getByTestId('input-name'), { target: { value: 'value here', name }});
this works
expect(onChange).toHaveBeenCalledTimes(1);
here is the error:
expect(onChange).toHaveBeenCalledWith(e);
Expected: ObjectContaining {"target": ObjectContaining {"name": "name", "value": "value here"}}
Received: {"_reactName": "onChange", "_targetInst": null, "bubbles": true, "cancelable": false, "currentTarget": null, "defaultPrevented": false, "eventPhase": 3, "isDefaultPrevented": [Function functionThatReturnsFalse], "isPropagationStopped": [Function functionThatReturnsFalse], "isTrusted": false, "nativeEvent": {"isTrusted": false},
"target": <input aria-invalid="false" class="MuiInputBase-input MuiOutlinedInput-input" data-testid="input-name" id="outlined-label" name="name" type="text" value="" />, "timeStamp": 1624028430742, "type": "change"}
});
});
});
The value on input is empty.
I tried whit userEvent and still not working.
How can i test onChange to be called with the new value?
Thank you..
Related
suppose i have quantity, available_qty, and sell_quantity ,I want if my quantity increase my sell_quantity will increase and available_qty decrease ,if my quantity will decrease my sell_quantity will decrease and available_qty will increase..
import React, { useState } from 'react'
import BackButton from '../../components/UI/BackButton'
import Input from '../../components/UI/Input'
import Button from '../../components/UI/Button'
import {
useUpdateOrderMutation,
useGetOrderByIdQuery,
useGetAllProductQuery,
useGetAllOrderQuery,
} from '../../Redux/Services/services'
import { useParams } from 'react-router-dom'
import { useForm, Controller } from 'react-hook-form'
import { useNavigate } from 'react-router-dom'
const UpdateOrder = () => {
const [noData, setNoData] = useState(false)
const {
control,
handleSubmit,
reset,
formState,
setValue,
formState: { errors, isSubmitSuccessful },
} = useForm({
// resolver: yupResolver(createProductSchema),
defaultValues: {
// Category_name: '',
// subCat_name: '',
selling_price: '',
quantity: '',
available_qty: '',
sell_quantity: '',
// unit: '',
total_cost: '',
},
})
React.useEffect(() => {
if (formState.isSubmitSuccessful) {
reset({ quantity: '' })
}
}, [formState, reset])
const params = useParams()
const order_id = params.id
const [updateOrder, responseInfoOrderUpdate] = useUpdateOrderMutation()
const responseInfoOrderById = useGetOrderByIdQuery(order_id)
const responseInfoProduct = useGetAllProductQuery()
const responseInfoAllOrder = useGetAllOrderQuery()
const orderDetails = responseInfoAllOrder.data
if (responseInfoOrderUpdate.isLoading) return <div>Loading....</div>
if (responseInfoOrderUpdate.isError)
return <h1>An error occured {responseInfoOrderUpdate.error.error}</h1>
if (responseInfoOrderById.isLoading) return <div>Loading....</div>
if (responseInfoOrderById.isError)
return <h1>An error occured {responseInfoOrderById.error.error}</h1>
if (responseInfoProduct.isLoading) return <div>Loading....</div>
if (responseInfoProduct.isError)
return <h1>An error occured {responseInfoProduct.error.error}</h1>
// const totalProductQty = responseInfoOrderById.data
// ?.filter((sellQuantity) => sellQuantity.subCat_id === subCat_id)
// .map((additionSell, i) => additionSell.quantity)
// .reduce((qty, purchase) => qty + purchase, 0)
var qty_data = responseInfoOrderById.data?.quantity
var sellingPrice_data = responseInfoOrderById.data?.selling_price
var available_qty = responseInfoOrderById.data?.available_qty
var sell_quantity = responseInfoOrderById.data?.sell_quantity
const totalProductQty =
parseInt(responseInfoOrderById.data?.available_qty) +
parseInt(responseInfoOrderById.data?.sell_quantity)
console.log('totalProductQty', totalProductQty)
console.log('sell_quantity', sell_quantity)
const sell_qty =
parseInt(totalProductQty) -
parseInt(responseInfoOrderById.data?.available_qty)
// const available_qty =
// parseInt(totalProductQty) -
// parseInt(responseInfoOrderById.data?.sell_quantity)
// console.log('sell_qty', sell_qty)
// console.log('available_qty', available_qty)
// const total_sell = parseInt(sellQty) + parseInt(quantity)
const AvailableProduct = parseInt(totalProductQty) - parseInt(qty_data)
const onSubmit = (data) => {
const orderUpdateData = {
id: order_id,
quantity: data.quantity,
available_qty: available_qty,
selling_price: data.selling_price,
total_cost: data.total_cost,
sell_quantity: sell_quantity,
}
updateOrder(orderUpdateData)
console.log(orderUpdateData, 'orderUpdateData')
}
var total_cost = parseInt(sellingPrice_data) * parseInt(qty_data)
console.log('supplier_name', sellingPrice_data, qty_data, total_cost)
setValue('quantity', qty_data)
setValue('selling_price', sellingPrice_data)
setValue('total_cost', total_cost)
setValue('available_qty', available_qty)
setValue('sell_quantity', sell_quantity)
return (
<div className="page__wrapper">
<BackButton />
<div className="place__order__wrapper">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="inputs__cover">
<Controller
control={control}
rules={{
required: true,
}}
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Selling price"
type="number"
id="selling_price"
readOnly={true}
onChange={onChange}
onBlur={onBlur}
value={value}
/>
)}
name="selling_price"
/>
{errors.selling_price && (
<span className="field_error">This is required.</span>
)}
<Controller
control={control}
rules={{
required: true,
}}
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Available qty"
type="number"
id="available_qty"
readOnly={true}
onChange={onChange}
onBlur={onBlur}
value={value}
/>
)}
name="available_qty"
/>
{errors.available_qty && (
<span className="field_error">This is required.</span>
)}
<Controller
control={control}
rules={{
required: true,
}}
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="sell Total qty"
type="number"
id="sell_quantity"
readOnly={true}
onChange={onChange}
onBlur={onBlur}
value={value}
/>
)}
name="sell_quantity"
/>
{errors.sell_quantity && (
<span className="field_error">This is required.</span>
)}
<div className="quantity__input">
<Controller
control={control}
rules={{
required: true,
}}
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Quantity"
type="number"
id="quantity"
onChange={onChange}
onBlur={onBlur}
value={value}
/>
)}
name="quantity"
/>
</div>
<Controller
control={control}
rules={
{
// required: true,
}
}
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Total cost"
type="number"
id="total_cost"
onChange={onChange}
onBlur={onBlur}
value={noData ? total_cost.toString() : value}
readOnly={true}
/>
)}
name="total_cost"
/>
{errors.total_cost && (
<span className="field_error">This is required.</span>
)}
</div>
<div className="button__wrapper">
<Button className="add__product__button" type="submit">
Add quantity
</Button>
</div>
<div className="button__wrapper">
<Button className="add__product__button" type="submit">
update quantity
</Button>
</div>
</form>
</div>
</div>
)
}
export default UpdateOrder
model is
const mongoose = require("mongoose");
const OrderSchema = new mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId, //convert id to normal form
customer_id: {
type: String,
required: true,
lowercase: true,
},
category_id: {
type: String,
required: true,
lowercase: true,
},
subCat_id: {
type: String,
required: true,
lowercase: true,
},
selling_price: {
type: Number,
required: true,
},
quantity: {
type: Number,
required: true,
},
sell_quantity: {
type: Number,
default: 0,
},
available_qty: {
type: Number,
default: 0,
},
unit: {
type: String,
required: true,
enum: {
values: ["kg", "litre", "pcs", "bag"],
message: "unit must be kg/litre/pcs/bag",
},
},
total_cost: {
type: Number,
required: true,
},
},
{ timestamps: true }
);
module.exports = mongoose.model("order", OrderSchema);
query is
const router = require("express").Router();
const { default: mongoose } = require("mongoose");
const Order = require("../models/Order");
//create order
router.post("/order-save", async (req, res) => {
try {
const customer_id = req.body.customer_id;
const category_id = req.body.category_id;
const subCat_id = req.body.subCat_id;
const selling_price = req.body.selling_price;
const quantity = req.body.quantity;
const sell_quantity = req.body.sell_quantity;
const available_qty = req.body.available_qty;
const unit = req.body.unit;
const total_cost = req.body.total_cost;
const oder_data = new Order({
_id: mongoose.Types.ObjectId(),
customer_id: customer_id,
category_id: category_id,
subCat_id: subCat_id,
selling_price: selling_price,
quantity: quantity,
sell_quantity: sell_quantity,
available_qty: available_qty,
unit: unit,
total_cost: total_cost,
});
const orderData = await oder_data.save();
message = "Order Create Successfully..";
return res.status(200).json(orderData);
} catch (err) {
return res.status(200).json(err);
}
});
//get all order
router.get("/get-order", async (req, res) => {
try {
const order = await Order.find();
return res.status(200).json(order);
} catch (err) {
return res.status(200).json(err);
}
});
//get order by id
router.get("/get-order/:id", async (req, res) => {
try {
const orderById = await Order.findById(req.params.id);
return res.status(200).json(orderById);
} catch (error) {
return res.status(500).json(error);
}
});
//get filter customer by id
router.get("/filter-order/:id", async (req, res) => {
try {
const order = await Order.find({ $and: [{ customer_id: req.params.id }] });
return res.status(200).json(order);
} catch (err) {
return res.status(500).json(err);
}
});
//Update Order
router.patch("/update-order/:id", async (req, res) => {
try {
await Order.findByIdAndUpdate(req.params.id, req.body, {
new: true,
});
message = "Order has been updated...";
return res.status(200).json({ message });
} catch (err) {
return res.status(200).json(err);
}
});
module.exports = router;
I am sending my useHistory (react-router-dom) variable as a parameter to the employeee.service in which I use the "history.push" method with a state and a pathname. unfortunetaly I cannot seem to find out what the correct type would be. I used:
History<unknown>
History<Location>
but both do not seem to understand the state that I pass. Does anyone know how to strongly type this? any help much appreciated!
The create method in the service:
export const createEmployee = async (body: IEmployee, history: any) => {
try {
const employeesResponse = await fetch(`http://localhost:3000/employees`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (employeesResponse.status !== 201) {
const response: IHttpResponse = {
status: employeesResponse.status,
error: {message: employeesResponse.statusText},
data: {content: ''}
}
return response
}
const employeeResult: IEmployee[] = await employeesResponse.json();
const response: IHttpResponse = {
status: employeesResponse.status,
error: {message: ''},
data: {content: employeeResult}
}
history.push({
pathname: '/',
state: { detail: 'reload', response: response },
});
} catch (error) {
console.log('error while creating', error);
const response: IHttpResponse = {
status: error.status,
error: {message: error.statusText},
data: {content: ''}
}
return response;
}
}
the component using the service
import React, { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Button, Input, Label } from 'reactstrap';
import FormGroup from 'reactstrap/es/FormGroup';
import { yupResolver } from '#hookform/resolvers/yup';
import * as yup from 'yup';
import Select from 'react-select';
import { useHistory } from 'react-router-dom';
import { gendersList, statusList } from '../models/lists/formLists';
import { IOptionType } from '../models/IOptionType';
import { createEmployee } from '../services/employee.service';
import { IEmployee } from './../models/IEmployee';
export const AddEmployeeForm = () => {
const history = useHistory();
const [gender, setGender] = useState<IOptionType>({ label: gendersList[0].label, value: gendersList[0].value });
const [status, setStatus] = useState<IOptionType>({ label: statusList[0].label, value: statusList[0].value });
const validationSchema = yup.object().shape({
firstname: yup.string().required().min(2),
lastname: yup.string().required().min(2),
email: yup.string().email().required(),
status: yup.object().shape({
label: yup.string(),
value: yup.string(),
}),
gender: yup.object().shape({
label: yup.string(),
value: yup.string(),
}),
});
const {
handleSubmit,
control,
formState: { errors },
register,
} = useForm({
resolver: yupResolver(validationSchema),
});
const handleGenderChange = (option: IOptionType) => {
setGender({ label: option.label, value: option?.value });
};
const handleStatusChange = (option: IOptionType) => {
setStatus({ label: option.label, value: option?.value });
};
const onSubmit = async (data: any) => {
console.log('errors', errors);
const body: IEmployee = {
first_name: data.firstname,
last_name: data.lastname,
email: data.email,
gender: gender.value ? gender.value : '',
status: status.value ? status.value : '',
};
console.log('Data', body);
createEmployee(body, history);
};
return (
<div className="col-12">
<form onSubmit={handleSubmit(onSubmit)}>
<FormGroup>
<Label for="firstname">First name</Label>
{errors.firstname && <p className="text-danger error-message">{errors.firstname.message}</p>}
<Input {...register('firstname')} />
</FormGroup>
<FormGroup>
<Label for="lastname">Last name</Label>
{errors.lastname && <p className="text-danger error-message">{errors.lastname.message}</p>}
<Input {...register('lastname')} />
</FormGroup>
<FormGroup>
<Label for="email">Email</Label>
{errors.email && <p className="text-danger error-message">{errors.email.message}</p>}
<Input {...register('email')} />
</FormGroup>
<FormGroup>
<Label for="gender">Gender</Label>
<Controller
name="gender"
control={control}
render={({ field: { onChange, onBlur, value, ref } }) => (
<Select
options={gendersList}
onChange={(value) => handleGenderChange({ value: value?.value, label: value?.label })}
onBlur={onBlur}
defaultValue={gender}
selected={value}
/>
)}
/>
</FormGroup>
<FormGroup>
<Label for="status">Status</Label>
<Controller
name="status"
control={control}
render={({ field: { onChange, onBlur, value, ref } }) => (
<Select
options={statusList}
onChange={(value) => handleStatusChange({ value: value?.value, label: value?.label })}
onBlur={onBlur}
value={status}
selected={value}
/>
)}
/>
</FormGroup>
<Button type="submit">Submit</Button>
</form>
</div>
);
};
You may want to look at how RouterProps is defined. Maybe helpful.
import { useHistory, RouterProps } from 'react-router-dom';
You can achieve the this like so:
const history = useHistory<{ detail: string, response: IHttpResponse }>();
console.log(history.location.state.detail); // should log 'reload'
You can use the RouterChildContext to access the useHistory type.
const foo = (history: RouterChildContext['router']['history']) => {
history.push('/your-path');
}
This is from the react-router type declaration file:
// This is the type of the context object that will be passed down to all children of
// a `Router` component:
export interface RouterChildContext<Params extends { [K in keyof Params]?: string } = {}> {
router: {
history: H.History;
route: {
location: H.Location;
match: match<Params>;
};
};
}
My workaround is:
import { useHistory, useLocation } from "react-router-dom";
type THistory<T = unknown> = ReturnType<typeof useHistory<T>>
type TLocation<T = unknown> = ReturnType<typeof useLocation<T>>;
const goTo = (history: THistory<{payload: string}>) => {
history.push({
pathname: '/',
state: { payload: 'some data' },
});
}
This is register form, I want to send the value orgTypes>name to orgType of data of same object using onChange of select.
https://codesandbox.io/s/react-typescript-zm1ov?fontsize=14&fbclid=IwAR06ZifrKrDT_JFb0A-d_iu5YaSyuQ9qvLRgqS20JgAcSwLtAyaOFOoj5IQ
When I use onChange of Select, it erases all other data of the inputs.
import React from "react";
import { Select } from "antd";
const { Option } = Select;
const RegisterFinal = () => {
const data = {
orgName: "",
orgRegNo: "",
orgType: "",
orgTypes: [
{ id: "1", name: "Vendor" },
{ id: "2", name: "Supplier" },
{ id: "3", name: "Vendor and Supplier" }
],
errors: {}
};
const [values, setValues] = React.useState(data);
const handleChange = (e: any) => {
e.persist();
setValues((values: any) => ({
...values,
[e.target.name]: e.target.value
}));
};
const handleSubmit = () => {
console.log(values);
};
return (
<React.Fragment>
<input
name="orgName"
onChange={handleChange}
placeholder="Organization Name"
value={values.orgName}
/>
<input
name="orgRegNo"
onChange={handleChange}
placeholder="Registration Number"
value={values.orgRegNo}
/>
<Select //imported from antd
id="select"
defaultValue="Choose"
onChange={(select: any) => {
setValues(select);
console.log(select); //shows Vender/Supplier, I want this value to be sent to orgType above
}}
>
{data.orgTypes.map((option: any) => (
<Option key={option.id} value={option.name}>
{option.name}
</Option>
))}
</Select>
</React.Fragment>
);
};
When I use onChange of Select, it erases all other data of the inputs.
Thank you for your help
setValues function expects to take the values object as an argument. Instead you are passing the select value to it.
Instead, you can do it like this:
const handleSelectChange = (selectValue: any) => {
setValues((values: any) => ({
...values,
orgType: selectValue
}));
}
and use handleSelectChange in the onChange of your select.
Check the solution here: https://codesandbox.io/s/react-typescript-p9bjz
i am trying to generate a form from JSON config. I am parsing the JSON and using map functions to generate material UI TextField components.
But issue is that the generated components do not get rendered, instead the whole JS code appears on screen. Not sure why.
here is my code in 2 files:
FormConfig.js:
"Form1": {
"fields": [
{
uiElement: "TextField",
id: '"standard-name"',
name: "'asdada'",
className: "{classes.textField}",
value: "{this.state.name}",
onChange: '{this.handleChange("name")}',
required: true,
margin: '"normal"'
},
{
uiElement: "TextField",
id: '"standard-uncontrolled"',
name: '"asda"',
label: '"Required"',
className: '"{classes.textField}"',
value: '"asd"',
onChange: "{}",
required: true,
margin: '"normal"'
}
]
},
"OtherForm":
{
"fields": [{}, {}]
}
}
const getForm = formName => {
return FormConfig[formName].fields.map(field => `<${field.uiElement} `+
Object.keys(field).filter(k => k !== 'uiElement')
.map(k => {
return k + "=" + field[k];
})
.join(" ") + `/>`
)
}
export default getForm;
TestForm.js
class TextFields extends React.Component {
state = {
name: 'Cat in the Hat',
age: '',
multiline: 'Controlled',
currency: 'EUR',
};
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
render() {
const { classes } = this.props;
return (
<form className={classes.container} noValidate autoComplete="off">
{
getForm("Form1")
}
<TextField
required
id="standard-required"
label="Required"
defaultValue="Hello World"
className={classes.textField}
margin="normal"
/>
I was expecting that the call to getForm() would have rendered my fields, but instead it spits out this on the web page. Am I doing something wrong?
<TextField id="standard-name" name='asdada' className={classes.textField} value={this.state.name} onChange={this.handleChange("name")} required=true margin="normal"/><TextField id="standard-uncontrolled" name="asda" label="Required" className="{classes.textField}" value="asd" onChange={} required=true margin="normal"/><TextField id="standard-read-only-input" name="asd" label="Read Only" className={classes.textField} value="asd" onChange={} required=false margin="normal" InputProps={{readOnly: true}}/><TextField id="standard-dense" name="w3rg" label="Dense" className={classNames(classes.textField, classes.dense)} value="sdas" onChange={} required=false margin="dense"/>
try to return the component at mapping:
const getForm = formName => {
return FormConfig[formName].fields.map(field => evalComponent(field))
}
const evalComponent = field => {
let { uiElement, ...props } = field
switch(uiElement) {
case 'TextField':
return <TextField {...props}/>
default:
return false
}
}
how to have dropdowns selected value in state.here is my code iam getting value for name field but dropdown not working, can anyone find out what i am missing?
MyComponent.js
import React,{Component} from 'react';
class MyComponent extends Component{
state={
data:{
name:'',
subject:''
}
}
onChange = e =>
this.setState({
data: { ...this.state.data, [e.target.name]: e.target.value }
},()=>{
console.log(this.state.data);
}
)
render(){
const {data}=this.state;
const subjects= [
{text: '1',value: 'kannada'},
{text: '2', value: 'english'},
{text: '3',value: 'hindhi'}
]
return(
<div>
<Input
name="name"
onChange={this.onChange}
placeholder='Your name ...'
/>
<Dropdown
placeholder='Select Subject'
name="subject"
onChange={this.onChange}
selection
options={subjects}
/>
</div>
)
}
}
export default MyComponent;
how to have selected dropdown value in state?, iam getting changed value for name field but for dropdown not getting.
handleChange = (e, { value }) => this.setState({ value })
Add value prop to Dropdown
render(
const { value } = this.state;
return(
<Dropdown
placeholder='Select Subject'
name="subject"
onChange={this.handleChange}
selection
options={subjects}
value={value}
/>)
)
If anyone is using react hooks and semantic ui react, this is how I got it to work, without having to create a separate change handler function for it.
const options = [
{ key: "1", text: "Speaker", value: "SPEAKER" },
{ key: "2", text: "Event Planner", value: "EVENT_PLANNER" }
];
const [values, setValues] = useState({
firstName: "",
userType: ""
});
const onChange = (event, result) => {
const { name, value } = result || event.target;
setValues({ ...values, [name]: value });
};
<Form.Dropdown
placeholder="I am a.."
name="userType"
label="User Type"
selection
onChange={onChange}
options={options}
value={values.userType}
/>
What kept throwing me off was the 'result' that the onChange function takes as an argument. Since the options are stored as objects in an array, the correct way to access their values is with the 'result' and not 'event.target.'
One more way to use DropDown in React Semantic. it worked perfectly for me.
const options = [
{ key: 'ex1', text: 'Example 1', value: 'Example 1' },
{ key: 'ex2', text: 'Example 2', value: 'Example 2' },
]
Method to set value
handleSelectChange=(e,{value})=>this.setState({stateValue:value})
In Form
<Form.Select fluid label='List Example' options={options}
placeholder='List Example'
value={value}
onChange={this.handleSelectChange} />
You can use the Form.Field also.
For more information.
constructor(props) {
super(props);
this.state={
subject : ""
}
}
handleChange = (e, result) => {
const { name, value } = result;
this.setState({
[name]: value
});
};
render() {
const options = [
{ key: 'ex1', text: 'Example 1', value: 'Example 1' },
{ key: 'ex2', text: 'Example 2', value: 'Example 2' },
];
return(
<div>
<Form>
<Form.Field
placeholder="Subject"
name="subject"
label="Subject"
control={Dropdown}
fluid
selection
onChange={this.handleChange}
options={options}
value={this.state.subject}
/>
</Form>
</div>
);
}