Here is the test for the :
it("changes the state when body input is changed", () => {
render(<CommentForm />)
// let input = screen.getByLabelText("Your Name");
let input = screen.getByRole("textbox", { name: "Your Comment" });
fireEvent.change(input, { target: { value: "MyComment" } });
expect(input.value).toEqual("MyComment");
});
With the commented line it works (when I search with getByLabelText). Here is what I am getting when I try to find it with getByRole:
Unable to find an accessible element with the role "textbox" and name "Your Comment"
Here are the accessible roles:
document:
Name "":
<body />
--------------------------------------------------
generic:
Name "":
<div />
Name "":
<div
class="input-group"
/>
Name "":
<div
class="input-group"
/>
--------------------------------------------------
heading:
Name "Post a Comment":
<h2 />
--------------------------------------------------
textbox:
Name "Your Name":
<input
id="author-name"
name="author"
type="text"
value=""
/>
Name "":
<textarea
cols="30"
id="body"
name="body"
rows="10"
/>
So it seems like the name is empty but I am not sure why that is.
And here is the actual component:
import React from "react";
import useInput from "../hooks/useInput";
const CommentForm = ({ onSubmit }) => {
const { value: author, reset: resetAuthor, bind: bindAuthor } = useInput("");
const { value: body, reset: resetBody, bind: bindBody } = useInput("");
const handleSubmit = (e) => {
e.preventDefault();
onSubmit({ author, body }, resetInputs);
};
const resetInputs = () => {
resetAuthor();
resetBody();
};
return (
<form onSubmit={handleSubmit}>
<h2>Post a Comment</h2>
<div className="input-group">
<label htmlFor="author-name">Your Name</label>
<input id="author-name" type="text" name="author" {...bindAuthor} />
</div>
<div className="input-group">
<label htmlFor="body">Your Comment</label>
<textarea
id="body"
name="body"
cols="30"
rows="10"
{...bindBody}
></textarea>
</div>
<button type="submit">Submit</button>
</form>
);
};
export default CommentForm;
Can anyone see the issue here. I am sure it should work and I don't see any reason why the author input can be grabbed with getByRole and name but this one can't.
I came across this problem when using a React Hook Form Controller for a Material UI TextField. Even though my TextField element had a label prop, no aria-label value was present in the HTML until I added the inputProps={{ "aria-label": "Email" }} prop to the TextField component.
Note: the aria-label is the value that the getByRole "name" actually refers to in this case. See docs for details.
<Controller
control={control}
defaultValue=""
id="email"
name="email"
render={({ onChange, ref }) => (
<TextField
autoComplete="email"
className={classes.textField}
error={Boolean(errors.email)}
fullWidth
helperText={errors.email && errors.email.message}
inputProps={{ "aria-label": "Email" }}
inputRef={ref}
label="Email"
onChange={onChange}
/>
)}
rules={{
pattern: {
message: "invalid email address",
value: EMAIL_REGEX,
},
required: true,
}}
/>
I think, your textarea has name=body, and you're looking for name=Your Comment. Try to use body instead.
Your use of <label htmlFor> is incorrect. Because you've given htmlFor, id and body the same values the DOM is getting confused and assigning the label content ("Your Comment") as the name. It's working with author by accident.
So:
<label htmlFor="author-name">Your Name</label>
<input id="author-name" type="text" name="author" />
Should be able to be checked with:
screen.getByRole('textbox', {name: author})
For the textarea if you do:
<label htmlFor="body">Your Comment</label>
<textarea
id="body"
name="bodyInput"
>
Then:
screen.getByRole('textarea', {name: body})
Should give you the result you're looking for.
Notice in both cases I'm checking the element's name value in the element where id matches the value of htmlFor. I am not checking the value of the <label> text itself.
Related
i'm a beginner in React and trying to learn useState. and I have difficulties on how to get the value of input and to save the value and print it on button click
const HomePage = () => {
const [state, setState] = useState({
Name: "",
surName: "",
});
const handleChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value,
});
};
const RenderNameOC = () => {
return (
<p>
Halo {Name} {surName}
</p>
);
};
return (
<DivContainer>
<ContainerTitle>
<p>Exercise 2 - Form</p>
</ContainerTitle>
<InputContainer>
<InputArea>
<label>Name: </label>
<input type="text" value={state.Name} onChange={handleChange} />
</InputArea>
<InputArea>
<label>Surname: </label>
<input type="text" value={state.surName} onChange={handleChange} />
</InputArea>
<SubmitButton onClick={RenderNameOC}>Submit</SubmitButton>
</InputContainer>
</DivContainer>
);
};
export default HomePage;
this is my code right now and the error it gave me was 'name' and 'surname' is not defined.
my expected result is that there will be 2 input textbox with for name and surname. and when the button is clicked, it will add a new <p> below it.
Here should be state.Name and state.surName
<p>
Halo {state.Name} {state.surName}
</p>
And add name in both inputs
<input
type="text"
name="Name"
value={state.Name}
onChange={handleChange}
/>
<label>Surname: </label>
<input
type="text"
name="surName"
value={state.surName}
onChange={handleChange}
/>
But no point of returning anything RenderNameOC since onClick is a void function. Just move this template below the submit button
Demo
I am messing around with Riot's API that allows getting information of a player by the player's name. I am trying to get an API key (I only got a 24 hour key) and the target player's name from users' input.
export function PlayerSearch() {
function handlesubmit(e) {
console.log(e.target);
e.preventDefault();
}
return (
<div className='player'>
<div className='inputfield'>
<form onSubmit={handlesubmit} method='GET' autoComplete="off">
<div>
<label htmlFor="key">Your API key:</label>
<input
placeholder='Your API key'
onFocus={(e)=>{e.target.placeholder=''}}
type="text"
id="key"
name="key" />
</div>
<div>
<label htmlFor="name">Player name:</label>
<input
placeholder='Player name'
onFocus={(e)=>{e.target.placeholder=''}}
type="text"
id="name"
name="name" />
</div>
<div>
<input type='submit' />
</div>
</form>
</div>
</div>
);
}
And I got this in the console:
So how exactly do I extract the two inputs from the form?
In addition, is it possible that I can call another component and pass data as props to handle the submitted data so that the code is cleaner?
You should have a state to store your inputs:
const [formData, setFormData] = useState({ key: "", name: "" });
Then you need a function that gets called onChange of your input fields to update your state:
const onChange = (event) => {
setFormData({ ...formData, [event.target.name]: event.target.value });
};
And you need to pass that function to your input onChange property:
<input
placeholder="Your API key"
onFocus={(e) => {
e.target.placeholder = "";
}}
type="text"
name="key"
value={formData.key}
onChange={onChange}
/>
Then you can access the state in your handleSubmit:
function handlesubmit(e) {
console.log(formData);
e.preventDefault();
}
I am going to update the form with each keystroke with useState Hook this way I have to add an onChange event listener plus a function for each input and as you can imagine it's going to be lots of functions how can I avoid repeating myself?
function firstNamInput(){
}
function lastNameInput(){
}
function emailInput(){
}
function phoneInput(){
}
function addressInput(){
}
function genderInput(){
}
function jobInput(){
}
function ageInput(){
}
in react we try to collect form data on every keystroke but in the past, we used to collect form data when submit clicked. so because of this new approach, we have to listen for every keystroke on every input! isn't this crazy? yes, kind of but there is a way to go around I am going to explain it to you :
first I am going to talk about Rules you have to know about inputs:
we are going to use a useState Hook and we pass an object to it which contain:
a property for every input that's single
a property for group inputs (for example if there is checkboxes for gender we create only one property for gender)
what attributes every input should have?
name (it's going to be equal to its property in the useState)
value or checked (as a rule of thumb if the inputs gets true or false its usually checked vice versa )
onChange event
so let's create useState I am going to have a total of 7 properties in the object:
const [formData, setFormData] = React.useState(
{
firstName: "",
lastName: "",
email: "",
comments: "",
isFriendly: true,
employment: "",
favColor: ""
}
)
we need to create a function for the onChange event i am going to make it as reusable as possible it's going to get form data from each input and update it in our Hook.
function handleChange(event) {
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
now we are going to add inputs look at note 2 again and now you are ready
import React from "react"
export default function Form() {
const [formData, setFormData] = React.useState(
{
firstName: "",
lastName: "",
email: "",
comments: "",
isFriendly: true,
employment: "",
favColor: ""
}
)
function handleChange(event) {
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
function handleSubmit(event) {
event.preventDefault()
// submitToApi(formData)
console.log(formData)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
value={formData.firstName}
/>
<input
type="text"
placeholder="Last Name"
onChange={handleChange}
name="lastName"
value={formData.lastName}
/>
<input
type="email"
placeholder="Email"
onChange={handleChange}
name="email"
value={formData.email}
/>
<textarea
value={formData.comments}
placeholder="Comments"
onChange={handleChange}
name="comments"
/>
<input
type="checkbox"
id="isFriendly"
checked={formData.isFriendly}
onChange={handleChange}
name="isFriendly"
/>
<label htmlFor="isFriendly">Are you friendly?</label>
<br />
<br />
<fieldset>
<legend>Current employment status</legend>
<input
type="radio"
id="unemployed"
name="employment"
value="unemployed"
checked={formData.employment === "unemployed"}
onChange={handleChange}
/>
<label htmlFor="unemployed">Unemployed</label>
<br />
<input
type="radio"
id="part-time"
name="employment"
value="part-time"
checked={formData.employment === "part-time"}
onChange={handleChange}
/>
<label htmlFor="part-time">Part-time</label>
<br />
<input
type="radio"
id="full-time"
name="employment"
value="full-time"
checked={formData.employment === "full-time"}
onChange={handleChange}
/>
<label htmlFor="full-time">Full-time</label>
<br />
</fieldset>
<br />
<label htmlFor="favColor">What is your favorite color?</label>
<br />
<select
id="favColor"
value={formData.favColor}
onChange={handleChange}
name="favColor"
>
<option value="red">Red</option>
<option value="orange">Orange</option>
<option value="yellow">Yellow</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="indigo">Indigo</option>
<option value="violet">Violet</option>
</select>
<br />
<br />
<button>Submit</button>
</form>
)
}
I'm getting a really weird return error that on submission I randomly add an extra duplicate field somehow which is of course then undefined. The input value is also randomly copied from one of the other fields within the form.
const GameForm = () => {
const url = 'http://localhost:6060/games'
const handleInputChange = e => {
const { name, value, year, rating, developer } = e.target
setGameData({ ...gameData, [name]: value, [year]: value, [rating]: value, [developer]: value })
}
const onSubmit = (e) => {
e.preventDefault()
const { name, year, rating, developer } = gameData
if (!name || !year || !rating || !developer) return
console.log(gameData)
axios
.post(url, gameData)
.then((res) => {
setGameData(res)
}).catch((error) => console.log(error))
}
const [gameData, setGameData] = useState({ name: '', year: 0, rating: '', developer: "" })
return (
<form onSubmit={onSubmit}>
<label id="name" htmlFor="name">Name: </label>
<input type="text" name="name" placeholder="Game title" onChange={handleInputChange} />
<br />
<label htmlFor="year">Year: </label>
<input type="number" name="year" placeholder="Release year" onChange={handleInputChange} />
<br />
<label htmlFor="rating">Rating: </label>
<input type="text" name="rating" placeholder="Age rating" onChange={handleInputChange} />
<br />
<label htmlFor="developer">Developer: </label>
<input type="text" name="developer" placeholder="Developer" onChange={handleInputChange} />
<br />
<input type="submit" value="Submit"></input>
</form>
)
}
Console logged return: (I also get a 500 error obviously)
{name: "1", year: "1", rating: "1", developer: "1", undefined: "1"}
The undefined value is seemingly taken from any of the existing fields at random.
I feel like I am likely overlooking something obvious.
You are mis-using e.target. It will not have all the properties that you try to destruct from it. From the ones you list in your example code, it will only have name and value, all the other ones (rating, year, developer) will be undefined as they are not part of the event's target property.
The reason you only get one undefined value in your state object is because it keeps overriding itself when you set your state.
The property from the event target you are trying to access is name, so in your case basically e.target.name
Anyway, with that in mind the fix for your app will be quite simple:
const handleInputChange = e => {
const { name, value } = e.target
// Note: The name will hold whatever value of the name prop you put on your input.
// When you are editing the input with the name prop set to name, it will be "name"
// For the input with the name prop set to "year", it will be "year:
// For the input with the name prop set to "developer" it will be "developer" and so on.
setGameData({ ...gameData, [name]: value })
}
Here is a demo for you with the fix:
const App = () => {
const [gameData, setGameData] = React.useState({ name: '', year: 0, rating: '', developer: "" })
const handleInputChange = e => {
const { name, value } = e.target
setGameData({ ...gameData, [name]: value })
}
return (
<div>
<label id="name" htmlFor="name">Name: </label>
<input type="text" name="name" placeholder="Game title" onChange={handleInputChange} />
<br />
<label htmlFor="year">Year: </label>
<input type="number" name="year" placeholder="Release year" onChange={handleInputChange} />
<br />
<label htmlFor="rating">Rating: </label>
<input type="text" name="rating" placeholder="Age rating" onChange={handleInputChange} />
<br />
<label htmlFor="developer">Developer: </label>
<input type="text" name="developer" placeholder="Developer" onChange={handleInputChange} />
<br />
<button onClick={() => console.warn('GAME DATA OBJECT', gameData)}>Click</button>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Hi everybody Tring to make a form with Form provided by bootstap but it doesn't work.
If I insert value it doesn't let me type, without value I digit but I can't get the value.
(I have also a Gatsby theme activated)
const StandAdd = () => {
const [item, setItem] = useState({
title: "",
kindof: "",
website: "",
image01: "",
image02: "",
image03: "",
})
const { title, kindof, website, image01, image02, image03 } = item
const handleInputChange = event => {
event.preventDefault()
setItem({ ...item, [event.target.name]: event.target.value })
}
const handleSubmit = event => {
event.preventDefault()
alert(`${title} ${item} ${website}`) // always empty I gat get anything
}
return (
<Layout>
<form onSubmit={handleSubmit}>
<Form>
<Form.Group controlId="title">
{/* <Form.Label>Email address</Form.Label> */}
<Form.Control
value={titlet} // <-- this blocks the typing
type="text"
placeholder="Enter Stand's Name"
onChange={handleInputChange}
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</form>
</Layout>
)
}
export default StandAdd
Any idea?
The issue is here:
<Form.Control
value="title" // <-- this blocks the typing
type="text"
placeholder="Enter Stand's Name"
onChange={handleInputChange}
/>
here you have to provide the name attribute so that your code:
setItem({ ...item, [event.target.name]: event.target.value })
will get some value in event.target.name.
So after adding name the code will be like:
<Form.Control
type="text"
name="title" <---- name added here
placeholder="Enter Stand's Name"
onChange={handleInputChange}
/>
And don't provide a hard-coded value to input like this:
value="title"
instead use defaultValue for providing an initial value.
I think you need to provide name attribute to <Form.Control> component.