I have a simple React component which has one email input field and a checkbox like this:
interface MyProps {
onSubmit?: (form: any) => void;
}
class Preferences extends React.Component<MyProps> {
state = {
primaryEmailCheckbox: false,
primaryEmail: "",
};
onPrimaryEmailChange = e => {
this.setState({ primaryEmail: e.target.value });
let checkbox = document.getElementById("primaryEmailCheckId") as HTMLInputElement;
checkbox.disabled = false; //<<< checkbox is null. lets say this is line 18
}
}
render() {
return (
<StaticContent />
<h3>Email Address</h3>
<div className="ui mini icon input">
<input type="email" value={this.state.primaryEmail} placeholder="Enter email..." onChange={this.onPrimaryEmailChange} />
</div>
<div className="ui checkbox">
<input type="checkbox" disabled={true} id="primaryEmailCheckId" onChange={e => this.setState({ primaryEmailCheckbox: e.target.checked })} /><label> Continue to receive further email communications from Us </label>
</div>
);
}
}
export default Preferences;
When anyone enters any thing on the email field, the checkbox becomes visible for user to check it or keep it unchecked.
When I run the application, it works as expected. But when I test it, it says checkbox is null (at line 18), so you cannot set disabled on that.
This is a test to test Preferences Component:
import * as React from "react";
import { shallow } from "enzyme";
import Preferences from "../../components/Preferences";
test("Preferences shows email and checkbox", () => {
const wrapper = shallow(<Preferences onSubmit={() => { }} />);
wrapper.find("input").at(0).simulate("change", {
target: {
value: "a#b.c",
}
});
expect(wrapper.find("input").state().value).toEqual("a#b.c");
});
This throws Null exception at line 18. The thing is, the value a#b.c is passed correctly and I verified it by placing log statements.
But, when I try to change the value of input type email, it calls a onChange method which tries to access (and change) the value of another input field.
I don't know how to change the value of 2nd input type which is a checkbox. How I can I make it work? Any help is appreciated.
This is because the shallow(...) rendering method provides a limited set of interaction patterns and document.getElementById(...) is not one of them. You should be able to get what you want using the following:
const wrapper = mount(<Preferences />, { attachTo: document.body });
(Docs for the above code. You can swap out document.body for the relevant equivalent if you're using something like JSDOM or similar).
That said... using document.getElementById at all is a huge red-flag in React development. Because React lets you interact with a virtual DOM and handles the application of that to the real DOM, fiddling with the real one yourself is a great way to end up with all sorts of bugs. A much better option would be to use refs to access the checkbox in the "React way", or just make checkboxEnabled: boolean part of your state and update it inside your onPrimaryEmailChange() method.
Related
export default function Form() {
const [user, setUser] = useState({
name: "",
numOfQs: 0
})
console.log(user)
function handleUserDataChange(event) {
setUser(prevUser => {
return {
...prevUser,
[event.target.name]: event.target.value
}
})
}
return (
<>
<input
type="text"
placeholder="username"
name="name"
value={user.name}
onChange={handleUserDataChange} />
<input
type="number"
name="numOfQs"
value={user.numOfQs}
onChange={handleUserDataChange} />
</>
)}
I was trying to build my form using react, and when I tried to use input[type: number] on the form field it was giving me this error, don't know why. I was reading through react docs about forms, and everything from the checkbox, radio buttons, textarea was all working fine. but when I used an input element of the type number, I got the following error.
*!Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property target on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist(). See fb.me/react-event-pooling for more information.
so, the problem only arises when an input of type "number" is introduced. when I remove it all of my other form elements work fine.
I'm still in the learning phase of react. please help me out.
This happened because the event that passed into the function is used as an asynchronous event.
To fix this, decompose the event object
function handleUserDataChange(event) {
const { name, value } = event.target;
setUser(prevUser => {
return {
...prevUser,
[name]: value
}
})
}
What is the best way to test the value of an <input> element in dom-testing-library/react-testing-library?
The approach I've taken is to fetch the raw input element itself via the closest() method, which then gives me direct access to the value attribute:
const input = getByLabelText("Some Label")
expect(input.closest("input").value).toEqual("Some Value")
I was hoping that there was a way I could this without having to directly access HTML attributes. It didn't seem like it was in the spirit of the testing library. Perhaps something like the jest-dom toHaveTextContent matcher matcher:
const input = getByLabelText("Some Label")
expect(input).toHaveTextContent("Some Value")
UPDATE
Based on request in the comments, here is a code example showing a situation where I felt the need to test the value in the input box.
This is a simplified version of a modal component I built in my app. Like, extremely simplified. The whole idea here is that the modal opens up with the input pre-filled with some text, based on a string prop. The user can freely edit this input and submit it by pressing a button. But, if the user closes the modal and then reopens it, I would like to have the text reset to that original string prop. I wrote a test for it because a previous version of the modal DID NOT reset the input value.
I'm writing this in TypeScript so that the types of each prop are very clear.
interface Props {
onClose: () => void
isOpen: boolean
initialValue: string
}
export default function MyModal({ onClose, isOpen, initialValue }) {
const [inputValue, setInputValue] = useState(initialValue)
// useEffect does the reset!
useEffect(() => {
if (!isOpen) {
setNameInput(initialValue)
}
}, [isOpen, initialValue])
return (
<SomeExternalLibraryModal isOpen={isOpen} onClose={onClose}>
<form>
<input
value={inputValue}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setInputValue(e.target.value)
}
/>
<button onClick={onClose}>Cancel</button>
</form>
</SomeExternalLibraryModal>
)
}
You are right in being suspicious of your testing method in regards to how this testing library wants you to test. The simplest answer to this question would be to use the getByDisplayValue query. It will search for an input, textarea, or select that has the value you are attempting to find. For example, using your component as an example, if I was trying to verify that inputValue = 'test', I would search like
expect(screen.getByDisplayValue('test')).toBeInTheDocument();
That is all you need to do. I assume your test is only rendering the MyModal component. Even if you have multiple inputs, it doesn't matter in regards to testing philosophy. As long as the getByDisplayValue finds any input with that value, it is a successful test. If you have multiple inputs and want to test that the exact input has the value, you could then dig into the element to determine it is the correct input:
note: you will need jest-dom for this to work.
expect(screen.getByDisplayValue('test')).toHaveAttribute('id', 'the-id');
or (without jest-dom):
expect(screen.getByDisplayValue('test').id).toBe('the-id');
You can of course search for any attribute you like.
One final alternative for testing the value is to find the input by role. This won't work in your example's case unless you add a label and affiliate it to your input through the htmlFor attribute. You could then test it like such:
expect(screen.getByRole('input', { name: 'the-inputs-id' })).toHaveValue('test');
or (without jest-dom):
expect(screen.getByRole('input', { name: 'the-inputs-id' }).value).toBe('test');
This I believe is the best way to test for the value while making sure the correct input has the value. I would suggest the getByRole method, but again, you will need to add a label to your example.
You can use screen.getByDisplayValue() to get the input element with a displayed value and compare it to your element.
type TestElement = Document | Element | Window | Node
function hasInputValue(e: TestElement, inputValue: string) {
return screen.getByDisplayValue(inputValue) === e
}
In your test:
const input = screen.getByLabelText("Some Label")
fireEvent.change(input, { target: { value: '123' } })
expect(hasInputValue(input, "123")).toBe(true)
expect(screen.getByLabelText("Name")).toHaveValue("hello"); - this gets you the value for the input :)
<label class="label" for="name">
Name
</label>
<div class="control ">
<input
class="input"
for="name"
id="name"
name="name"
value="hello"
/>
</div>
Test:
userEvent.type(screen.getByLabelText("Name"), "hello")
await waitFor(() => {
expect(screen.getByLabelText("Name")).toHaveValue("hello");
});
Using #testing-library/dom (or any of the wrapped libraries here)
You can do:
expect(inputField).toHaveDisplayValue('some input value');
Full example:
test('should show input with initial value set', async () => {
render(<Input type="text" value="John Doe" data-testid="form-field-firstname" />);
const inputField = await screen.findByTestId(`form-field-firstname`);
await waitFor(() => expect(inputField).toHaveDisplayValue('John Doe')));
});
There is very clean way to test it using testing library.
//In describe
const renderComponent = (searchInputValue, handleSearchInputValue) => {
const wrapper = render(<yourComponentWithInput
value={searchInputValue}
onChange={handleSearchInputValue}
/>);
return wrapper;
};
//In test
const mockHandleSearchInputValue = jest.fn();
const { getByLabelText } = renderComponent('g', mockHandleSearchInputValue);
const inputNode = getByLabelText('Search label'); // your input label
expect(inputNode.value).toBe('s'); // to test input value
fireEvent.change(inputNode, { target: { value: 'su' } }); // triggers onChange event
expect(mockHandleSearchInputValue).toBeCalledWith('su'); // tests if onChange handler is called with proper value
Please consider a following case:
I have created a "baseUser" component which is a form having three fields username, password and name. Now I want this form in three different applications : Application1, Application2 and Application3.
In Application1, User component should use this baseUser component but want only two fields (username, password) from baseUser state and should also have two additional fields which is first-name and last-name.
In Application2 and Application3, User component should work same as the baseUser.
Also all the actions, events, states should be able to work alone and be able to overridden.
The render method should also be overridden as their can be different UIs for different applications.
How can we achieve this functionality using react components? Does inheriting "baseUser" in applications using "extends" cause any issue (Or is it correct way to do it)?
React does not use Inheritance. You can't extend a react component. React basically work on the Composition. Composition means, creating small parts and combine them together to make a complete part.
Now, in your situation. There are only 3 input fields into your baseUsr component and you want to use only two of them into your application1. Simply you can't do this. You have to render complete baseUsr component.
You are getting the concept of components wrong. Think about components like a function or part of UI that is abstract or can be used in standalone.
For example, you can create a Header component because it can be used in isolation and can be used on multiple pages. But an input field in a form can not be used in isolation.
This confusion is created because you create components just like classes in javascript but they are the basic building block of UI.
For more information read Composition VS inheritance on React docs
You can write your baseUser this way:
class BaseUserForm extends Component {
handleChange = ({ currentTarget: input }) => {
const data = { ...this.state.data };
data[input.name] = input.value;
this.setState({ data }); //state is in inheriated form.
};
handleSubmit = e => {
e.preventDefault();
//Form validation
if (!errors) this.doSubmit();
};
renderInput = (name, label, type = "text") => {
return (
<Input
name={name}
label={label}
type={type}
error={this.state.errors[name]}
value={this.state.data[name]}
onChange={this.handleChange}
/>
);
};
renderButton = (label, type = "submit") => {
return (
<button
type={type}
className="btn btn-primary"
// disabled={this.validate()}
>
{label}
</button>
);
};
}
export default BaseUserForm;
Then depending on your Application, you can add/remove input fields as needed.
class AppForm extends BaseUserForm{
state={
data:{username:"",
password:""}
};
doSubmit = () => {
//Do your submit here.
};
render(){
return(
<form onSubmit={this.handleSubmit}>
{this.renderInput("username", "User Name")}
{this.renderInput("password", "Password")}
//Add more field as needed.
</form>
);
}
}
Lets imagine we want an input for a "product" (stored in redux) price value.
I'm struggle to come up with the best way to handle input constraints. For simplicity, lets just focus on the constraint that product.price cannot be empty.
It seems like the 2 options are:
1: Controlled
Implementation: The input value is bound to product.price. On change dispatches the changePrice() action.
The main issue here is that if we want to prevent an empty price from entering the product store, we essentially block the user from clearing the input field. This isn't ideal as it makes it very hard to change the first digit of the number (you have to select it and replace it)!
2: Using defaultValue
Implementation: We set the price initially using input defaultValue, that allows us to control when we want to actually dispatch changePrice() actions and we can do validation handling in the onChange handler.
This works well, unless the product.price is ever updated from somewhere other than the input change event (for example, an applyDiscount action). Since defaultValue doesn't cause rerenders, the product.price and the input are now out of sync!
So what am I missing?
There must be a simple & elegant solution to this problem but I just can't seem to find it!
What I have done in the past is to use redux-thunk and joi to solve input constraints/validation using controlled inputs.
In general I like to have one update action that will handle all the field updating. So for example if you have two inputs for a form, it would looks something like this:
render() {
const { product, updateProduct } = this.props;
return (
<div>
<input
value={product.name}
onChange={() => updateProduct({...product, name: e.target.value})}
/>
<input
value={product.price}
onChange={() => updateProduct({...product, price: e.target.value})}
/>
</div>
)
}
Having one function/action here simplifies my forms a great deal. The updateProject action would then be a thunk action that handles side effects. Here is our Joi Schema(based off your one requirement) and updateProduct Action mentioned above. As a side note, I also tend to just let the user make the mistake. So if they don't enter anything for price I would just make the submit button inactive or something, but still store away null/empty string in the redux store.
const projectSchema = Joi.object().keys({
name: Joi.number().string(),
price: Joi.integer().required(), // price is a required integer. so null, "", and undefined would throw an error.
});
const updateProduct = (product) => {
return (dispatch, getState) {
Joi.validate(product, productSchema, {}, (err, product) => {
if (err) {
// flip/dispatch some view state related flag and pass error message to view and disable form submission;
}
});
dispatch(update(product)); // go ahead and let the user make the mistake, but disable submission
}
}
I stopped using uncontrolled inputs, simply because I like to capture the entire state of an application. I have very little local component state in my projects. Keep in mind this is sudo code and probably won't work if directly copy pasted. Hope it helps.
So I think I've figure out a decent solution. Basically I needed to:
Create separate component that can control the input with local state.
Pass an onChange handler into the props that I can use to dispatch my changePrice action conditionally
Use componentWillReceiveProps to keep the local value state in sync with the redux store
Code (simplified and in typescript):
interface INumberInputProps {
value: number;
onChange: (val: number) => void;
}
interface INumberInputState {
value: number;
}
export class NumberInput extends React.Component<INumberInputProps, INumberInputState> {
constructor(props) {
super(props);
this.state = {value: props.value};
}
public handleChange = (value: number) => {
this.setState({value});
this.props.onChange(value);
}
//keeps local state in sync with redux store
public componentWillReceiveProps(props: INumberInputProps){
if (props.value !== this.state.value) {
this.setState({value: props.value});
}
}
public render() {
return <input value={this.state.value} onChange={this.handleChange} />
}
}
In my Product Component:
...
//conditionally dispatch action if meets valadations
public handlePriceChange = (price: number) => {
if (price < this.props.product.standardPrice &&
price > this.props.product.preferredPrice &&
!isNaN(price) &&
lineItem.price !== price){
this.props.dispatch(updatePrice(this.props.product, price));
}
}
public render() {
return <NumberInput value={this.props.product.price} onChange={this.handlePriceChange} />
}
...
What i would do in this case is to validate the input onBlur instead of onChange.
For example consider these validations in the flowing snippet:
The input can't be empty.
The input should not contain "foo".
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myVal: '',
error: ''
}
}
setError = error => {
this.setState({ error });
}
onChange = ({ target: { value } }) => {
this.setState({ myVal: value })
}
validateInput = ({ target: { value } }) => {
let nextError = '';
if (!value.trim() || value.length < 1) {
nextError = ("Input cannot be empty!")
} else if (~value.indexOf("foo")) {
nextError = ('foo is not alowed!');
}
this.setError(nextError);
}
render() {
const { myVal, error } = this.state;
return (
<div>
<input value={myVal} onChange={this.onChange} onBlur={this.validateInput} />
{error && <div>{error}</div>}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Edit
As a followup to your comments.
To make this solution more generic, i would pass the component a predicate function as a prop, only when the function will return a valid result i would call the onChange that passed from the parent or whatever method you pass that updating the store.
This way you can reuse this pattern in other components and places on your app (or even other projects).
I have given someone else's React project which is using the React-Redux-Form (this is not the redux-form) and there is a need to enable a checkbox which is on another React Component based upon a value being entered in a textbox in the React-Redux-Form. By defult the checkbox is disabled and should only be enabled if a value is present.
Searching on the Internet have failed to find an example in which to use. Can anyone help?
Here is a very rough example, and is not intended to run - simply show the flow of how to do it:
The component with the input field might have something like:
//1) this is setting a local state, when the user submits you can send it to redux so that it is available across the app.
this._saveInputToLocal() {
var emailRegEx = /^.+#.+\..+$/i;
this.setState({
email: e.target.value
}, function() {
}.bind(this));
},
//2) when the user clicks submit - send the input to redux
//lets assume this is saved into a property on state called `formInput`:
this._saveInputToRedux() {
const = { dispatch }
dispatch(saveUserInput(this.state.email))
},
render: function(){
return (
<div className={`field email ${css(styles.emailContainer)}`}>
<input
type='email'
placeholder={ 'helloworld#code.com' }
onChange={this._saveEmailToState}
/>
</div>
<button type='submit' onClick={this._saveInputToRedux}>Submit</button>
)
}
});
So you see, you have a few things: a function that updates a local state, and one that handles the submit - where the value of the local state fires and action that will store that value in redux. Don't forget to import connect so that you have dispatch available to you on props, and also don't forget to import your action into the component.
Now, In the other component that has the checkbox:
// 3) assuming you have mapped your redux state to your props you make a function that checks for a valid input value:
this._hasValidValue() {
const { formInput } = this.props
return formInput && formInput !== null && formInput !== undefined && formInput.length > 0
}
//here in the render you display either an enabled or disabled checkbox based on if you have a valid input from the other component. Because this is based on state, this component will re-render when state is updated (goes from invalid to valid or vice versa)
render: function(){
return (
<div className="checkbox">
{ hasValidValue()
? <input type="checkbox" name="some-name" />
: <input type="checkbox" name="some-name" disabled/>
}
)
}
});