Dropdown option cannot be selected if I put the component inside my label - reactjs

Why doesn't my Dropdown component work when I put it inside my <label> tag? The dropdown is displaying the options, but if you click on one, the selection is not working/displayed.
Any suggestions?
export default function App() {
return (
<div>
<div className="mt-10 mb-3 h-6 text-md uppercase font-bold>
People
</div>
<button type="button" onClick={addInvitee}>
+Add menu
</button>
<form onSubmit={handleSubmit}>
{invited.map(({ age, email, id, location, name }, index) => (
<div key={id}>
<div className="grid grid-cols-3 gap-5">
<label className="mr-3 h-6 text-md font-bold">
Names:
<input
type="text"
value={name}
placeholder="Names"
name="name"
onChange={updateInvitee(id)}
/>
</label>
//other inputs with the exact same pattern
<label>
Choice:
<Dropdown className="w-3/5" options={CHOICE} isMulti={false} />
</label>
...//other inputs
</form>
</div>
);
}
Dropdown.jsx
import React, { useState } from "react";
import Select from "react-select";
import Tag from "./Tag";
export default function Dropdown({
className,
style,
options,
styleSelect,
defaultValue,
isMulti = false
}) {
const [selected, setSelected] = useState(defaultValue);
const styles = {
select: {
width: "100%",
maxWidth: 200
}
};
return (
<div style={style}>
{selected && isMulti === false ? (
<Tag
selected={selected}
setSelected={setSelected}
styleSelect={styleSelect}
/>
) : (
<Select
className={className}
style={styles.select}
value={selected}
onChange={setSelected}
options={options}
isMulti={isMulti}
/>
)}
</div>
);
}
Here is my CodeSandbox

Firstly here's something important to note:
Based on this image, a console snippet from sandbox, it shows that the selection happens, but it gets cleared instantly.
What's the cause for this? Hmm.. let's take a look.
Consider this code snippet:
<label>
Press the text
<br /><br />
<button onClick="console.log('button-clicked')">Button</button>
</label>
Here, the <button> is placed inside a <label>. Do you notice, that when you click on press the text, the button's onClick gets triggered? ... But why?
Well, by default, a <label> is a focusable html element, and when focused, it triggers any form control element placed inside it.
So how does this relate to your code?
You have this line of code inside your <Tag> component onClick={() => setSelected(null)} and that's where the problem is. When you pick a selection, the selected gets updated and the component re-renders and displays your <Tag> component... but the event still bubbles up tree again until it reaches the <label> element. remember, at this point, it's no longer the <Select> component shown, but the <Tag> component. And guess what? the <label> gets focused and triggers the <button> inside your <Tag> component which clears (setSelected(null)) the selected state property. Once the selected is cleared, the <Dropdown> component re-renders and the <Select> component is displayed again.
This goes on and on and on as you try to select, then the process repeats.
So, from your code... Just remove this here onClick={() => setSelected(null)} and you'll see it will work. So you just need to work around it on how to clear the selected, but I have suggested a solution below.
The Solution
In your <Dropdown> component, we should try and prevent the event from bubbling. So all you need to do is add the following onClick={e=>e.preventDefault()} in your <div>
<div style={style} onClick={(e) => e.preventDefault()}>
{selected && isMulti === false ? (
<Tag
selected={selected}
setSelected={setSelected}
styleSelect={styleSelect}
/>
) : (
<Select
className={className}
style={styles.select}
value={selected}
onChange={setSelected}
options={options}
isMulti={isMulti}
/>
)}
</div>
Here's the original sandbox with the solution applied.

Related

clear <input> tag value when opening new page from search bar

So I have 2 components. SearchBar.js and ProductDetail.js
The searchBar JS help me to search product from my header. And when clicked it will redirect to another productDetail page. the problem is I got that take a number as the value, when I change it's number and then proceed to search another product. When I got redirected to that productPage, the input value will stay and not reset. How to fix this ?
Here's my code :
ProductDetail.js
const [productCount, setproductCount] = useState(0);
<input
type="number"
className='input-quantity'
value={productCount || 1}
onChange={(e) => {
setproductCount(parseInt(e.target.value))
}}
/>
SearchBar.js
<div className="searchInput">
<input
id="searchKeyInputId"
type="text"
placeholder={placeholder}
onChange={handleFilter}
ref={inputRef}
/>
<BiSearch className="input-append-icon" />
{filteredData.length !== 0 && (
<div className="searchResult">
{filteredData && filteredData.slice(0, 5).map((value) => {
return <Link
to={`/p/${value.id}/${value.slug}`}
className="searchItem"
key={value.id}
onClick={clearInput}
>
<p className='fontRegular'>{value.name}</p>
</Link>
})}
</div>
)}
</div>

onChange not getting called React

I'm trying to debug a react checkbox and the onChange event doesn't ever get called. I have added a console.log to test and this doesn't ever run. Here is the code for the checkbox. What is the issue?
return (
<div className="RampPane">
<div className="RampPane--content">
<p className="RampText">{transaction.merchant} </p>
<b>{moneyFormatter.format(transaction.amount)}</b>
<p className="RampText--hushed RampText--s">
{transaction.employee.firstName} {transaction.employee.lastName} - {transaction.date}
</p>
</div>
<InputCheckbox
id={transaction.id}
checked={approved}
disabled={loading}
onChange={async (newValue) => {
console.log("click")
await consumerSetTransactionApproval({
transactionId: transaction.id,
newValue,
})
setApproved(newValue)
}}
/>
</div>
)
Here is the InputCheckBox Component
return (
<div className="RampInputCheckbox--container" data-testid={inputId}>
<label
className={classNames("RampInputCheckbox--label", {
"RampInputCheckbox--label-checked": checked,
"RampInputCheckbox--label-disabled": disabled,
})}
/>
<input
id={inputId}
type="checkbox"
className="RampInputCheckbox--input"
checked={checked}
disabled={disabled}
onChange={() => onChange(!checked)}
/>
</div>
)
Use this for InputCheckBox Component
return (
<div className="RampInputCheckbox--container" data-testid={inputId}>
<label
className={classNames("RampInputCheckbox--label", {
"RampInputCheckbox--label-checked": checked,
"RampInputCheckbox--label-disabled": disabled,
})}
htmlFor={inputId}
/>
<input
id={inputId}
type="checkbox"
className="RampInputCheckbox--input"
checked={checked}
disabled={disabled}
onChange={() => onChange(!checked)}
/>
</div>
)
This works!
If the InputCheckBox is a custom built react component that renders an html input element try checking that the onChange handler is passed to the root component correctly
IF Not try including the component here to get a better insight

Need to do Radio button in antd default checked

I have a Radio Button Group with dynamically data coming from an array.
Here's the code :
<Radio.Group
options={uniqueRadioElements}
onChange={onCategorySelect}
value={selectedCategory}
optionType="button"
buttonStyle="solid"
className="select-category"
checked="true"
/>
<Col span={24}>
<div className="professionals-wrapper">
{arrayOfElements
.filter((item) => item.category === selectedCategory)
.map((item) => {
return (
<div className="details-of-categories">
<Image
src="/images/clinic.png"
preview={false}
alt="photo 4"
/>
<h3>{item.title}</h3>
<Rate defaultValue={4} disabled />
</div>
)
})}
</div>
</Col>
I want to make it default checked for a specific element of Radio Button Group
You can add the prop defaultChecked to the specific index you want. An example for the first one would be:
<input defaultChecked={index === 0} />
In your case I would delete the checked value and add the defaultChecked.
<Radio.Group
options={uniqueRadioElements}
onChange={onCategorySelect}
value={selectedCategory}
optionType="button"
buttonStyle="solid"
className="select-category"
defaultChecked={index === 0} />

React hook form Rendering too many times

I am trying to do something like this.
When user Click on Edit Button then Form fields will appear and user can edit the data. otherwise user can only view the data.
I facing the trouble, when user don't want to edit the form and click on cancel button then page start rendering as many times as the page have total form fields.
On my original form I have 80+ form fields and when user click on cancel button page becomes very slow.
When I remove
ref={register}
from my page then form don't render to many times, but form does not submit with the data.
Is there any way to stop extra rendering?
Here is my code and logic.
Thanks for your attention.
import React, { useState } from "react";
import { useForm } from "react-hook-form";
function Test() {
const { register, handleSubmit } = useForm();
const [edit, setEdit] = useState(false);
const onSubmit = (data) => {
console.log(data);
};
return (
<div className="App">
<header className="App-header">
{console.log("I am redering.")}
<form onSubmit={handleSubmit(onSubmit)}>
<a
href=""
onClick={(e) => {
setEdit(!edit);
e.preventDefault();
}}
>
{edit ? "Cancel" : "Edit"}
</a>
{edit && (
<p>
<input
type="text"
name="email"
ref={register}
placeholder="Email"
/>
</p>
)}
{edit && (
<p>
<input
type="text"
name="firstname"
ref={register}
placeholder="First Name"
/>
</p>
)}
{edit && (
<p>
<input
type="text"
name="lastname"
ref={register}
placeholder="Last Name"
/>
</p>
)}
{edit && (
<>
<input
type="checkbox"
name="contact[]"
id="contact-one"
value="1"
ref={register}
/>
<label htmlFor="contact-one">One</label>
</>
)}
{edit && (
<>
<input
type="checkbox"
name="contact[]"
id="contact-two"
value="2"
ref={register}
/>
<label htmlFor="contact-two">Two</label>
</>
)}
{edit && <button type="submit">Submit</button>}
{edit === false && (
<>
<p>{`My First Name`}</p>
<p>{`My Last Name`}</p>
<p>{`My Email address`}</p>
<p>{`My Contacts`}</p>
</>
)}
</form>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default Test;
<form onSubmit={handleSubmit(onSubmit)}>
This is a function call, which means this gets called immediatelt the form is created on the DOM, your event listeners need to take a reference to the function which will then get called when the event occurs, to do that you wrap this function call in another anonymous function. Then the function will get called when the submit event occurs. So you need to do this
<form onSubmit={()=>{handleSubmit(onSubmit)}}>
Also since this is a submit event, you might want to stop the default behaviour which refreshes the page. Like this
<form onSubmit={(ev)=>{
ev.preventDefault()
handleSubmit(onSubmit)}}>
Update
I did not look at the JSX properly. There are a few things you are probably doing wrong
using ref to get input values rather than state
when taking in any kind of input you want to convert those fields into control components Officila docs on controlled components
I suggest you understand this and state first, that will help you a lot. React docs still uses class components in example code but simply use use state instead of this.state for state varibales

React Hooks Form Reset dont set Select to initial value

When clicking Reset, form should go back to initial values, but the Select element goes back to first option available, have used the same method for all other form elements, and they update correctly.
Have made a Codesandbox example of the problem : https://codesandbox.io/s/lively-snowflake-tf5te
function App() {
// Data for select dropdown box - selected is Lime
const SelectData = ["Grapefruit", "Lime", "Coconut", "Mango"];
// Selected Value
const selectedValue = "Lime";
// Set state
const [selectBox, setSelectBox] = useState(selectedValue);
// Submit finction
const handleSubmit = e => {
e.preventDefault();
alert(`Select Selection #1: ${selectBox}`);
};
// Reset function
const handleReset = () => {
setSelectBox(selectedValue);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>
When 'Reset' is clicked first options is selected
<br />
and not the state value.
</h2>
<form onSubmit={handleSubmit}>
<Select
label="Select Fruit"
value={selectBox}
handleChange={e => setSelectBox(e.target.value)}
data={SelectData}
/>
<br />
<br />
<input type="submit" value="Submit" />
<input type="reset" value="Reset" onClick={handleReset} />
</form>
<br />
Selectbox state value: {selectBox}
<br />
</div>
);
}
Expected result, after Resetting Form, is that then Select element value is "Lime", but it is "Grapefruit".
I changed the value to defaultValue in your DropDown.js file and it worked like charm. Check the sandbox
<select defaultValue={value} onChange={handleChange}>
{data.map(item => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
Your code is correct. Your "bug" is due to the <input type='reset'/>.
Change it to a regular button and you'll see that it works just fine.
<button onClick={handleReset}>Reset</button>
https://codesandbox.io/s/dark-cdn-qchu5
From MDN: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/reset
elements of type "reset" are rendered as buttons, with a default click event handler that resets all of the inputs in the form to their initial values.
This is concurring with your controlled component.
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>
When 'Reset' is clicked first options is selected
<br />
and not the state value.
</h2>
<form onSubmit={handleSubmit}>
<Select
label="Select Fruit"
value={selectBox}
handleChange={e => setSelectBox(e.target.value)}
data={SelectData}
/>
<br />
<br />
<input type="submit" value="Submit" />
{/* <input type="reset" value="Reset" onClick={handleReset} /> */}
<button onClick={handleReset}>Reset</button>
</form>
<br />
Selectbox state value: {selectBox}
<br />
</div>
);
Even though select is a controlled component, Form doesn't support onReset event. Thus, the behavior is similar to $form.reset() which will reset the select value to default value when button of type=Reset is clicked.
So an alternative is to use defaultValue prop in select or use a normal button with onChange prop.

Resources