input simulate change doesn't work in enzyme with event object provided - redux-form

I have a unit test written as:
it('shows validation message when predicate is violated', () => {
const input = subject.find('input').at(0);
input.simulate('change', { target: { value: " " } });
input.simulate('blur');
expect(subject.find('[role="alert"]').length).toEqual(1);
});
which I get Expected value to equal: 1, Received: 0.
The input validation picks up empty value and returns an error message. I have tested that subject.find('[role="alert"]') will catch the error message if it presents, and input.props().value also gives empty string. So I suspect simulate('click') didn't work. I have gone through many, many related issues but didn't find my answer, hope someone could help!
My input element is simple:
<Field
name="customerFirstName"
label = "First name"
type="text"
component={inputField}
validate={required}
/>
const inputField = props => {
const { input, label, type, meta: { touched, error }} = props;
return (
<FormGroup>
<ControlLabel>{label}</ControlLabel>
<FormControl {...input} placeholder={label} type={type} />
{ touched && error && <span>{error}</span> }
</FormGroup>
)
}
Validation is:
export const required = value => {
if (value) { value = value.trim() };
return value ? undefined : ErrorMessage('This field is required')
}

Related

React hooks one checkbox checked to uncheck other checkbox

Hi I have a parent component which contains a bunch child components which are checkboxes.
parent component is something like this inside:
const [items, setItems] = useState([
{
id:1,
selected: false,
},
{
id:2,
selected: false,
}
]);
const changeSelected = (id) =>
{
items.forEach((item)=>
{
if (item.id === id)
{
item.selected = !item.selected;
}
else{
item.selected = false;
}
})
}
return(
<div>
{items.map((item)=>{
<Child item={item} changeSelected={changeSelected}/>
})}
</div>
)
and in the child component, it has something like this inside:
return(
<div>
<input type="checkbox" checked={props.item.selected} onChange={()=>{props.changeSelected(props.item.id)}} />
</div>
)
I know partially this isnt working is because useState is async but I dont know what to do to make it work, or if I should try a different approach? Thank you
You can refactor your function to this:
const changeSelected = (id) => {
setItems(prev => ([...prev, {id, selected: !prev.filter(x => x.id === id)[0].selected}]))
}
Obviously I forgot to setItem according to the two answers I received. But I think the right way to do it is to make a deep copy of my existing items, and setItems again. For my future reference, here is what I have now working (an example):
let newItems = [...items];
newItems.forEach((item)=>
{
if (item.id === id)
{
item.selected = !item.selected;
}
else{
item.selected = false;
}
})
setProducts(newItems);
You are not updating the state anywhere. You should make a copy of the array and then change the object you want to:
const changeSelected = (id) =>
{ let newItems = [];
items.forEach((item)=>
{
if (item.id === id)
{
newItems.push({ id : item.id , selected : item.selected });
}
else{
newItems.push({ id : item.id , selected : false });
}
})
setItems(newItems);
}
Call setItems to set it.
Note:
Not related to the question but you should use unique keys when iterating over list.
{items.map((item)=>{
<Child key={item.id} item={item} changeSelected={changeSelected}/>
})}

Uncaught TypeError: Cannot read properties of undefined (reading 'target'),

My problem is that how do i access 'handleInputChange', because i cant write 'handleInputChange' function outside the useEffect hook since it is performing a sideeffect. I would love it if someone can help me out with this situation.
1. const [values, setValue] = useState({});
const dispatch = useDispatch();
let handleInputChange
useEffect(()=>{
handleInputChange = (e) =>{
setValue(
{
values: { ...values, [e.target.name]: e.target.value }
},
() => {
dispatch({
type: "SET_FORMVALUES",
payload: values
})
}
)}
handleInputChange();
},[dispatch])
<TextField id="outlined-basic" label="First Name" variant="outlined"
name="firstName"
className='app__input'
placeholder='First Name'
type="text"
value={values['firstName']}
onChange = {handleInputChange} />
//Reducer.js
const initialState = {
formValues: {},
message: ""
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "SET_FORMVALUES":
console.log("--- Changing form data ---");
return {
...state,
formValues: action.payload
};
case "SUBMIT_FORM":
return {
...state,
message: "Form submitted!!"
};
default:
return state;
}
};
First, you don't need the core React useState hook, because you are using React Redux. This is actually creating a second state local to your component. Instead, use the centralized Redux store you've configured and React-Redux hooks. As long as you have wrapped your app in a context provider and passed your store to it, you can use useDispatch to update state and useSelector to retrieve state and subscribe to changes.
Second, you don't need useEffect, as you are just updating state.
Here's an example:
import { useDispatch, useSelector } from 'react-redux';
export default function App() {
const formValues = useSelector((state) => state.formValues);
const dispatch = useDispatch();
const handleInputChange = (name, value) => {
dispatch(
{
type: "SET_FORMVALUES",
payload: {
...formValues,
[name]: value
}
}
);
}
return (
<div className="App">
<input type="text" name="FirstName" onChange={ (e) => handleInputChange(e.target.name, e.target.value)} />
<span>{formValues["FirstName"]}</ span>
<input type="text" name="LastName" onChange={ (e) => handleInputChange(e.target.name, e.target.value)} />
<span>{formValues["LastName"]}</ span>
</div>
);
}
Much of this is probably not directly related to the error in the question title, but simplifying your code should help you debug more easily. That error may have been simply because you didn't explicitly pass the event in your onChange handler. I.e. onChange = {handleInputChange} vs. onChange = {(e) => handleInputChange(e)}

React Search Filter of Objects not filtering

I'm trying to create a search filter that will filter through facility names that lives in an array of objects.If I hard code an array into the state the filter works, but I need it to drab the info from props. The filtered list is being generated and showing all of the names on the screen but when I type it the textbox to filter nothing happens. What have I overlooked?
class FacilitySearch extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ""
};
}
componentDidMount() {
this.props.dispatch(actions.getFacilitiesList());
}
//The subsr limits the # of characters a user can enter into the seach box
updateSearch = event => {
this.setState({ search: event.target.value.substr(0, 10) });
};
render() {
if (!this.props.facilityList) {
return <div>Loading...</div>
}
let filteredList = this.props.facilityList;
filteredList.filter(facility => {
return facility.facilityName.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
});
return (
<div>
<input
type="text"
value={this.state.search}
onChange={this.updateSearch.bind(this)}
placeholder="Enter Text Here..."
/>
<ul>
{filteredList.map(facility => {
return <li key={facility.generalIdPk}>{facility.facilityName}</li>;
})}
</ul>
</div>
);
}
}
const mapStateToProps = state => ({
facilityList: state.facilityList.facilityList
});
export default connect(mapStateToProps)(FacilitySearch)
The problem is that you are not storing the return value of filter in any variable.
You should do something like:
let filteredList = this.props.facilityList.filter(facility => {
return facility.facilityName.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
});
From MDN:
The filter() method creates a new array with all elements that pass the test implemented by the provided function.

How to get input field name that value has changed [ Angular 6 ]

I tried reactive form valueChanges but valueChanges method doesn't return input field name which has changed.
I thought code like this. but I think this is not smart way. Because I have to compare every each input field. so I need more smart way.
// get init view data from local storage
this.localstorageService.getPlaceDetail().subscribe(data => {
this.initPlaceDetail = data;
// watch changed value
this.editPlaceForm.valueChanges.subscribe(chengedVal => {
if (chengedVal['ja_name'] !== this.initPlaceDetail.languages.ja.name.text) {
this.changedJA = true;
}
if (chengedVal['ja_romaji'] !== this.initPlaceDetail.languages.ja.name.romaji) {
this.changedJA = true;
}
// ...... I have to check all input fields??
});
});
I'm adding form controls from an array and something like this worked for me. Just reference the item you need instead of expecting the valueChanges observable to pass it to you.
myFields.forEach(item => {
const field = new FormControl("");
field.setValue(item.value);
field.valueChanges.subscribe(v => {
console.log(item.name + " is now " + v);
});
});
This is my way to get changed control in form.
I shared for whom concerned.
Method to get list control changed values
private getChangedProperties(form): any[] {
let changedProperties = [];
Object.keys(form.controls).forEach((name) => {
let currentControl = form.controls[name];
if (currentControl.dirty)
changedProperties.push({ name: name, value: currentControl.value });
});
return changedProperties;
}
If you only want to get latest changed control you can use
var changedProps = this.getChangedProperties(this.ngForm.form);
var latestChanged = changedProps.reduce((acc, item) => {
if (this.changedProps.find(c => c.name == item.name && c.value == item.value) == undefined) {
acc.push(item);
}
return acc;
}, []);
Instead of listening to whole form changes you can listen to value changes event for each form control as shown in below code:
this.myForm.get('ja_name').valueChanges.subscribe(val => {
this.formattedMessage = `My name is ${val}.`;
});

Angular2 Async Form Validator (return Promise)

I'm trying to update the Angular2 Forms Validation example to handle an Async Validation response. This way I can hit an HTTP endpoint to validate a username.
Looking at their code they currently aren't currently using a Promise and it's working just fine:
/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
const name = control.value;
const no = nameRe.test(name);
return no ? {'forbiddenName': {name}} : null;
};
}
I'm trying to update to return a Promise. Something like:
/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl) => {
const name = control.value;
return new Promise( resolve => {
resolve({'forbiddenName': {name}});
});
};
}
However, the result I get doesn't display the error message, it's showing undefined.
My thought is it has something to do with the way they are handling displaying the errors:
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
However I'm not sure of a better way of doing this.
Angular2 example:
https://angular.io/docs/ts/latest/cookbook/form-validation.html#!#live-example
Link to my example attempting to return Promise:
https://plnkr.co/edit/sDs9pNQ1Bs2knp6tasgI?p=preview
The problem is that you add the AsyncValidator to the SyncValidator Array. AsyncValidators are added in a separate array after the SyncValidators:
this.heroForm = this.fb.group({
'name': [this.hero.name, [
Validators.required,
Validators.minLength(4),
Validators.maxLength(24)
],
[forbiddenNameValidator(/bob/i)] // << separate array
],
'alterEgo': [this.hero.alterEgo],
'power': [this.hero.power, Validators.required]
});

Resources