React use form for img radio buttons - reactjs

I am trying to create a rock, paper and scissor game in react. I would assume that it makes sense to use a form for this, I would atleast use a form haven't it been for react.
I figured that the simplest way of doing this, would be three radio inputs and a submit.
However, since I want to use three pictures as the actual radio buttons. Would it even make sense to use a form since react aims to take the state out of the form. This is the point in my code where i realized that I might be on a sidetrack.
onChangeHandler = (event) => {
this.setState({ [event.target.name]: event.target.value });
}
render() {
return (
<div>
<div>
<form>
<input type="radio" value="Rock" name="gender" onChange={this.onChangeHandler} /> Rock
<input type="radio" value="Paper" name="gender" onChange={this.onChangeHandler} /> Paper
<input type="radio" value="Scissor" name="gender" onChange={this.onChangeHandler} /> Scissor
<input type="submit" value="Submit" onClick = () => {submit()}/>
</form>
</div>
<div>
<img id="r" value="rock" src="http://www.jerrylow.com/demo/rps/rock.svg" alt="a rock" />
<img id="p" value="paper" src="http://www.jerrylow.com/demo/rps/paper.svg" alt="a piece of paper" />
<img id="s" value="scissor" src="http://www.jerrylow.com/demo/rps/scissor.svg" alt="a scissor" />
</div>)
}
Should I use this form, even though the form serves no particular purpose. If yes, how should i integrate it with the img elements?

You have a few ways of going about it. You can forgo the form entirely and attach onClick listeners to each of the images that would modify the state when clicked and then have a button that when clicked would call your submit function.
Or, if you wish to retain the form, you could either wrap the radio buttons and images in labels, hide the radio buttons such that when the image is clicked, it would trigger the onChange. Or, you could specify a for prop on the label that matches the id of a radio button and have the image in that and it would behave as the previously described, something like
<input type="radio" id="myButton" onChange={handleChange} />
<label for="myButton"><img src="img.png" /></label>
I guess it really comes down to the solution you want because either way would be fine. I would personally prefer not using a form for this scenario purely because it's not that necessary and the code would be smaller.

Related

Testing radio inputs with React Testing Library - no form control was found associated to that label

I am testing several radio inputs in my form using React Testing Library. Most of them have labels of yes/no so I cannot use findByLabelText.
The full error I am getting is: Found a label with the text of: yes, however no form control was found associated to that label. Make sure you're using the "for" attribute or "aria-labelledby" attribute correctly. .
Here is one of my failing tests:
test('Sealing Properly radio input should exist, be unchecked, and be clickable', async () => {
render(
<Component />
);
const sealingProperlyRadioGroup = await screen.findByTestId('sealingProperly');
const sealingProperlyRadio = await within(sealingProperlyRadioGroup).findByLabelText('yes');
expect(sealingProperlyRadio).toBeInTheDocument();
expect(sealingProperlyRadio).not.toBeChecked();
fireEvent.click(sealingProperlyRadio)
expect(sealingProperlyRadio).toBeChecked();
});
And this is the html that gets outputted after the error:
<div
class="radioGroup mb-3"
data-testid="sealingProperly"
>
<label
class="radio"
for="sealingProperlyYes"
>
<input
id="sealingProperlyYes"
name="sealingProperly"
required=""
type="radio"
value="yes"
/>
Yes
</label>
<label
class="radio ms-5"
for="sealingProperlyNo"
>
<input
id="sealingProperlyNo"
name="sealingProperly"
required=""
type="radio"
value="no"
/>
No
</label>
</div>
I think there are a couple of problems:
The text you are querying is "yes" while in your component it is capitalized. There are ways to ignore case sensitivity in React Testing Library but by default it is case sensitive. You can learn more about it here
In React the for attribute is written slightly differently - htmlFor="your-id" so I think your labels don't get associated with their inputs correctly. source

URL search parameters gets replaced - Remix Run

I'm working on a search UI where I have quite a few filters which I want as URL parameters when someone selects/checks the options. I've used the technique as advised on the Remix.run docs to come up with multiple forms within the filters. Each time a group of Filters gets submitted, the selected old parameters get disappeared. Heres my code,
<Panel header="Status" key="status">
<Form
name="search"
action='/search'
method="get"
onChange={(e) => submit(e.currentTarget, { replace: false })}
>
<ul>
<li>
<Checkbox
name="status"
value="buy_now"
defaultChecked={status.includes('buy_now')}
>
Buy Now
</Checkbox>
</li>
<li>
<Checkbox
name="status"
value="on_auction"
defaultChecked={status.includes('on_auction')}
>
On Auction
</Checkbox>
</li>
</ul>
</Form>
</Panel>
<Panel header="Price" key="price">
<Form name="search" action='/search' method="get">
<Select
name="blockchain"
value={
blockchain
? options.filter((a) => a.value === blockchain)
: undefined
}
options={options}
placeholder="Blockchain"
type="white"
/>
<div className="d-flex align-center price">
<TextInput
value={min ? min : undefined}
name="min"
placeholder="Min"
/>
<span>to</span>
<TextInput
value={max ? max : undefined}
name="max"
placeholder="Max"
/>
</div>
<button
onClick={(e) => {
e.stopPropagation()
submit(e.currentTarget, { replace: false })
}}
className="btn primary-btn btn-lg w-100"
>
Apply
</button>
</Form>
</Panel>
How Can I get around this to have all the parameters without having to manage them on my own using React state?
Edit:- I want the first filter to be submitted automatically and the latter to be submitted on a button click.
Bit of a UI of what I'm trying to achieve,
Answer: After investing enough time to look through for shortcuts, finally understood that it's not one of the magic that remix.run does. use something like formik and update the state imparatively.
When you submit a form, the only values included are the one under the submitted form. The values from any other form are not included (fortunately!).
So I'd use a single Form with all the inputs under it (checkboxes as well as text inputs).
Then instead of a onChange on the Form, you can add something like an onChange handler on the checkboxes and submit the form inside imperatively (using a ref click on the submit button or something, I think using a ref on the form you need to submit all values in the submit function so a button ref click may be simpler).
Keep in mind that if you want to "restore" the field values after submitting, you need to return them from the loader function like this:
// Loader function
const url = new URL(request.url);
return {
results: [...],
values: Object.fromEntries(url.searchParams.entries())
};
Then in the component, use values from useLoaderData:
<input type="text" name="max" defaultValue={values.max || ""}/>
Added benefit: if you come back to this page (by clicking browser back for example), your search parameters and search results are still there!
I actually put up a stackblitz for you but I lost all my changes :(
It seems like you could just keep all fields in a single form and submit that form when the submit button is pressed.
Then onChange, check if the target's name is 'status', and submit the form anyway.
export default function App() {
const submit = (form) => {
form.submit();
};
return (
<form
name="search"
action="/search"
method="get"
onChange={(e) => {
if (e.target.name === "status") {
submit(e.currentTarget);
}
}}
>
<fieldset>
<legend>status</legend>
<label>
<input type="checkbox" name="status" value="buy_now" />
buy_now
</label>
<label>
<input type="checkbox" name="status" value="on_auction" />
on_auction
</label>
</fieldset>
<fieldset>
<legend>price</legend>
<label>
<div>blockchain</div>
<select name="blockchain">
<option value="option_1">Blockchain Option 1</option>
<option value="option_2">Blockchain Option 2</option>
</select>
</label>
<label>
min <input type="number" name="min" />
</label>
<label>
max <input type="number" name="max" />
</label>
</fieldset>
<button type="submit">Apply</button>
</form>
);
}
demo
Note: not sure what your motivation is to want to separate this into separate forms, but I think the magic you're referring to is that server state, URLSearchParams, FormData and UI are all aligned because they are using the same underlying data using only common web APIs.

How To Check checked value of a checkbox using Reactstrap

I've got a React web app using bootstrap 4 and Reactstrap. I want to have several checkboxes in a toolbar and I can't figure out how to get anything out of the onChange event besides "on".
Here is my code that continues to write "on" to the console every time I click the checkbox. The checkbox does toggle on and off.
<li className="show-sessions">
<FormGroup check>
<Label check>
<Input
type="checkbox"
onChange={(e) => console.log(e.target.value)}
/>{" "}
<strong>Show Favorites</strong>
</Label>
</FormGroup>
</li>
Is there another way to make a checkbox in Reactstrap? The doc's are very limited.
you can check checked state rather than value; it is a boolean
<Input
type="checkbox"
onChange={(e) => console.log(e.target.checked)}
/>

React validation on multiple checkboxes

anyone can point me to an example of a react multiple checkboxes validation? I have some questions, every one of them has a checkbox when it's done and when all are checked, a continue button must be activated.. I know how to do this with only one checkbox, but don't know how to handle more of them. I would rather do this in plain react and not by installing any package. Any help will be appreciated.
You can control all your inputs using useState. Example for two inputs.
import React, { useState } from "react"
const ControlledCheckboxes = () => {
const [ firstCheckbox, setFirstCheckbox ] = useState(false);
const [ secondCheckbox, setSecondCheckbox ] = useState(false);
return(
<form>
<div >
<div className="form-check">
<input type="checkbox" id="first" className="form-check-input"
onClick={()=>setFirstCheckbox(!firstCheckbox)}
value={firstCheckbox}
/>
<label className="form-check-label" htmlFor="first">FIRST</label>
</div>
<div className="form-check">
<input type="checkbox" id="second" className="form-check-input"
onClick={()=>setSecondCheckbox(!secondCheckbox)}
value={secondCheckbox}
/>
<label className="form-check-label" htmlFor="second">SECOND</label>
</div>
</div>
<button type="submit" className="btn btn-outline-success" disabled={ !(firstCheckbox && secondCheckbox) } >SUBMIT</button>
</form>
)
};
You can achieve this by using controlled inputs.
Basically you would make the value of the checkboxes correspond to variables in the state of your component. After that it is as simple as if (boolean1 && boolean2) with a conditionally rendered save button.
Controlled inputs
Conditional rendering
You can achieve most of what you want with just HTML.
<form>
<input type="checkbox" required />
<input type="checkbox" required />
<button>submit</button>
</form>
The form cannot be submitted until all checkboxes are checked.
Let's say that's not enough, you want to access the form validity in your render logic to apply styles or whatever so you need component state.
const [valid, setValid] = useState(false);
All form control change events bubble up to their form (unless explicitly stopped). We can spy on form control changes by adding a change event listener that updates component state to the form element.
event => setValid(event.target.form.checkValidity())
If your form isn't as simple as my example and you need to specifically check certain checkboxes you can find all form controls in event.target.form.elements. You can use the elements property to not even need HTML form validation.
event => setValid(Array.from(event.target.form.elements).every(
input => input.type !== 'checkbox' || input.checked
))
You can then pass valid as a prop to your submit button.

Reactjs which one is better form onChange or input onChange?

There are two ways you can get input changes in your React app.
One is by using
<input type="text" onChange={this.handleChange} />
The other was is
<form onChange={this.handleChange} onSubmit={this.handleChange} />
...
</form>
When you should use first one and when the other one.
The reason that there are two ways is because there are more than the two ways. You can do this too:
<div onChange={this.handleChange}>
<form>
<input />
</form>
</div>
I'd argue that the first approach is better because the handler receives the event as early as possible and possibly because the binding between the input and the component state is encoded within the render function, but that depends on what the handler would look like.

Resources