I have an input number value which I'm trying to send to my clickHandler but I've done something wrong ...
On click I want to send the value "this.state.NumberHolder" to the handler
<input value={this.state.NumberHolder} onClick={this.clickHandler} type="number" />
Doing a console.log I can see that my clickHandler is being called but I'm not getting the updated state value
clickHandler = (target) => {
console.log("targetHere", target);
this.setState({
NumberHolder: target.value
});
};
Actually, what you receive by default property is the context of the event.
So, to handle correctly the value of the input tag, you need to do this:
clickHandler = (event) => {
console.log("targetHere", event.target);
this.setState({
NumberHolder: event.target.value
});
};
And there is a big issue with your JSX, onClick is executed when the input is clicked, not changed. So, you will never receive the new input value. Use on change:
<input value={this.state.NumberHolder} onChange={this.clickHandler} type="number" />
And this should work perfectly. Check this fiddle to see it working.
I believe it should be like this:
// destructure target from the event object by wrapping it in braces
clickHandler = ({target}) => {
console.log("targetHere", target);
this.setState({
NumberHolder: target.value
});
};
But there is a bigger issue with your code. Since the value of your input will always be this.state.NumberHolder, you are simply setting the same value over and over again.
If you have a particular value you want to send on the click event, you can turn your click event into a curried function like this:
// pass in number as first argument to function that returns another anonymous function
clickHandler = (NumberHolder) => () =>{
this.setState({ NumberHolder });
};
And then on the element with the click event, pass the onClick like this:
<input onClick={this.clickHandler(3)} />
That will pass the argument in scope of the function, and allow you to access it.
Considering your comments, i believe you want to add the event on change of the input and log the value entered like this:
<input value={this.state.NumberHolder} onChange={this.clickHandler} type="number" />
clickHandler = (target) => {
console.log("target Value", target.value);
this.setState({
NumberHolder: target.value
});
};
Related
I'm working on a form that initially only shows one input field and when it is focused, it shows other inputs and the submit button.
I also want to hide all those extra fields if the form loses focus while they are empty. And this is the part that I'm not being able to implement.
This is my code: I use a controlled form and a state to handle focus.
const FoldableForm = () => {
const [formState, setFormState] = useState(defaultFormState);
const [hasFocus, setFocus] = useState(false);
const handleOnBlur = () => {
if (!formState.message.trim() && !formState.other_input.trim()) {
setFocus(false);
}
};
return (
<form
onFocus={() => setFocus(true)}
onBlur={handleOnBlur}
>
<textarea
name="message"
onChange={(e) => setFormState({ ...formState, message: e.target.value })}
/>
{hasFocus && (
<>
<input
type="text" name="other_input"
onChange={(e) => setFormState({ ...formState, message: e.target.other_input })}
/>
<button type="button">Post comment</button>
</>
)}
</form>
);
}
Currently, if I type something in the text area, setFocus(false) is never invoked, so it works as intended.
Otherwise, if I leave it empty and click on the other input field, the handleOnBlur function is called, it sets focus to false, so the form is 'minimized'.
This is expected because the blur event (from the textarea) is triggered before the focus event (from the new input field). So I tried to use setTimeout to check, after a fraction of a second if the focus event had already occurred.
To do so, I used a second state (shouldShow) that is updated in a setTimeout inside the handleOnBlue function.
setTimeout(() => {
if(!hasFocus) {
setShouldShow(false); // this should cause the form to minimize
}
}, 100);
However, according to the react lifecycle, the value of hasFocus that is passed to the setTimeout function is at the invocation time, not at execution. So setTimeout here is useless.
I also tried to use references, but I couldn't make it work.
In your case i think that the usage of the shouldShow state is redundant and you can also avoid using a timeout which may lead to bugs.
You can take advantage of the FocusEvent.relatedTarget attribute and prevent hiding the extra fields when blur from an input and focus to another happens simultaneously.
The handleOnBlur function should look like this:
const handleOnBlur = (e) => {
if (e.relatedTarget && e.relatedTarget.name === "other_input") return;
if (!formState.message.trim() && !formState.other_input.trim()) {
setFocus(false);
}
};
You can find a working example in this code sandbox.
The problem with this approach is that if you have multiple fields appearing you need to check if any of those is focused like below:
["other_input", "another_input"].includes(e.relatedTarget.name)
This behavior is because of closures in JavaScript. The value of hasFocus is not the value of the variable at the moment your callback inside setTimeout is executed. It's the value when the onBlur callback is executed.
One solution would be to use functional updates.
Define a state which holds both hasFocus and shouldShow inside:
const [state, setState] = useState({ hasFocus: false, shouldShow: false });
When you try to access the previous state using functional updates, you get the most recent value:
setTimeout(() => {
setState((state) => {
if (!state.hasFocus) {
return { ...state, shouldShow: false };
}
return state;
});
}, 100);
codesandbox
Another solution would be to debounce a function which sets the hasFocus state to false, which imo is way better.
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
I am trying to change the input value on dynamically added input fields.
Each input field value is set to a state value which is made of an array.
Seems like there should be a simple solution for this.. But I can't just figure it out.
JSfiddle:
https://jsfiddle.net/o51Lkvm6/1/
handleInputChange = (e) => {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
return (
<div>
{ this.state.data.map((d, index) =>
<input name={d.Name} type="text" className="form-control"
value={d.Name} onChange={this.handleInputChange} />
)}
</div>
);
}
Update:
Is it possible to solve this without having to use defaultvalue? Since React does not recommend "Uncontrolled Components"?
First of all there are couple issues with your code:
You forgot to bind your handler method or use arrow function to
preserve this context of a class. To fix that you can either put this
in Test constructor:
this.handleInputChange = this.handleInputChange.bind(this)
or modify your existing function to:
handleInputChange = e => {};
Input value should actually use the value which
corresponds to current item from state, like that:
value={this.state.data[index]["Name"]}
Later to access proper item in your stateData you have to somehow
store that index in the input. I did this by assigning it to
data-index attribute. Also you forgot to include key prop:
<input
key={d.ID}
data-index={index}
name={d.Name}
type="text"
className="form-control"
value={this.state.data[index]["Name"]}
onChange={this.handleInputChange}
/>
In your actual handleInputChange you were not targeting the correct
thing. You need to first get the appropriate item from the array and
then modify the name. I did it by copying the actual state and later
assigning it:
handleInputChange = e => {
const stateDataCopy = this.state.data.slice();
const objectCopy = Object.assign({}, stateDataCopy[e.target.dataset.index]);
objectCopy["Name"] = e.target.value;
stateDataCopy[e.target.dataset.index] = objectCopy;
this.setState({ data: stateDataCopy });
};
Here you can find working example:
ok I fixed it for you
do these 2 things
handleInputChange(e){ make this an arrow function so it has the concept of this like so: handleInputChange = (e) => {
and use defaultValue instead of value in the input
updated fiddle for you: https://jsfiddle.net/a17gywvp/1/
I have an input text and I can't write in it !
I tried to control it like that :
handleUserInput(e){
console.log('ok')
this.setState({
newPlayer: e.target.value
})
}
<input type="text" className="inputTexte" placeholder="Username" value={this.state.newPlayer} onChange={e => this.handleUserInput(e.target.value)} />
But even if I just put an input like that :
<input type="text" />
I can't write in it.
It's drive me crazy...
Do you know what I am doing wrong ?
Thanks
handleUserInput(e){
console.log('ok')
this.setState({
newPlayer: e.target.value
})
}
Most probably you have to bind this value to handler function, inside your constructor,
this.handleUserInput= this.handleUserInput.bind(this);
Or Change the handler function to fat arrow function like below
const handleUserInput = (e)=>{//write your code inside}
But before this please the check the developer console and update the error here
Define like this
handleUserInput(value){
console.log('ok')
this.setState({
newPlayer: value
})
}
because you already passed input value e.target.value
onChange={e => this.handleUserInput(e.target.value)}
You are setting state the state wrong, you are passing e.target.value to handleChange function and again using event.target.value while setting state
change your input as follows and try
<input type="text" className="inputTexte" placeholder="Username" value={this.state.newPlayer} onChange={e => this.handleUserInput(e)} />
or change handleUserInput as follows
handleUserInput(value){
console.log('ok')
this.setState({
newPlayer: value
})
}
State does update synchronously. This means that when React actually performs the update, it is at a later time and the value of the event is lost. This is because React uses a synthetic event wrapper to wrap all DOM events to ensure compatibility across all browsers and this wrapper is emptied after a while to be reused. My proposal is to simply store the event value at a variable, which will ensure it retains its value when the state update does happen.
handleUserInput(e){
console.log('ok')
const eventValue = e.target.value;
this.setState({
newPlayer: eventValue
})
}
Also, you call the function with event.target.value as parameter, and then you access the .target.value again, so it does not work.
onChange={e => this.handleUserInput(e)}
And as described above, inside handleUserInput access it via e.target.value, the way you had it previous implied that inside the function you were trying to access e.target.value.target.value.
It have a code erro, you are pass the event target value in the function but is expected the event only
try:
handleUserInput(e){
console.log('ok')
this.setState({
newPlayer: e.target.value
})
}
<input type="text" className="inputTexte" placeholder="Username" value={this.state.newPlayer} onChange={this.handleUserInput} />
In vanilla js if we want to add a listener to a dom we can do like this
<div onclick="myFunction(this)">...</div>
but wen i do it inside react component this is refers to component class itself. how to handle something like this?
You should pass event in your function, and then you access DOM element with e.target.
For example if you want to handle input change, you can do something like this:
const [values, setValues] = useState({
username: '',
});
const handleChange = event => {
event.persist();
setValues(values => ({
...values,
[event.target.id]: event.target.value
}));
};
event.target.id is the id of DOM element and event.target.value is the value of the same element, which in this case is input.
<input
id="username"
type="text"
placeholder="Enter username"
onChange={handleChange}
value={values.username}
required
></input>
Also
Note:
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.
In react u should use onClick (with capital C) and pass the event to your function
<div onClick={(e) => {myfunction(e)}}>...</div>
Then in your function, use event.target to get the clicked tag.
const myfunction = (event) => {
let id = event.target.id;
}
In your context, this refers to the DOM target element, so you'd define your function as:
function myFunction(target) { /* Code here */ }
But in react, when you define an input:
<input onClick={myFunction} />
Your function is given an event not a target, so your function is defined as:
function myFunction(event) { console.log(event.target) }