After inserting text in input and later removing it completely with backspace "false" appears in it. It cannot be removed - after removing last letter it appears again. It appears in input which are rendered using map() method. The same problem doesn't occur in textarea which is not rendered using this method so I guess the problem lays somewhere here, but I dont have idea where.
export default function AddTodo({ active, setActive }: AddTodoProps) {
const { isDarkMode } = useContext(DarkModeContext);
const [todoDetails, setTodoDetails] = useState({
task: "",
description: "",
name: "",
deadline: "",
});
const { submitTodo, loading, status } = useAddTodoToDb(todoDetails);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setTodoDetails((prev) => {
return {
...prev,
[e.target.id]: e.target.value || e.target.checked,
};
});
};
useEffect(() => {
if (typeof status == "string")
setTodoDetails({
task: "",
description: "",
name: "",
deadline: "",
});
}, [status]);
return (
{todoInputs.map((item) => {
return (
<StyledLabelAndInput
isDarkMode={isDarkMode}
err={status?.includes(item.id)}
key={item.id}
>
<label htmlFor={item.id}>{item.text}</label>
<input
value={todoDetails[item.id as keyof ITodoDetails]}
type={item.type}
id={item.id}
min={today}
onChange={(e) => handleChange(e)}
/>
</StyledLabelAndInput>
);
})}
<label htmlFor="description">Description (optional)</label>
<textarea
value={todoDetails.description}
id="description"
onChange={(e) =>
setTodoDetails((prev) => {
return {
...prev,
description: e.target.value,
};
})
}
/>
The false value is comming from the input's checked attribute.
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setTodoDetails((prev) => {
return {
...prev,
[e.target.id]: e.target.value || e.target.checked, /* <--- HERE */
};
});
};
You're trying to get the input's value OR checked, and since the input has no value it's returning the checked attribute, which is false by default.
Instead you'll need to check if the item type is checkbox before using the checked attribute.
Related
I have following state:
const [startPaymentIn, setStartPaymentIn] = useLocalStorage<StartPaymentIn>(
'startPaymentIn', {})
It read content from localstorage. And it is rendered in return. But no value is rendered, though I see values are set.
useEffect(() => {
if (user?.partnerData) {
setStartPaymentIn({
...startPaymentIn,
['partnerData']: user!.partnerData!,
})
setStartPaymentIn({
...startPaymentIn,
['deliveryData']: user!.deliveryData!,
})
} else {
setStartPaymentIn((prevState) => ({
...prevState,
partnerData: { country: 'Hungary' },
}))
}
}, [user])
<div className={'inputContainer'}>
<p>Név</p>
<input
value={startPaymentIn?.partnerData?.name ?? ''}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setData('partnerData', 'name', event.target.value)
}}
/>
</div>
how can I set value from text Input to option[0].title when user input? I tried in many way and get error. Please help me to fix this issue.
const [options, setOptions] = useState({
0: {
title: "",
},
2: {
title: "",
1: {
title: "",
}, },
3: {
title: "",
},
title: "", });
let name, value;
const handleChange = (e) => {
name = e.target.name;
value = e.target.value;
setOptions({ ...options, [name]: value }); };
return ( <TextInput
type="text"
name="0"
value={options[0].title}
onChange={handleChange}
required
placeholder="something"
icon="check_box_outline_blank"
/> )
you just have to edit the title in the object:
const handleChange = (e) => {
name = e.target.name;
value = e.target.value;
setOptions({ ...options, [name]: { title: value } });
};
and if you need the previous information from the targeted name, spread within that object too:
setOptions({ ...options, [name]: { ...options[name], title: value } });
Fully working code,
const [options, setOptions] = useState({
0: { title: "" },
2: {
title: "",
1: { title: "" },
},
3: { title: "" },
title: "",
});
const handleChange = (e) => {
const { name, value } = e.target;
options[+name].title = value;
setOptions({...options});
};
return (
<>
<input
type="text"
name="0"
value={options[0].title}
onChange={handleChange}
required
placeholder="something"
icon="check_box_outline_blank"
/>
<input
type="text"
name="2"
value={options[2].title}
onChange={handleChange}
required
placeholder="something"
icon="check_box_outline_blank"
/>
</>
);
I tried to check this on codesandbox and found a solution -
The name that you have updated here is string and the json has number keys. Which was the reason why it wouldn't update on title node. Also you never said .title = value, which is why values got directly updated to the options[0] element
export default function App() {
const [options, setOptions] = useState({
0: {
title: ""
},
2: {
title: "",
1: {
title: ""
}
},
3: {
title: ""
},
title: ""
});
const handleChange = (e) => {
const name = e.target.name,
value = e.target.value;
let updatedOptions = { ...options };
updatedOptions[Number(name)].title = value;
setOptions(updatedOptions);
};
return (
<div className="App">
<input
type="text"
name="0"
value={options[0].title}
onChange={handleChange}
required
placeholder="something"
icon="check_box_outline_blank"
/>
</div>
);
}
I am using custom register in react-hook-form, and I am unable to get formState.isValid to be true when I enter text into the input (and so satisfy the required condition).
Here is an example code:
interface FormValues {
readonly value: string;
}
export default function App() {
console.log("rendering");
const form: UseFormMethods<FormValues> = useForm<FormValues>({
mode: "onChange",
reValidateMode: "onChange",
defaultValues: { value: "" }
});
useEffect(() => {
form.register({ name: "value" }, { required: true });
}, [form, form.register]);
const { isValid } = form.formState;
const value = form.watch("value");
return (
<div>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
form.setValue("value", e.target.value);
}}
/>
<div>IsValid: {JSON.stringify(isValid)}</div>
<div>Errors: {JSON.stringify(form.errors)}</div>
<div>Value: {JSON.stringify(value)}</div>
</div>
);
}
The question is sepcifically for this type of register use, not other types (ref or Controller).
Here is a full example.
Does someone know why this is the case, what am I missing?
Additionally, but this is less relevant - does anyone know why rendering is triggered twice for each input change?
EDIT
After discussion with Dennis Vash, there was some progress on this issue, but it is still not resolved.
The docs at https://react-hook-form.com/api/#setValue actually do specify an option for triggering validation:
(name: string, value: any, shouldValidate?: boolean) => void
You can also set the shouldValidate parameter to true in order to trigger a field validation. eg: setValue('name', 'value', true)
At the time when I am writing this, the docs refer to version 5 of react-form-hook, I am actually using 6.0.0-rc.5 so the signature changed a bit to something similar to the following:
(name: string, value: any, { shouldValidate: boolean; shouldDirty: boolean; }) => void
However, in an example that I have when using shouldValidate: true, I get an infinite loop:
interface FormValues {
readonly value: string;
}
export default function App() {
console.log("rendering");
const form: UseFormMethods<FormValues> = useForm<FormValues>({
mode: "onChange",
reValidateMode: "onChange",
defaultValues: { value: "" }
});
useEffect(() => {
form.register({ name: "value" }, { required: true, minLength: 1 });
}, [form, form.register]);
const { isValid } = form.formState;
const value = form.getValues("value");
return (
<div>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
form.setValue("value", e.target.value, {
shouldValidate: true
});
}}
/>
<div>IsValid: {JSON.stringify(isValid)}</div>
<div>Errors: {JSON.stringify(form.errors)}</div>
<div>Value: {JSON.stringify(value)}</div>
</div>
);
}
The loop is occurring when isValid is true, but stops when it is false.
You can try it out here. Entering a key will start the continuous re-rendering, and clearing the input will stop the loop...
Referring to https://github.com/react-hook-form/react-hook-form/issues/2147
You need to set the mode to onChange or onBlur
const { register, handleSubmit, formState: { errors, isDirty, isValid }} = useForm({ mode: 'onChange' });
In this case, the 'isValid' will work as expected.
The component rendered twice because of React.StrictMode.
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions: ...
Also, in order to validate you need to submit the form (try pressing enter), if you on onChange mode without using a reference, you should use triggerValidation instead.
export default function App() {
const {
register,
formState,
watch,
errors,
setValue,
handleSubmit
}: UseFormMethods<FormValues> = useForm<FormValues>();
useEffect(() => {
register(
{ name: "firstName", type: "custom" },
{ required: true, min: 1, minLength: 1 }
);
console.log("called");
}, [register]);
const { isValid } = formState;
const values = watch("firstName");
const onSubmit = (data, e) => {
console.log("Submit event", e);
console.log(JSON.stringify(data));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setValue("firstName", e.target.value);
}}
/>
{/*<button>Click</button>*/}
<div>IsValid: {JSON.stringify(isValid)}</div>
<div>Errors: {JSON.stringify(errors)}</div>
<div>Form value: {JSON.stringify(values)}</div>
</form>
);
}
This is register form, I want to send the value orgTypes>name to orgType of data of same object using onChange of select.
https://codesandbox.io/s/react-typescript-zm1ov?fontsize=14&fbclid=IwAR06ZifrKrDT_JFb0A-d_iu5YaSyuQ9qvLRgqS20JgAcSwLtAyaOFOoj5IQ
When I use onChange of Select, it erases all other data of the inputs.
import React from "react";
import { Select } from "antd";
const { Option } = Select;
const RegisterFinal = () => {
const data = {
orgName: "",
orgRegNo: "",
orgType: "",
orgTypes: [
{ id: "1", name: "Vendor" },
{ id: "2", name: "Supplier" },
{ id: "3", name: "Vendor and Supplier" }
],
errors: {}
};
const [values, setValues] = React.useState(data);
const handleChange = (e: any) => {
e.persist();
setValues((values: any) => ({
...values,
[e.target.name]: e.target.value
}));
};
const handleSubmit = () => {
console.log(values);
};
return (
<React.Fragment>
<input
name="orgName"
onChange={handleChange}
placeholder="Organization Name"
value={values.orgName}
/>
<input
name="orgRegNo"
onChange={handleChange}
placeholder="Registration Number"
value={values.orgRegNo}
/>
<Select //imported from antd
id="select"
defaultValue="Choose"
onChange={(select: any) => {
setValues(select);
console.log(select); //shows Vender/Supplier, I want this value to be sent to orgType above
}}
>
{data.orgTypes.map((option: any) => (
<Option key={option.id} value={option.name}>
{option.name}
</Option>
))}
</Select>
</React.Fragment>
);
};
When I use onChange of Select, it erases all other data of the inputs.
Thank you for your help
setValues function expects to take the values object as an argument. Instead you are passing the select value to it.
Instead, you can do it like this:
const handleSelectChange = (selectValue: any) => {
setValues((values: any) => ({
...values,
orgType: selectValue
}));
}
and use handleSelectChange in the onChange of your select.
Check the solution here: https://codesandbox.io/s/react-typescript-p9bjz
i am trying to get an object {email:"abc#gmail.com",password:"1234"} when the user press the submit button
i have tried to make a const in the handleChange to dave that value in the but i am only getting one value at a time.
this.state = {
is_authenticated: false,
application_status_modal_visible: false,
login_data: []
};
handleChange = event => {
const { login_data } = this.state;
const { name, value } = event.target;
const obj = { ...obj };
obj[name] = value;
console.log(obj);
this.setState({ login_data: [...login_data, obj] });
};
handleSubmit = () => {
console.log(this.state.login_data);
};
modalCancel = () => {
this.setState({ application_status_modal_visible: false });
};
<Modal
title="Login"
visible={application_status_modal_visible}
onOk={this.handleSubmit}
onCancel={this.modalCancel}
>
<Form>
<FormItem>
<Input
prefix={<Icon type="user" style={{ color: "rgba(0,0,0,.25)" }} />}
placeholder="Email"
name={"email"}
onChange={this.handleChange}
/>
</FormItem>
<FormItem>
<Input
prefix={<Icon type="lock" style={{ color: "rgba(0,0,0,.25)" }} />}
type="password"
placeholder="Password"
name={"password"}
onChange={this.handleChange}
/>
</FormItem>
</Form>
</Modal>;
...
i am expecting this result {email:"sasas#gmail.com",password:"121212"}
When working with state, you have to use immutable operations. With nested objects this can be a bit tricky without using a custom library. You should also avoid accessing this.state before updating state. Instead, use an update function:
handleChange = event => {
const { name, value } = event.target;
this.setState(prevState => {
const { login_data } = prevState;
return { // returning a change object
login_data: {
...login_data, // copy the original
[name]: value // overwrite value
}
};
});
};
Also note that you should initialize login_data as {} not []. It's an object, not an array.
However, the ideal solution is not to use nested objects at all unless your state gets more complicated:
this.state = {
is_authenticated: false,
application_status_modal_visible: false
email: "",
password: ""
};
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
});
};
handleSubmit = () => {
const { email, password } = this.state;
const loginData = { email, password };
console.log(loginData);
};
try this
this.state = {
is_authenticated: false,
application_status_modal_visible: false,
login_data: {
email: '',
password: ''
}
}
handleChange = (event) => {
const login_data = { ...this.state.login_data };
const { name, value } = event.target;
login_data[name] = value;
this.setState({ login_data });
};