Enter key event handler on react-bootstrap Input component - reactjs

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

Related

RadioGroup's in React forms using formik-material-ui

I have a multi-page form controlled by a material-ui stepper, with each 'subform' component being brought in via a switch on the stepper value. Works perfectly well. The state is being held by the parent component via judicious use of the useState hook, and the back and forward functions handleBack, and handleNext are being passed in as props.
The issue is, that whilst the Radio buttons are inserted correctly, and the chosen value put back into state, an error seems to stopping the useEffect hook that calculates values for the next subform from firing.
The error is :
Material-UI: A component is changing an uncontrolled RadioGroup to be controlled.
Elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled RadioGroup element for the lifetime of the component.
The code looks roughly like this (with the usual Grid components nuked):
import React from 'react'
import { Formik, Field, Form } from 'formik'
import { TextField, RadioGroup } from 'formik-material-ui'
import * as Yup from 'yup'
import { FormControlLabel, Radio, Button, Container } from '#material-ui/core'
import { Styles } from '../Styles'
export default function subForm(props) {
const classes = Styles()
const {
formChoice,
templateChoices,
formTitle,
activeStep,
isLastStep,
handleBack,
handleNext,
templates,
} = props
return (
<div>
<Formik
initialValues={formChoice}
validationSchema={Yup.object({
formChoice: Yup.string().required('Required'),
})}
>
{({
submitForm,
validateForm,
setTouched,
isSubmitting,
values,
setFieldValue,
formChoice,
}) => (
<Container>
<Form>
<Field
name="formChoice"
label="Radio Group"
value={formChoice || ''}
component={RadioGroup} >
{templateChoices.map(({ name, description, index }) => (
<FormControlLabel
value={name}
control={<Radio disabled={isSubmitting} />}
label={name} />
))}
</Field>
</Form>
<div className={classes.buttons}>
{activeStep !== 0 && (
<Button
onClick={() => {
handleBack(values)
}}
className={classes.button} >Back</Button>
)}
<Button
className={classes.button}
variant="contained"
color="primary"
onClick={() =>
validateForm().then(errors => {
if (
Object.entries(errors).length === 0 &&
errors.constructor === Object
) {
handleNext(values)
} else {
setTouched(errors)
}
})
}
>
{isLastStep ? 'Submit Draft' : 'Next'}
</Button>
</div>
</Container>
)}
</Formik>
</div>
)
}
The list of options to display is handed in in templateChoices, and looks roughly like this:
[
{"index":0,"name":"Form 1","description":"The standard form.","version":"1.0"},
{"index":1,"name":"Form 2","description":"The special form.","version":"1.1"}
]
Should I be using a RadioGroup? Is there something I'm forgetting to pass through props?

Content in reactstrap modal continues to exist after closing using enzyme/jest

I'm trying to do some testing with enzyme and jest in react, and things work fine when I open a modal e.g. input fields in the modal aren't there and the modal state is false (as intended) when I try to find them using
expect(wrapper.find("input")).toHaveLength(0);
and do exist after I've opened the modal using
const edit = wrapper.find("Button.update-button");
edit.simulate("click");
expect(wrapper.find("input")).toHaveLength(2);
which all works (including the modal state turning to true after it opens) as intended. But when I close the modal, the state gets toggled off correctly, but the modal content (e.g. the input boxes and buttons in the modal) still exist when I try:
expect(wrapper.find("input")).toHaveLength(0);
I still somehow have 2 input fields that shouldn't be there as the modal is closed.
Here is my code for the component I am trying to test if that helps:
/*
Artefact Component displays just UI for the Artefact itself and it's information.
*/
import React, { Component } from "react";
import DeleteArtefact from "../DeleteArtefact";
import UpdateArtefact from "../UpdateArtefact";
import {
Card,
CardImg,
CardTitle,
CardBody,
ButtonGroup,
Button,
CardFooter
} from "reactstrap";
class Artefact extends Component {
// Initialise State
state = {
updatemodal: false,
deletemodal: false
};
// Toggle function for toggling modal open/close
toggleUpdate = () => {
this.setState({
updatemodal: !this.state.updatemodal
});
};
toggleDelete = () => {
this.setState({
deletemodal: !this.state.deletemodal
});
};
prepareUpdateState = () => {
this.props.editUpdate(this.props.artefact);
this.toggleUpdate();
};
render() {
const {
artefact,
onChange,
onUpdateClick,
editUpdate,
onDeleteClick
} = this.props;
return (
<Card>
<CardImg
src={artefact.img}
alt={`Image for Artefact ${artefact.name}`}
/>
<CardBody>
<CardTitle>
<h6>{artefact.name}</h6>
</CardTitle>
</CardBody>
<CardFooter>
<ButtonGroup>
<Button
className="update-button"
color="dark"
onClick={this.prepareUpdateState}
>
Edit
</Button>
<Button
className="delete-button"
color="dark"
onClick={this.toggleDelete}
>
Delete
</Button>
</ButtonGroup>
<UpdateArtefact
artefact={artefact}
onChange={onChange}
onUpdateClick={onUpdateClick}
editUpdate={editUpdate}
toggle={this.toggleUpdate}
modal={this.state.updatemodal}
/>
<DeleteArtefact
_id={artefact._id}
onDeleteClick={onDeleteClick}
toggle={this.toggleDelete}
modal={this.state.deletemodal}
/>
</CardFooter>
</Card>
);
}
}
export default Artefact;
And here is the UpdateArtefact Component that has the modal I'm trying to test:
/*
UpdateArtefact Component is a child Component of ArtefactGallery and
creates a new Artefact by using functions onChange() and updateClick()
and editUpdate() which are passed as props from ArtefactGallery and
passes state back up and makes api calls using axios.
*/
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input
} from "reactstrap";
class UpdateArtefact extends Component {
// Passes state up to ArtefactGallery component and updates the artefact.
onSubmit = e => {
e.preventDefault();
this.props.onUpdateClick(this.props.artefact._id);
this.props.toggle();
};
// Sets state in ArtefactGallery to the initial values of the artefact
// to prepare for any edits to be made in the case that some fields have
// no change, so that there are no null fields.
prepareUpdateState = () => {
this.props.editUpdate(this.props.artefact);
this.props.toggle();
};
render() {
const { artefact } = this.props;
return (
<div style={{ marginLeft: "1rem" }}>
<Modal isOpen={this.props.modal} toggle={this.props.toggle}>
<ModalHeader toggle={this.props.toggle}>
Edit Artefact
</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label>Artefact</Label>
<Input
type="text"
name="name"
id="artefactName"
defaultValue={artefact.name}
onChange={this.props.onChange}
/>
<Label>Image</Label>
<Input
type="text"
name="img"
id="artefactImg"
defaultValue={artefact.img}
onChange={this.props.onChange}
/>
<Button
className="modal-submit-button"
color="dark"
style={{ marginTop: "2rem" }}
block
>
Submit
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
export default UpdateArtefact;
So basically I just want to know what the reason if for why the modal content is still being picked up by enzyme and how to fix this. I've tried searching all over but couldn't find an answer so I'm guessing there's something obvious that I'm missing.
See, your components does not use conditional rendering like
{someFlag && <SomeElement>}
but just pass down isOpen prop:
<Modal isOpen={this.props.modal} toggle={this.props.toggle}>
so probably Modal just hides its props.children and input is kept.
As a workaround you may validate against ModalComponentYouHaveRendered.props().isOpen instead of checking amount of inputs
You can try using:
wrapper.update()
after closing the modal.
In this way, the wrapper should get updated.

Enzyme Shallow not finding a react-bootstrap Component

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

Material-UI - TextField - select text programmatically

Material-UI V1 beta.
Could not find the answer in the Docs.
How do I select text of a TextField component?
Create a ref to it, then call the value of the ref. Something like this:
<TextField ref="myTextField" />
// Call this in the component that contains the text field so 'this' is set properly
function getTextFieldValue() {
return this.refs.myTextField.getValue();
}
This is known as an uncontrolled react component. An alternative would be to use a controlled component and save the value in your state. Here is some info on the difference between controlled and uncontrolled components: https://reactjs.org/docs/uncontrolled-components.html
if you are using a stateless functional component then you can use react hooks.
Also make sure you are using inputRef
import React, { useState, useRef } from "react";
let MyFunctional = props => {
let textInput = useRef(null);
return (
<div>
<Button
onClick={() => {
setTimeout(() => {
console.log(textInput.current.value);
}, 100);
}}
>
Focus TextField
</Button>
<TextField
fullWidth
required
inputRef={textInput}
name="firstName"
type="text"
placeholder="Enter Your First Name"
label="First Name"
/>
</div>
);
};

React bootstrap select dropdown

I am using ReactJS with BootStrap and is unable to get dropdown data that I am passing, in the event handler. The html is not built with option lists only input field is coming.
My code looks like:
import React from 'react';
import PropTypes from 'prop-types';
import { FormWithConstraints, FieldFeedback } from 'react-form-with-constraints';
import { FieldFeedbacks, FormGroup, FormControlLabel, FormControlInput } from 'react-form-with-constraints-bootstrap4';
class Select extends React.Component{
handleChange(event) {
//event.target.value
//do required changes
}
render(){
return(
<FormGroup for={this.props.name}>
<FormControlLabel htmlFor={this.props.name} style={styles.lableStyle}>{this.props.label}{this.props.required?<b
style={{color: "#2c3d4d"}}>*</b>:null}</FormControlLabel>
<FormControlInput
id={"Source"}
label={this.props.label}
placeholder="Select Country"
type="text"
// value={this.props.value}
onChange={this.handleChange}
// (event)=>{
// this.handleChange(event);
// this.props.onChange(event);
// }}
options={this.props.dropDownList}
required={this.props.required}
className="form-control"
optionsKey="name"
optionsValue="id"
// readOnly={this.props.readOnly}
// onClick={this.props.onClick}
/>
<FieldFeedbacks for={this.props.name} className="form-control-feedback"
style={styles.errorStyle}>
<FieldFeedback when="valueMissing" />
{(this.props.required)?
<FieldFeedback when={value => /^ *$/.test(value)}>Please enter valid text</FieldFeedback>:null}
</FieldFeedbacks>
</FormGroup>
);
}
}
and the drop data looks like
dropDownList=[
{name: "NAme",id:1,key:"Key"}];
Check the documentation of the widget you are using - FormControlInput
https://github.com/tkrotoff/react-form-with-constraints ?
Usually onChange callback gets only the event as input, and you can get the value from the event. eg:
event.target.value
More info:
React: Forms
https://react-bootstrap.github.io/components/forms/
React: Handling Events
https://reactjs.org/docs/handling-events.html
Any of these two possibilities are there.
Either you have to add an attribute as="select" to <FormControlInput> and remove type attribute, or Change type="text" to type="select"

Resources