Enzyme Shallow not finding a react-bootstrap Component - reactjs

Below is a Sign In React SignIn Component made by using Redux-forms
const renderInput = ({input,label,type,placeholder}) => {
return (
<div>
<Form.Label>{label}</Form.Label>
<Form.Control type={type} placeholder={placeholder} { ...input}/>
</div>
)
}
export let signInForm = props => {
const { error,handleSubmit , pristine , submitting } = props
return (
<Container className="justify-content-md-center">
<Alert variant="primary">Sign in here if you already have an account</Alert>
<Form onSubmit={handleSubmit}>
<Form.Group>
<Field name="email" component={renderInput} label="Email" type="email" placeholder="Email" />
</Form.Group>
<Form.Group>
<Field name="password" component={renderInput} label="Password" type="password" placeholder="Password" />
</Form.Group>
<Button type="submit" disabled= { pristine || submitting }>Sign In</Button>
</Form>
</Container>
)
}
export default signInForm = reduxForm ({
form : 'signIn'
})(signInForm)
My enzyme-shallow test for this
import React from 'react';
import { shallow } from 'enzyme';
import {signInForm as SignIn} from './SignIn';
import Button from 'react-bootstrap/Button'
import { expect } from 'chai';
describe('Test SignIn component', () => {
it('Test click event', () => {
const mockCallBack = jest.fn();
let wrapper = shallow(<SignIn onSubmit={mockCallBack}/>);
expect(wrapper.find(Button)).to.have.lengthOf(1);
})
})
My test output says
AssertionError: expected {} to have a length of 1 but got 0
1) The test fails. The Button component is not found in the test. I am expecting it to have a length of 1
2) I am using chai method to.Have.lengthOf because I could not get the jest method toHaveLength to work. toHaveLength seems to be used for only checking arrays or strings size. How could I use jest to do this?

If you are trying to simulate the SignIn form submit, you would actually call the simulate event on the form itself and not on the button.
You can simulate that with this code:
wrapper.find('form').simulate('submit');
Here is some info on why that is from the Enzyme docs:
Currently, event simulation for the shallow renderer does not propagate as one would normally expect in a real environment. As a
result, one must call .simulate() on the actual node that has the
event handler set.
Even though the name would imply this simulates an actual event, .simulate() will in fact target the component's prop based on the
event you give it. For example, .simulate('click') will actually get
the onClick prop and call it.
As noted in the function signature above passing a mock event is optional. Keep in mind that if the code you are testing uses the
event for something like, calling event.preventDefault() or accessing
any of its properties you must provide a mock event object with the
properties your code requires.
https://airbnb.io/enzyme/docs/api/ShallowWrapper/simulate.html

Related

latest react-hook-form error handling with material-ui TextField

I have difficulties, using react-hook-form with material-ui.
I prepared a codesandbox example.
import { TextField } from "#material-ui/core";
import React from "react";
import { useForm } from "react-hook-form";
import "./styles.css";
interface IMyForm {
vasarlo: string;
}
export default function App() {
const {
handleSubmit,
formState: { errors },
register
} = useForm<IMyForm>();
const onSubmit = (data: IMyForm) => {
alert(JSON.stringify(data));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>First Name</label>
<TextField
variant="outlined"
margin="none"
label="Test"
{...register("vasarlo", {
required: "error text"
})}
error={errors?.vasarlo ? true : false}
helperText={errors?.vasarlo ? errors.vasarlo.message : null}
/>
<input type="submit" />
</form>
);
}
How can I properly use the register function, to get error message, write to input field, also the onSubmit function to work?
I couldn't find answer in the documentation for this scenario.
In react-hook-form v7, this is how you register an input:
<input {...register('name')} />
Calling register() will return necessary props for your input like onChange, onBlur and ref. These props make it possible for react-hook-form to keep track of your form data. Now when you use register with Material-UI TextField like this:
<TextField {...register('name')} />
You pass the ref property directly to the TextField while the correct place to put it is in the inputRef:
<TextField inputRef={ref} />
So you have to modify your code like this:
const { ref: inputRef, ...inputProps } = register("vasarlo", {
required: "error text"
});
<TextField inputRef={inputRef} {...inputProps} />
How can I properly use the register function, to get error message
There is nothing wrong with your error handling code. Though you can shorten you code a bit more using Typescript's optional chaining operator ?.:
<TextField
error={!!errors.vasarlo}
helperText={errors?.vasarlo?.message}
inputRef={ref}
{...inputProps}
/>
Live Demo
You're misusing Controller. react-hook-form's default functionality is using uncontrolled inputs. Remove <Controller/> and put this on your TextField
inputRef={register({
required: 'This is required',
validate: (data) => myValidationFunction(data)
})}
You'll only want to use a controller if you NEED to modify/intercept/format a value that is being displayed in the TextField that is different from what a user is typing, i.e a phone number getting shown as (xxx)-xxx-xxxx when only typing the digits.

Creating a controlled form with Grommet throws errors

I am trying to create a basic form using Grommet following the examples at https://v2.grommet.io/form. My specific form looks like this:
import React from 'react';
import { Box, Form, FormField, TextInput, Button } from 'grommet';
const defaultValue = {};
const LoginForm = () => {
const [value, setValue] = React.useState(defaultValue);
function handleSubmit(e) {
e.preventDefault();
const { email, password } = e.value;
console.log('pretending to log in:', email, password);
// doLogin(email, password)
}
return (
<Form
value={value}
onChange={nextValue => {
setValue(nextValue);
}}
onReset={() => setValue(defaultValue)}
onSubmit={handleSubmit}
>
<FormField label="email" name="email" required>
<TextInput name="email" />
</FormField>
<FormField label="password" name="password" required>
<TextInput name="password" />
</FormField>
<Box direction="row" justify="between" margin={{ top: 'medium' }}>
<Button type="reset" label="Reset" />
<Button type="submit" label="Login" primary />
</Box>
</Form>
);
};
As soon as I start typing into either field, I get the following:
Warning: A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: ...
Note that I get the exact same error if I replace my form code with a cut/paste from the example in the link above.
I have a fairly reasonable understanding of what the error means, but I have no idea how to fix it in this case. Is Grommet's implementation of controlled form components broken, or am I missing something elsewhere in my configuration or packages that might be causing this?
The React.js controlled standards are not allowing objects to be undefined.
So the problem starts with how you've defined your defaultValue = {};, since it is an empty object, there is no initial value to the FormField children and that causes them to be undefined and hence the error.
So if you'll change the preset value to be more accommodating to your fields, such as defaultValue = { password: '' }; it will fix your error.
For more reading about React controlled and uncontrolled inputs read this A component is changing an uncontrolled input of type text to be controlled error in ReactJS

React Hooks - Input loses focus when 1 character is typed in

I'm playing with React Hooks - rewriting a form to use hook concepts. Everything works as expected except that once I type any 1 character into the input, the input loses focus.
I guess there is a problem that the outside of the component doesn't know about the internal changes in the component, but how do I resolve this issue?
Here is the useForm Hook:
import React, { useState } from "react";
export default function useForm(defaultState, label) {
const [state, setState] = useState(defaultState);
const FormComponent = () => (
<form>
<label htmlFor={label}>
{label}
<input
type="text"
id={label}
value={state}
placeholder={label}
onChange={e => setState(e.target.value)}
/>
</label>
</form>
);
return [state, FormComponent, setState];
}
Here is the component that uses the Hook:
function App() {
const [formValue, Form, setFormValue] = useForm("San Francisco, CA", "Location");
return (
<Fragment>
<h1>{formValue}</h1>
<Form />
</Fragment>
);
}
While answer by Kais will solve the symptoms, it will leave the cause unaddressed. It will also fail if there are multiple inputs - which one should autofocus itself on rerender then?
The issue happens when you define a component (FormComponent) inside the scope of another function which is called each render of your App component. This gives you a completely new FormComponent each time your App component is rerendered and calls useState. That new component is then, well, without focus.
Personally I would feel agains returning components from a hook. I would instead define a FormComponent component, and only return state from useForm state.
But, a working example closest to your original code could be:
// useForm.js
import React, { useState } from "react";
// Define the FormComponent outside of your useForm hook
const FormComponent = ({ setState, state, label }) => (
<form>
<label htmlFor={label}>
{label}
<input
type="text"
id={label}
value={state}
placeholder={label}
onChange={e => setState(e.target.value)}
/>
</label>
</form>
);
export default function useForm(defaultState, label) {
const [state, setState] = useState(defaultState);
return [
state,
<FormComponent state={state} setState={setState} label={label} />,
setState
];
}
// App.js
import useForm from "./useForm";
export default function App() {
const [formValue, Form] = useForm("San Francisco, CA", "Location");
return (
<>
<h1>{formValue}</h1>
{Form}
</>
);
}
Here's a sandbox
When you enter any text in input box. Parent Component is also re-rendering. So you need to make focus on input manually.
For this, use autoFocus in input tag
<input
type="text"
id={label}
value={state}
placeholder={label}
onChange={e => setState(e.target.value)}
autoFocus
/>
The above answers didn't work for me. The solution that worked for me was much simpler and, for that reason, less obvious.
The Problem
Essentially, the value that I was changing with the input was also being used for each key in a list of inputs.
Hence, when I updated the value the key would change and React would detect that it's different relative to the last key and create a new input in its place. As a new input it wouldn't focus on itself.
However, by using autoFocus it would automatically focus on the newly created input. But the issue wasn't fixed as it was obvious that the input was continually going through a cycle of un-focus and focus.
Here's an article demonstrating the problem.
The Fix
Update the key to an unchangeable value so React would have a stable reference to the list items. In my case I just updated it to the index. This is not ideal (React docs recommend using a stable ID), but in my situation it was okay because the order of the items won't change.
The first solution actually worked for me , initially the functional component which contained the text field was part of the main functional component , i was facing the same issue but when i extracted the text-field component into another page and imported it it worked fine
This was my component
<Paper >
<div>
<div style={{padding:'10px' ,display:'flex'}} >
<inputstyle={{marginRight:'5px'}} value={val} onChange={(e)=>{setVal(e.target.value)}} />
</div>
</div>
</Paper>

Redux Form wrapper for kendo-react-ui Input error

I am using react-kendo-ui. I want to wrap Input from #progress/kendo-react-inputs to use it with ReduxForm. Please find my code below:
import React from 'react'
import { Input } from '#progress/kendo-react-inputs';
const InputText = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<Input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
)
export default InputText
Call the InputText from another component as below:
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { Input } from '#progress/kendo-react-inputs';
import InputText from './input-text';
const validateNotEmpty = value => !value ? 'Must enter a value' : null;
const onSubmit = (values) => {
console.log(values);
}
const AddLoc= ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<Field
label="Address"
name="address"
component={InputText}
validate={validateNotEmpty}
/>
</div>
<button type="submit">Submit</button>
</form>
)
export default reduxForm({
form: 'AddLoc'
})(AddLoc)
But while typing inside the input text it keeps giving the following error/warning:
Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property `nativeEvent` on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist().
While typing inside the input text automatically outputs [object Object]. Please check the image above. Could anyone please let me know what is causing the error.
Thanks
The reduxForm works only with pure DOM <input />. Internally it clones the elements search the children and then attaches the onChange dynamically. So it will not work with the Input and the NumericTextBox from the #progress/kendo-react-inputs package. This statement is based on the official kendo documentation about integrating with the redux-form.
The same author of the redux-form have fork of it called react-final-form which could be used on any component that have Value and onChange props. By our tests it works with the components from #progress/kendo-react-inputs and #progress/kendo-react-dropdowns packages. It looks kendo already have an example using final-form in their integration section.

Enter key event handler on react-bootstrap Input component

I have an Input component with a button (buttonAfter property), I set an onClick handler associated to the button and so user can type some text and clic the button to fire the right action.
However, I would like user to be able to press [Enter] key (keycode 13) to achieve the same effect as clicking on the button, just to make the UI easier to use.
I could not find a way to do it, of course I tried onKeydown to register an handler for key down event but it is just ignored.
I think this question is related to React itself instead of react-bootstrap.
Look at this for some basics about React event system: https://facebook.github.io/react/docs/events.html
When you use onKeyDown, onKeyPress or onKeyUp React will pass to your handler an instance of say "target" object with the following properties:
boolean altKey
number charCode
... (for all see link above)
So you can do something like this:
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { Input } from 'react-bootstrap';
class TestInput extends React.Component {
handleKeyPress(target) {
if(target.charCode==13){
alert('Enter clicked!!!');
}
}
render() {
return (
<Input type="text" onKeyPress={this.handleKeyPress} />
);
}
}
ReactDOM.render(<TestInput />, document.getElementById('app'));
I tested above code and it works. I hope this is helpful for you.
The question can also be related to React-bootstrap.
React-bootstrap also has a way to handle instance when ever a button or enter key or any form element is pressed.
The below code explains how to handle an instance when enterkey is pressed without the involvement of React Handlers.(and that makes it cool)
import React from "react";
import ReactDOM from "react-dom";
import { FormGroup, FormControl } from "react-bootstrap";
class TestInput extends Component {
search() {
console.log("Enter Button Pressed");
}
render() {
return (
<FormGroup>
<InputGroup>
<FormControl
placeholder="Press Enter"
type="input"
onKeyPress={event => {
if (event.key === "Enter") {
this.search();
}
}}
/>
</InputGroup>
</FormGroup>
);
}
}
React Bootstrap does not support Input form element anymore.
Instead it introduced below items at your disposal
The FormGroup component wraps a form control with proper spacing, along with support for a label, help text, and validation state.
Wrap your form control in an InputGroup, then use for normal add-ons and for button add-ons.
The FormControl component renders a form control with Bootstrap styling.
References:
https://react-bootstrap.github.io/components.html#forms
https://react-bootstrap.github.io/components.html#forms-input-groups
The right way to do it in a form is the same like in regular JavaScript:
Don't use onClick on a button but use type="submit"
The form should be wrapped with <form onSubmit={handler}>
Handler should prevent page reload handler = (event) => event.preventDefault(); processForm()
Now both the button and pressing Enter on any field will call handler
Piece of functional component doing this
function register(event) {
event.preventDefault()
distpach(authActionCreator.register(username, email, password));
}
return (
<Card>
<form onSubmit={register}>
<Card.Body>
<Card.Title as="h4">Register</Card.Title>
<FormGroup controlId="username">
<FormLabel>Username</FormLabel>
<FormControl type="text" label="Username" placeholder="Username" onChange={handleChange} />
</FormGroup>
<FormGroup controlId="email">
<FormLabel>Email</FormLabel>
<FormControl type="email" label="Email" placeholder="Email" onChange={handleChange} />
</FormGroup>
<FormGroup controlId="password">
<FormLabel>Password</FormLabel>
<FormControl type="password" label="Password" placeholder="Password" onChange={handleChange} />
</FormGroup>
<ButtonToolbar>
<Button variant="primary" type="submit">Register</Button>
</ButtonToolbar>
</Card.Body>
</form>
</Card>
);
A somewhat abbreviated version of the already suggested solutions is:
import React from 'react';
import ReactDOM from 'react-dom';
import { Input } from 'react-bootstrap';
const TestInput = () => {
const handleSubmit = () => {
/* handle form submit here */
}
return (
<Input type="text" onKeyPress={event => event.key === "Enter" && handleSubmit()} />
);
}
ReactDOM.render(<TestInput />, document.getElementById('app'));
onKeyPress={event => event.key === "Enter" && handleSubmit()} will ensure handleSubmit is called when the Enter key is pressed.
Uncaught TypeError: Cannot read property 'charCode' of undefined
You have to use:
handleKeyPress(target) {
if (target.key === 'Enter') {
alert('Enter clicked!!!');
}
}

Resources