onChange not getting called React - reactjs

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

Related

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

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.

Why My Checkboxes Doesn't Check Back After Uncheck in ReactJS

I have checkboxes that you can click as many as you want.
My problem is that it doesn't put a check after I uncheck. Also the values when I submit is not appearing in console.log
Pls check my codesandbox here
CLICK HERE
<Label htmlFor={id}>
<Input
type="checkbox"
id={name}
name={name}
value={value}
checked={checked}
onChange={onChange}
/>
<Indicator />
</Label>
<form onSubmit={handleSubmit}>
{products.map((product) => (
<div key={product}>
<Input
name={values.products}
value={values.products}
checked={values.products}
onChange={({ target }) => setFieldValue("products", target.value)}
/>
</div>
))}
<button type="submit">Submit</button>
</form>
when you are checking a checkbox your will get the value and check whether if the value is present already in the products list if it is not present then you should add the value ( this will be your check part ) else you can filter the value from the products ( this will be your uncheck ) .
<Checkbox
name="products"
value={product}
checked={values.products.includes(product)}
onChange={({ target }) => {
let updatedProducts;
if (values.products.includes(product)) {
updatedProducts = values.products.filter(
(product) => product !== target.value
);
} else {
updatedProducts = [...values.products, target.value];
}
setFieldValue("products", updatedProducts);
}}
/>
Working Sandbox
Sandbox
values.products is an array of your values, so it doesn't make sense to have this for your name or value. Instead, you need to pick out the specific value from the array that is relevant to your specific checkbox.
{products.map((product, index) => (
<div key={product}>
<p>{product}</p>
<Input
name={product}
value={values.products[index]}
checked={values.products[index]}
onChange={({ target }) => setFieldValue(`products.${index}`,!values.products[index])
}
/>
</div>
))}
Working Sandbox

React Hooks Form not returning values on submit

I want to build a form where I loop over an array of questions and collect the values of radio buttons for a quiz. Later I want to store those values, so I can can perform some calculations on the result, but for now I just want to log the value (1,2,3,4) and the name of the input to the console. As I'm new to React and Hooks, I have tried the following, but the result that is logged is always an epmty object. The desired result should look like this (built in vanilla JS):
https://florestankorp.github.io/D-D-AlignmentTest/
App.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { Question } from '../shared/interfaces';
const QUESTIONS: Question[] = [
{
id: 'q201',
question:
'1. Family elders are expressing disapproval of you to the rest of the family. Do you:',
option1: 'Accept the criticism and change your ways?',
option2: 'Seek a compromise with them?',
option3:
'Besmirch the reputation of those expressing disapproval as you ignore their scorn?',
option4: 'Silence them any way you can?',
},
];
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = (data: any) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{QUESTIONS.map((question) => (
<Question {...question} />
))}
<input type="submit" />
</form>
);
}
function Question(props: any): any {
return (
<div>
<p className="question">{props.question}</p>
<div>
<input name={props.id} value="1" type="radio" />
<label htmlFor={props.id}>{props.option1}</label>
</div>
<div>
<input name={props.id} value="2" type="radio" />
<label htmlFor={props.id}>{props.option2}</label>
</div>
<div>
<input name={props.id} value="3" type="radio" />
<label htmlFor={props.id}>{props.option3}</label>
</div>
<div>
<input name={props.id} value="4" type="radio" />
<label htmlFor={props.id}>{props.option4}</label>
</div>
</div>
);
}
I see you are using the useForm hook. Looking at the docs, they provide a method called "register" which you use to register each input component with the hook. That needs to be incorporated into your Question component.
I would suggest
Ensure you add a "key" attribute on the Question component when you map the QUESTIONS array
pass register as a prop to Question, and use it as ref={props.register} on every input component.
see below
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = (data: any) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{QUESTIONS.map((question) => (
<Question {...question} register={register} key={question.id} /> //<-- notice register and key attribute
))}
<input type="submit" />
</form>
);
}
now you can include this prop in your question component
function Question(props: any): any {
return (
<div>
<p className="question">{props.question}</p>
<div>
<input name={props.id} value="1" type="radio" ref={props.register} /> //<-- note ref={props.register}
<label htmlFor={props.id}>{props.option1}</label>
</div>
<div>
<input name={props.id} value="2" type="radio" ref={props.register} />
<label htmlFor={props.id}>{props.option2}</label>
</div>
<div>
<input name={props.id} value="3" type="radio" ref={props.register} />
<label htmlFor={props.id}>{props.option3}</label>
</div>
<div>
<input name={props.id} value="4" type="radio" ref={props.register} />
<label htmlFor={props.id}>{props.option4}</label>
</div>
</div>
);
}
This should then update the state for the submit event. See this CodeSandbox
EDIT
Added the Answer of #Amit from the comment added below
react-hook-form updated to 7.0.0 from 6.X.X and has breaking changes:
You have to replace all ref={register} with
{...register('value_name')}
Example: Version 6.X.X:
<input ref={register({ required: true })} name="test" />
Version 7.0.X:
<input {...register('test', { required: true })} />

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.

ReactJS: Triggering event onBlur

I am having child component for input fields which is checking its validation onBlur event.
Child component usage:
<TextInput
id={'lastName'}
label={'Last name'}
required={true}
minChars={3}
maxChars={25}
/>
Child component code:
onBlur(event) {
// logic here
}
render() {
let props = this.props;
return (
<div>
<div className="form-group">
<label htmlFor={props.id}>{props.label}</label>
<input
id={props.id}
type={props.type}
className="form-control"
onBlur={this.onBlur.bind(this)}
/>
<p className="text-danger">{this.error}</p>
</div>
</div>
);
}
This works just fine.
When user submits form from parent component, I would like onBlur to be triggered across all inputs. How could I accomplish this?
In the parent component:
_handleBlur (eventData) {}
render () {
const handleBlur = this._handleBlur.bind(this);
return (
<form>
<ChildComponent onBlur={handleBlur} {...moreProps} />
<ChildComponent onBlur={handleBlur} {...moreProps} />
<ChildComponent onBlur={handleBlur} {...moreProps} />
<button type="submit" onClick={handleBlur}>Submit</button>
</form>
);
}
In the child component:
render () {
const props = this.props;
return (
<div>
<div className="form-group">
<label htmlFor={props.id}>{props.label}</label>
<input
id={props.id}
type={props.type}
className="form-control"
onBlur={props.onBlur}
/>
<p className="text-danger">{this.error}</p>
</div>
</div>
);
}
Based on eventData or other params, you can define which field needs to be blurred. I don't know what exactly needs to happen on blur. In some cases it might be better to split it into a handleBlur and handleBlurAll method.
Hope this helps

Resources