DateTimePicker from react widgets open on focus - javascript

I'm starting my adventure with react and I have a reusable component of type DateTime like that:
interface IProps
extends FieldRenderProps<Date, HTMLElement>,
FormFieldProps {}
const DateInput: React.FC<IProps> = ({
input,
width,
placeholder,
date = false,
time = false,
meta: { touched, error },
id,
...rest
}) => {
const [isOpen, setIsOpen] = useState<false | "date" | "time" | undefined>(false);
return (
<Form.Field error={touched && !!error} width={width}>
<DateTimePicker
onFocus={() => setIsOpen("date")} //here I would like it to be set false or time/date depending on props value
placeholder={placeholder}
value={input.value || null}
onChange={input.onChange}
date={date}
time={time}
{...rest}
open={isOpen}
/>
{touched && error && (
<Label basic color='red'>
{error}
</Label>
)}
</Form.Field>
)
};
I would like it to open when I click on the field. Thats why I added that:
const [isOpen, setIsOpen] = useState<false | "date" | "time" | undefined>(false);
Maybe it is not the best way to do that :/
Problem with that code is that when I select input the it is set to open but even if it looses focuse then still it is open. I would like it to be closed after sleecting a date or clicking somewhere else.
And second thing is that I would like it to open date/time depending on props set on component (date = false; time = false)
Could you please help me with that task?

Ok, so I did it this way, do you think that somehow it can be improved?
interface IProps
extends FieldRenderProps<Date, HTMLElement>,
FormFieldProps {}
const DateInput: React.FC<IProps> = ({
input,
width,
placeholder,
date = false,
time = false,
meta: { touched, error },
id,
...rest
}) => {
const [isFocused, setIsFocused] = useState<false | "date" | "time" | undefined>(false);
const setOpen = (active: boolean) => {
if(!active){
setIsFocused(false);
}
else{
if(date === true){
setIsFocused("date");
}else{
setIsFocused("time");
}
}
}
return (
<Form.Field
error={touched && !!error}
width={width}>
<DateTimePicker
onBlur={() => setOpen(false)}
onFocus={() => setOpen(true)}
placeholder={placeholder}
value={input.value || null}
onChange={input.onChange}
onSelect={() => setIsFocused(false)}
date={date}
time={time}
{...rest}
open={isFocused}
/>
{touched && error && (
<Label basic color='red'>
{error}
</Label>
)}
</Form.Field>
)
};

You need to add a function that handles outside click or date selection.
e.g.:
const closePicker = () => setIsOpen(false)
...
<DateTimePicker ...
onSelect={closePicker}
...>
to watch the outside click, you need to add listener for click events.
Have a look at this answer

Related

Why isn't my child component updating data when changing the state in React?

I have a list of users and I want to display in another component on the same page the user data in input fields for every user that I click on.
When no user is selected, I want the component to just render some text and a button to add a user. When the button is clicked the component renders the form with empty input fields so that we can add a new user.
I tried the following, but it's just showing the data for the first one I click on. It's not updating.
The main page:
const index = (props) => {
const [selectedUser, setSelectedUser] = useState(null);
const [state, setState] = useState("Index");
const onChange = (item) => {
setState("Edit");
setSelectedUser(item);
};
const onClick = (e, item) => {
if (e.type === "click" && e.clientX !== 0 && e.clientY !== 0) {
onChange(item);
} else {
console.log('prevented "onClick" on keypress');
}
};
const renderComponent = () => {
switch (state) {
case "Index":
return (
<>
<div className="btn" onClick={(e) => setState("Edit")}>
+ New Staff
</div>
<img src="/storage/illustrations/collaboration.svg" />
</>
);
case "Edit":
return (
<div>
<StaffForm profile={selectedUser} />
</div>
);
}
};
return (
<>
<div>
<div>
<h1>Staff</h1>
</div>
<div>
<div>
{profiles.map((item, index) => {
return (
<div key={index} onClick={(e) => onClick(e, item)}>
<input
type={"radio"}
name={"staff"}
checked={state === item}
onChange={(e) => onChange(item)}
/>
<span>{item.user.name}</span>
</div>
);
})}
</div>
<div>{renderComponent()}</div>
</div>
</div>
</>
);
};
The Staff Form Component:
const StaffForm = ({ profile }) => {
const { data, setData, post, processing, errors, reset } = useForm({
email: profile ? profile.user.email : "",
name: profile ? profile.user.name : "",
phone_number: profile ? profile.user.phone_number : "",
avatar: profile ? profile.user.avatar : "",
});
const [file, setFile] = useState(data.avatar);
const handleImageUpload = (e) => {
setFile(URL.createObjectURL(e.target.files[0]));
setData("avatar", e.target.files[0]);
};
const onHandleChange = (event) => {
setData(
event.target.name,
event.target.type === "checkbox"
? event.target.checked
: event.target.value
);
};
return (
<div>
<ImageUpload
name={data.name}
file={file}
handleImageUpload={handleImageUpload}
/>
<TextInput
type="text"
name="name"
value={data.name}
autoComplete="name"
isFocused={true}
onChange={onHandleChange}
placeholder={t("Name")}
required
/>
<TextInput
type="text"
name="phone_number"
value={data.phone_number}
autoComplete="phone_number"
placeholder={t("Phone Number")}
onChange={onHandleChange}
required
/>
<TextInput
type="email"
name="email"
value={data.email}
autoComplete="email"
onChange={onHandleChange}
placeholder={t("Email")}
required
/>
</div>
);
};
First of all something you should avoid is the renderComponent() call.Check here the first mistake mentioned in this video. This will most likely fix your problem but even if it doesn't the video explains why it should not be used.
Something else that caught my eye(possibly unrelated to your question but good to know) is the onChange function. When two pieces of state change together it is a potential source of problems, check out this article on when to use the useReducer hook.
Also be careful with naming React Components, they need to be capital case, this question contains appropriate answers explaining it.
(To only solve your problem stick to no.1 of this list, there are some improvements i'd do here overall for code quality and beauty, msg me for more details)

Updated state not reflecting properly

I have below code where I am checking or unchecking the checkbox based on some conditions, and I came across an issue where I am trying to check the checkbox, and it is failing the first time and from the second time onwards works perfectly.
export const AntdDefaultOverrideInputNumberAdapter = ({
input: { onChange, value },
disabled,
defaultValue,
formatter,
...rest
}) => {
const [isDefaultValueOverriden, setIsDefaultValueOverriden] = useState(
!!value && value !== defaultValue
);
const handleCheckboxChange = () => {
const hasOverride = !isDefaultValueOverriden;
setIsDefaultValueOverriden(hasOverride);
onChange(hasOverride && !!value ? value : defaultValue);
};
const handleOverrideChange = (v) => {
onChange(v);
};
return (
<Badge
offset={[0, -6]}
count={
<div>
<Checkbox
disabled={disabled}
checked={isDefaultValueOverriden}
onChange={handleCheckboxChange}
style={{ zIndex: 10 }}
/>
</div>
}
>
<InputNumber
size="small"
onChange={handleOverrideChange}
disabled={disabled || !isDefaultValueOverriden}
style={{ width: 65 }}
value={isDefaultValueOverriden ? value : defaultValue}
formatter={formatter}
{...rest}
/>
</Badge>
);
};
I am not sure where I am wrong with the above code, The problem only appears on trying to check the checkbox the first time, and it disappears from the second time onwards..
Could anyone suggest any idea on this? Many thanks in advance!!
I am using the "ANTD" library for the checkbox, and the "value" is an empty string, and the defaultValue is "5"

Value of a props is not same as sent in the callback funtion

I have a react component as :
const CustomDatePicker = ({ errors, id, name, control, rules, getValues, minDate, maxDate, placeholder, required, defaultValue, ...rest }) => {
console.log("required 1", name, required);
const inputRef = React.useRef();
const validateField = () => {
console.log("required 2", required, name)
if (required && !getValues(name)) {
return false
}
else if (getValues(name)) {
let dateObj = typeof (getValues(name)) == 'string' ? new Date(getValues(name)) : getValues(name)
return !isNaN(dateObj);
}
else return true;
}
return (
<div className="form-group custom-input-container">
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<Controller
name={name}
margin="normal"
fullWidth
variant="outlined"
onFocus={() => {
if (inputRef.current) {
inputRef.current.focus()
}
}}
defaultValue={defaultValue}
as={<KeyboardDatePicker
inputRef={inputRef}
className="custom-date-col"
fullWidth
autoOk
clearable
variant="inline"
inputVariant="outlined"
placeholder={placeholder}
minDate={minDate}
format="dd-MM-yyyy"
maxDate={maxDate}
/>}
rules={{
validate: validateField
}}
control={control}
errors={errors}
{...rest}
/>
</MuiPickersUtilsProvider>
</div>
);
};
export default CustomDatePicker;
and a parent component uses the above as follows :
export default function Dummy(props) {
const [req,setReq]=useState(false);
return (
<div className="FamilyDetails__emergency-form-row FamilyDetails__three-col-grid">
<CustomDatePicker
name={`marriageDate`}
errors={props.errors}
control={props.control}
maxDate={new Date()}
minDate={getMinDate()}
placeholder={"dateOfMarriageLbl"}
required={req}
defaultValue={new Date()}
/>
</div>
);
}
I use the above components in the form. Initially "required" prop is going to be false then later it will be changed to true. When I submit the form validateField method gets called and the required prop value in console is printed as false whereas the original value is true. Then console printed outside the function prints "require" prop value as true. The value of "required" prop in validateField function is taking the initial value with which component is initially rendered. Please help me through this.
It looks like react-hook-forms caches validation and the issue has been resolved in latest versions. find the discussion here. This has solved my issue.

Error: Maximum update depth exceeded when using react input mask on formik

I am trying to bind react-input-mask with formik. However, I get an error on the first key stroke. Here is how I am trying to use it. If i remove react-input-mask then the code works as a normal textfield but if i use input mask then only i get 'maximum updated depth' error. What might be the reason?
const InputMaskField = ({
label,
field,
form,
prepend,
append,
innerAppend,
...rest
}) => {
const [active, setActive] = React.useState(false)
const hasError =
(get(form.touched, `${field.name}`) || get(form, 'submitCount') > 0) &&
get(form.errors, `${field.name}`)
return (
<InputWrapper>
<Label isActive={active}>{label}</Label>
<InputMask {...rest}>
{(inputProps) => {
return (
<>
<TextInputWrapper isActive={active}>
{prepend && <InputAddonItem prepend>{prepend}</InputAddonItem>}
<InputAddonField
isActive={active}
hasError={hasError}
prepend={!!prepend}
append={!!append}
{...field}
{...inputProps}
onBlur={(e) => {
setActive(false)
field.onBlur(e)
}}
onFocus={() => setActive(true)}
/>
{append && (
<InputAddonItem
isActive={active}
hasError={hasError}
{...field}
{...rest}
>
{append}
</InputAddonItem>
)}
{innerAppend && <InnerAppend>{innerAppend}</InnerAppend>}
</TextInputWrapper>
</>
)
}}
</InputMask>
{hasError && <Error>{hasError}</Error>}
</InputWrapper>
)
}

Sending parameter with function to child component and back to parent component

I've got a custom component called InputWithButton that looks like this:
const InputWithButton = ({ type = "text", id, label, isOptional, name, placeholder = "", value = "", showPasswordReset, error, isDisabled, buttonLabel, handleChange, handleBlur, handleClick }) => (
<StyledInput>
{label && <label htmlFor="id">{label}{isOptional && <span className="optional">optioneel</span>}</label>}
<div>
<input className={error ? 'error' : ''} type={type} id={id} name={name} value={value} placeholder={placeholder} disabled={isDisabled} onChange={handleChange} onBlur={handleBlur} autoComplete="off" autoCorrect="off" />
<Button type="button" label={buttonLabel} isDisabled={isDisabled} handleClick={() => handleClick(value)} />
</div>
{error && <Error>{Parser(error)}</Error>}
</StyledInput>
);
export default InputWithButton;
Button is another component and looks like this:
const Button = ({ type = "button", label, isLoading, isDisabled, style, handleClick }) => (
<StyledButton type={type} disabled={isDisabled} style={style} onClick={handleClick}>{label}</StyledButton>
);
export default Button;
I'm using the InputWithButton component in a parent component like this:
render() {
const { name } = this.state;
return (
<React.Fragment>
<InputWithButton label="Name" name="Name" buttonLabel="Search" value={name} handleChange={this.handleChange} handleClick={this.searchForName} />
</React.Fragment>
);
}
If the button is clicked, the searchForName function is called:
searchForName = value => {
console.log(value); //Input field value
}
This is working but I want to add another parameter to it but this time, a parameter that comes from the parent component
// handleClick={() => this.searchForName('person')}
<InputWithButton label="Name" name="Name" buttonLabel="Search" value={name} handleChange={this.handleChange} handleClick={() => this.searchForName('person')} />
The output in searchForName is now 'person' instead of the value.
I thought I could fix this with the following code:
searchForName = type => value => {
console.log(type); //Should be person
console.log(value); //Should be the value of the input field
}
However this approach doesn't execute the function anymore.
How can I fix this?
EDIT: Codepen
I would try handleClick={this.searchForName.bind(this, 'person')}, please let me know if it'll work for you.
EDIT:
I changed fragment from your codepen, it's working:
searchName(key, value) {
console.log(key);
console.log(value);
}
render() {
const { name } = this.state;
return (
<InputWithButton name="name" value={name} buttonLabel="Search" handleChange={this.handleChange} handleClick={this.searchName.bind(this, 'person')} />
)
}
as I suspected, just pass it an object and make sure you're accepting the argument in your handleClick function
handleClick={value => this.searchName({value, person: 'person'})}
or more verbose - without the syntactic sugar
handleClick={value => this.searchName({value: value, person: 'person'})}
then you can get at it with value.person
full codepen here
Hope this helps

Categories