Typescript, React, strongly typing useHistory - reactjs

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' },
});
}

Related

I want to update my quantity at the same time based on quantity update my available quantity and sell quantity

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;

How to test setFieldValue from Formik, in a react-slider component

maybe it is a weird question, but I am new to unit testing, and can't wrap my head around this one.
import { Field, FieldInputProps, FieldMetaProps, FormikHelpers, FormikValues } from 'formik';
import Slider from 'components/elements/slider';
import FormError from 'components/elements/formerror';
import { useEffect, useState } from 'react';
interface IScheduleSlider {
min?: number;
max?: number;
title: string;
name: string;
unitLabel: string;
conversions: {
first: {
//eslint-disable-next-line
convertFct: (x: number) => number;
label: string;
};
second: {
//eslint-disable-next-line
convertFct: (x: number) => number;
label: string;
};
};
showError?: boolean;
}
const ScheduleSlider = ({ min = 0, max = 100, title, name, unitLabel, conversions, showError = false }: IScheduleSlider) => {
const [error, setError] = useState(false);
console.log(conversions);
useEffect(() => {
if (showError) {
setError(true);
}
}, [showError]);
const getRealValue = (val: number) => (val > max ? max : val);
return (
<Field name={name}>
{({
field,
form: { setFieldValue, setFieldTouched },
meta,
}: {
field: FieldInputProps<never>;
form: FormikHelpers<FormikValues>;
meta: FieldMetaProps<never>;
}) => (
<div className={`schedule-slider ${error ? 'error' : ''}`} data-testid="schedule-slider-test">
<div className="schedule-slider__top">
<h4>{title}</h4>
{typeof conversions?.first?.convertFct === 'function' && typeof conversions?.second?.convertFct === 'function' && (
<div>
<div className="schedule-slider__top--conversion">
<h5>{conversions?.first?.convertFct(field.value)}</h5>
<span>{conversions?.first?.label}</span>
</div>
<div className="schedule-slider__top--conversion">
<h5>{conversions?.second?.convertFct(field.value)}</h5>
<span>{conversions?.second?.label}</span>
</div>
</div>
)}
</div>
<div className="schedule-slider__bottom">
<Slider
max={Math.ceil(max)}
min={min}
value={Math.ceil(field.value)}
onChange={(val: number) => {
setFieldValue(field?.name, getRealValue(val));
setFieldTouched(field?.name, true);
setError(false);
}}
labels={{ left: `${min} ${unitLabel}`, right: `${max} ${unitLabel}` }}
/>
<div className="schedule-slider__value">
<div>
<h3 data-testid="field-test-value">{field.value}</h3>
<span>{unitLabel}</span>
</div>
</div>
</div>
{error && <FormError meta={meta} />}
</div>
)}
</Field>
);
};
export default ScheduleSlider;
This is my tsx file with the component, I have a ScheduleSlider component which in itself contains a formik component, and a react-slider, which has the onChange prop where setFieldValue and setFieldTouched is.
/* eslint-disable testing-library/no-container */
/* eslint-disable testing-library/no-node-access */
import ScheduleSlider from './scheduleslider';
import * as Formik from 'formik';
import { cleanup, fireEvent, render, screen, waitFor } from '#testing-library/react';
import React from 'react';
import userEvent from '#testing-library/user-event';
import { renderWithRedux } from 'test-utils';
describe('render ScheduleSlider', () => {
const mockFn = jest.fn();
const useFormikContextMock = jest.spyOn(Formik, 'useFormikContext');
beforeEach(() => {
useFormikContextMock.mockReturnValue({
setFieldValue: jest.fn(),
setFieldTouched: jest.fn(),
} as unknown as never);
});
afterEach(() => {
jest.clearAllMocks();
cleanup();
});
const defaultId = 'schedule-slider-test';
const sliderId = 'slider-test';
const fieldId = 'field-test-value';
it('render component with props', async () => {
const props = {
min: 0,
max: 100,
title: 'Test title',
name: 'POWER',
unitLabel: 'kWh',
conversions: {
first: {
convertFct: jest.fn().mockImplementation((x) => x),
label: '%',
},
second: {
convertFct: jest.fn().mockImplementation((x) => x),
label: 'km',
},
},
showError: false,
};
const { container } = renderWithRedux(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
// const slider = screen.queryByRole('slider');
const slider = screen.getByTestId(sliderId);
expect(container).toBeVisible();
props.conversions.first.convertFct.mockReturnValue('10');
props.conversions.second.convertFct.mockReturnValue('30');
// expect(slider).toBeVisible();
// expect(slider).toHaveClass('slider__thumb slider__thumb-0');
if (slider) {
slider.ariaValueMin = '1';
slider.ariaValueMax = '100';
// screen.getByTestId(fieldId).textContent = '80';
fireEvent.keyDown(slider, { key: 'ArrowLeft', code: 'ArrowLeft', charCode: 37 });
}
// expect(useFormikContextMock).toBeCalled();
// console.log(slider?.ariaValueMin);
// console.log(screen.getByTestId(fieldId).textContent);
console.log(props.conversions.second.convertFct.mock.results);
console.log(container.textContent);
});
it('render with default min, max, showError value', () => {
const props = {
min: undefined,
max: undefined,
title: 'test title',
name: 'schedule',
unitLabel: 'test unit',
conversions: {
first: {
convertFct: jest.fn(),
label: '%',
},
second: {
convertFct: jest.fn(),
label: 'km',
},
},
showError: true,
};
render(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
expect(screen.getByTestId(defaultId)).toBeVisible();
});
it('checks for onChange values', () => {
const props = {
min: 0,
max: 100,
title: 'test title',
name: 'schedule',
unitLabel: 'test unit',
conversions: {
first: {
convertFct: jest.fn(),
label: '%',
},
second: {
convertFct: jest.fn(),
label: 'km',
},
},
showError: undefined,
};
render(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
expect(screen.getByTestId(defaultId)).toBeVisible();
});
});
And this is my test file, I could render the component, and some of the branches, functions, statemnts are covered, but don't know how to test setFieldValue. Tried to fire events, but I am making some errors and can't see where. Anybody has any idea how to start with this. Sorry for the comments, console.log-s but I was tryng all kinds of solutions

Tag 'CreateStudent' expects at least '5' arguments, but the JSX factory 'React.createElement' provides at most '2'

I'm making a function to get a student's name, age, and e-mail input and register in the array.
index.tsx
import React, { useState, useRef } from "react";
import Student from "./UserList";
import CreateStudent from "./CreateUser";
function StudentList() {
const [students, setStudents] = useState<any>([
{
id: "st001",
name: "김남준",
age: 28,
email: "rm#gmail.com",
},
]);
const nextId = useRef(2);
const [inputs, setInputs] = useState({
name: "",
age: "",
email: "",
});
const { name, age, email } = inputs;
const onDataChange = (e: { target: { name: any; value: any } }) => {
const { name, value } = e.target;
setInputs({
...inputs,
[name]: value,
});
};
const onCreate = () => {
const student = {
id: "st00" + nextId.current,
name,
age,
email,
};
setStudents([...students, student]);
setInputs({
name: "",
age: "",
email: "",
});
nextId.current += 1;
};
return (
<div>
<CreateStudent
name={name}
age={age}
email={email}
onDataChange={onDataChange}
onCreate={onCreate}
/>
{students.map((student: { id: React.Key | null | undefined }) => (
<Student student={student} key={student.id} />
))}
</div>
);
}
export default StudentList;
./CreateStudent.tsx
import React from "react";
function CreateStudent(
{ name }: { name: any },
{ age }: { age: any },
{ email }: { email: any },
{ onDataChange }: { onDataChange: any },
{ onCreate }: { onCreate: any }
) {
return (
<div>
<input type="text" name="name" onChange={onDataChange} value={name} />
<input type="text" name="age" onChange={onDataChange} value={age} />
<input type="text" name="email" onChange={onDataChange} value={email} />
<button onClick={onCreate}>Add</button>
</div>
);
}
export default CreateStudent;
This is the content of the error that occurred to me.
Tag 'CreateStudent' expects at least '5' arguments, but the JSX factory 'React.createElement' provides at most '2'.
name={name}
age={age}
email={email}
onDataChange={onDataChange}
onCreate={onCreate}
I'm actually using these five arguments.
Why are errors occurring here and how do I resolve them?
The CreateStudent is a react functional component, and in most cases it should just accept only one props argument. In your case, you should destructure the props into 5 keys instead of 5 arguments.
interface CreateStudentProps {
name: any;
age: any;
email: any;
onDataChange: any;
onCreate: any;
}
function CreateStudent({
name,
age,
email,
onDataChange,
onCreate
}: CreateStudentProps) {
...your code here
}

RTL - test expect(onChange).toHaveBeenCalledWith({ name, value }) not working

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..

React Native Store value in redux

i wanted to store the Email in the redux store and i am unable to do so here is my sign in component and redux store any help would be appreciated i am using react-navigation
My Dispatch Method is invoked on the initial load as well as on every key stroke for email input i want that to invoke only on hit of continue button
I need a way to store the email in the store and retrieve it in some other screen later
SignUp.js
import React, { Component } from 'react';
import {
StyleSheet,
View,
KeyboardAvoidingView,
Keyboard,
TouchableWithoutFeedback,
Alert
} from 'react-native';
import { SocialIcon } from 'react-native-elements';
import PropTypes from 'prop-types';
import { Header } from 'react-navigation';
import { connect } from 'react-redux';
import {
Container, Footer, FooterContainer, DefaultInput, Typography
} from '../components/common';
import { validate } from '../config';
import * as actionTypes from '../store/actions';
const styles = StyleSheet.create({
container: {
flex: 1
},
input: {
width: '80%',
height: 40
}
});
class SignUp extends Component {
state = {
controls: {
email: {
value: '',
valid: false,
validationRules: {
isEmail: true
},
touched: false
},
password: {
value: '',
valid: false,
validationRules: {
minLength: 6
},
touched: false
},
confirmPassword: {
value: '',
valid: false,
validationRules: {
equalTo: 'password'
},
touched: false
}
}
};
updateInputState = (key, value) => {
let connectedValue = {};
const stateObject = this.state;
if (stateObject.controls[key].validationRules.equalTo) {
const equalControl = stateObject.controls[key].validationRules.equalTo;
const equalValue = stateObject.controls[equalControl].value;
connectedValue = {
...connectedValue,
equalTo: equalValue
};
}
if (key === 'password') {
connectedValue = {
...connectedValue,
equalTo: value
};
}
this.setState(prevState => ({
controls: {
...prevState.controls,
confirmPassword: {
...prevState.controls.confirmPassword,
valid:
key === 'password'
? validate(
prevState.controls.confirmPassword.value,
prevState.controls.confirmPassword.validationRules,
connectedValue
)
: prevState.controls.confirmPassword.valid
},
[key]: {
...prevState.controls[key],
value,
valid: validate(value, prevState.controls[key].validationRules, connectedValue),
touched: true
}
}
}));
};
render () {
const stateData = this.state;
const { navigation } = this.props;
return (
<KeyboardAvoidingView
style={styles.container}
behavior="padding"
keyboardVerticalOffset={Header.HEIGHT + 20}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<Container>
<Typography textType="loginLabelStyle" textLabel="Use any of your existing profiles" />
<View style={‌{
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
<SocialIcon type="twitter" />
<SocialIcon type="facebook" />
<SocialIcon type="google" light onPress={this.signIn} />
</View>
<Typography textType="loginLabelStyle" textLabel="or create one on SimpliFid" />
<DefaultInput
placeholder="Your E-Mail Address"
style={styles.input}
value={stateData.controls.email.value}
onChangeText={val => this.updateInputState('email', val)}
valid={stateData.controls.email.valid}
touched={stateData.controls.email.touched}
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
/>
<DefaultInput
placeholder="Password"
style={styles.input}
value={stateData.controls.password.value}
onChangeText={val => this.updateInputState('password', val)}
valid={stateData.controls.password.valid}
touched={stateData.controls.password.touched}
secureTextEntry
/>
<DefaultInput
placeholder="Confirm Password"
style={styles.input}
value={stateData.controls.confirmPassword.value}
onChangeText={val => this.updateInputState('confirmPassword', val)}
valid={stateData.controls.confirmPassword.valid}
touched={stateData.controls.confirmPassword.touched}
secureTextEntry
/>
<FooterContainer>
<Footer
leftButtonHandler={() => navigation.goBack()}
rightButtonHandler={this.props.onSignUp(stateData.controls.email.value, navigation)}
/* rightButtonHandler={() => navigation.navigate('ChatBot')} */
/>
</FooterContainer>
</Container>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
}
}
SignUp.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func.isRequired
}).isRequired
};
const mapDispatchToProps = dispatch => ({
onSignUp: (email, navigation) => {
Alert.alert(email);
dispatch({ type: actionTypes.SIGNUP, email });
navigation.navigate('ChatBot');
}
});
export default connect(
null,
mapDispatchToProps
)(SignUp);
Reducers.js
import * as actionTypes from './actions';
const initialState = {
email: '',
accountType: '',
name: '',
dob: '',
address: '',
ssn: '',
phoneNumber: '',
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SIGNUP:
return {
...state,
email: action.email,
};
default:
return state;
}
};
export default reducer;
You are calling the this.props.onSingUp methods on each render
Try wrapping the call in a handler method:
handleRightButton = () => {
this.props.onSignUp(this.state..controls.email.value, this.props.navigation);
}
// And on render
render() {
...
rightButtonHandler={this.handleRightButton}
...
}
The problem was that i was trying to access the store in a wrong way i was trying using this
import state from '../store/reducers';
const Email = state.email;
However the correct way and probably the only way to access the store is using mapStateToProps
const mapStateToProps = state => ({
email: state.email,
});
<Footer
leftButtonHandler={() => navigation.goBack()}
rightButtonHandler={(event) => {
event.preventDefault();
this.props.onSignUp(stateData.controls.email.value,navigation)
/>
Try adding the event.preventDefault() in the rightButtonHandler.

Resources