I can't not pick a day at the weekend. The cursor is not changed. Only days in the width of the div, where the datepicker is in, can be picked. How I can fix this? I give an example using bootstrap to show the problem.
import React from "react";
import DatePicker from "react-datepicker";
class KSTabs extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.onOpenDatepicker = this.onOpenDatepicker.bind(this);
this.state = {
startDate: Date.now(),
isOpen: false
};
}
onOpenDatepicker = () => {
this.setState({ isOpen: !this.state.isOpen });
};
handleChange(date) {
this.setState({ isOpen: !this.state.isOpen, date: date });
}
render() {
return (
<div className="row">
<div className="col-sm-2">
<button onClick={this.onOpenDatepicker}> Open</button>
{this.state.isOpen && (
<DatePicker
selected={this.state.startDate}
onChange={this.handleChange}
inline
/>
)}
</div>
<div className="col-sm-2" />
</div>
);
}
}
export default KSTabs;
Edit
When the calendar is open, then I can select the first day on the left side, but I can't select the first date on the right side. The div column has a width of 185px and the datepicker 240px. Every days which are outside of the 185px are not selectable.
Related
I'm building an abstract form component with react-hook-form and Yup for validation. The form works, and validation works, but only after the submit button is pressed.
It's on codesandbox, but ...
import React, { cloneElement } from "react";
import "./styles.css";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import { string as yupString, object as yupObject } from "yup";
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
TextField
} from "#mui/material";
let renderCount = 0;
export const FormContent = ({ content }) => {
return content.map((item, i) => {
const name = item.component.props.name;
return (
<Controller
key={name + "_" + i}
name={name}
defaultValue=""
render={({ field, fieldState: { error }, formState: { isDirty } }) => {
return cloneElement(item.component, {
...field,
error: isDirty && !!error,
helperText: isDirty && error?.message,
FormHelperTextProps: { error: true }
});
}}
/>
);
});
};
export default function App() {
renderCount++;
const usernameInput = {
validation: yupString().required("Username is required"),
component: (
<TextField required label="Username" name="username" type="text" />
)
};
const passwordInput = {
validation: yupString().required("Password is required"),
component: <TextField required label="Password" name="password" />
};
const content = [usernameInput, passwordInput];
let validationSchema = yupObject().shape({});
// construct schema
content.forEach((item) => {
validationSchema = validationSchema.concat(
yupObject().shape({
[item.component.props.name]: item.validation
})
);
});
const methods = useForm({
resolver: yupResolver(validationSchema)
});
const onFormSubmit = (data) => {
console.log(data);
};
return (
<Dialog open>
<Box>Render Count: {renderCount}</Box>
<FormProvider {...methods}>
<Box component="form" onSubmit={methods.handleSubmit(onFormSubmit)}>
<DialogContent>
<FormContent content={content} />
</DialogContent>
<DialogActions>
<Button
type="submit"
fullWidth
name="login"
variant="contained"
color="primary"
size="large"
>
Login
</Button>
</DialogActions>
</Box>
</FormProvider>
</Dialog>
);
}
If you type some data in the fields, and then erase the data without pressing the button, nothing happens. If you leave the fields empty and press the button, it gives the native component error message for required (i.e., it doesn't do the Yup resolving). But, if you enter some data, press the button, and then erase the data, then the Yup validation kicks in. How do I make it work before the button is pressed?
You need to remove required prop from input components because otherwise native html validation will kick in.
And if you want start validation before pressing submit button you need to use some other mode for form, for example:
const methods = useForm({
resolver: yupResolver(validationSchema),
mode: 'onChange' // or 'onBlur' for example
});
Codesandbox
More info in the docs
I'm working to create a geocoding component that allows a user to search for their address, using Quasar's <q-select /> component. I'm running in to one issue with the popup however.
After a user enter's the search query, I fetch the results from an API and the results are set to a reactive local state (which populates the select's options). Instead of the popup displaying though, it closes, and I have to click on the chevron icon twice for the popup to display the results.
This first image is what it looks like when I first click in to the input.
The second image shows what happens after entering a query. The data is fetched, options are set, and the popup closes.
The third image shows the select after clicking on the chevron icon twice.
How do I programmatically show the popup, so that once the results are fetched, the popup is displayed correctly?
Edit: Created a working repro here.
<template>
<q-select
ref="geolocateRef"
v-model="state.location"
:options="state.locations"
:loading="state.loadingResults"
clear-icon="clear"
dropdown-icon="expand_more"
clearable
outlined
:use-input="!state.location"
dense
label="Location (optional)"
#clear="state.locations = undefined"
#input-value="fetchOptions">
<template #prepend>
<q-icon name="place " />
</template>
<template #no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
</template>
<script lang='ts' setup>
import { reactive } from 'vue';
import { debounce, QSelect } from 'quasar';
import { fetchGeocodeResults } from '#/services';
const state = reactive({
location: undefined as string | undefined,
locations: undefined,
loadingResults: false,
geolocateRef: null as QSelect | null,
});
const fetchOptions = debounce(async (value: string) => {
if (value) {
state.loadingResults = true;
const results = await fetchGeocodeResults(value);
state.locations = results.items.map(item => ({
label: item.title,
value: JSON.stringify(item.position),
}));
state.loadingResults = false;
state.geolocateRef?.showPopup(); // doesn't work?
}
}, 500);
</script>
I'd also posted this question over in the Quasar Github discussions, and someone posted a brilliant solution.
<template>
<q-select
v-model="state.location"
:use-input="!state.location"
input-debounce="500"
label="Location (optional)"
:options="options"
dense
clear-icon="bi-x"
dropdown-icon="bi-chevron-down"
clearable
outlined
#filter="fetchOptions">
<template #prepend>
<q-icon name="bi-geo-alt" />
</template>
<template #no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
</template>
<script lang='ts' setup>
import { reactive, ref } from 'vue';
import { QSelect } from 'quasar';
import { fetchGeocodeResults } from '#/services';
interface Result {
position: {
lat: number;
lng: number;
}
title: string;
}
const state = reactive({
...other unrelated state,
location: undefined as string | undefined,
});
const options = ref([]);
const fetchOptions = async (val: string, update) => {
if (val === '') {
update();
return;
}
const needle = val.toLowerCase();
const results = await fetchGeocodeResults(needle);
options.value = results.items.map((item: Result) => ({
label: item.title,
value: JSON.stringify(item.position),
}));
update();
};
</script>
I'm using useCombobox from Downshift as a use-hook-form component and everything works fine except that I can't get the value when a user types in a value not in the list that is passed into useComboBox.
onSelectedItemChange is never fired unless the value is in the inputItems. This seems like it should be easy but I can't find an answer from the docs.
import React, { memo, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useCombobox } from 'downshift';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faChevronDown } from '#fortawesome/free-solid-svg-icons';
const comboboxStyles = { display: 'inline-block', marginLeft: '5px' };
let Item = ({ isHighlighted, getItemProps, item, index }) => {
return (
<li
className="auto-complete-list-item"
style={isHighlighted ? { backgroundColor: '#bde4ff' } : {}}
key={`${item}${index}`}
{...getItemProps({ item, index })}
>
{item}
</li>
);
};
Item = memo(Item);
const Autocomplete = ({ items, onChange, isSubmitting }) => {
const [inputItems, setInputItems] = useState(items);
const {
isOpen,
getToggleButtonProps,
getLabelProps,
getMenuProps,
getInputProps,
getComboboxProps,
highlightedIndex,
getItemProps,
inputValue,
reset
} = useCombobox({
items: inputItems,
onSelectedItemChange: ({ inputValue }) => onChange(inputValue),
onInputValueChange: ({ inputValue }) => {
setInputItems(
items.filter(item =>
item.toLowerCase().includes(inputValue.toLowerCase())
)
);
}
});
useEffect(() => {
if (inputValue.length > 0 && isSubmitting) reset();
}, [inputValue, isSubmitting, reset]);
return (
<div className="input-field">
<div style={comboboxStyles} {...getComboboxProps()}>
<input name="autocomplete" {...getInputProps()} />
<button
type="button"
{...getToggleButtonProps()}
aria-label="toggle menu"
>
<FontAwesomeIcon icon={faChevronDown} />
</button>
</div>
<ul {...getMenuProps()} className="auto-complete-list">
{isOpen &&
inputItems.map((item, index) => (
<Item
key={item}
isHighlighted={highlightedIndex === index}
getItemProps={getItemProps}
item={item}
index={index}
/>
))}
</ul>
</div>
);
};
Autocomplete.propTypes = {
list: PropTypes.array
};
export default Autocomplete;
You need to take control of item selection:
const {
isOpen,
selectItem,
getToggleButtonProps,
...
}
Then call selectItem in an onClick handler:
<Item
key={item}
onClick={() => selectItem(item)}
isHighlighted={highlightedIndex === index}
...
/>
I used react typescript project to ant design and i used this ant design form validation, but its not working correctly, anyone know how to fix that issue?
Thanks
git a this error
index.tsx?789d:32 Uncaught TypeError: Cannot read property
'getFieldDecorator' of undefined
import * as React from "react";
import { Input, Form, Icon, Button, } from "antd";
import 'antd/dist/antd.css';
import "./style.css";
export class Registerform extends React.Component<any> {
handleSubmit = (e:any) => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err:any, values:any) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
/* Start add bulk upload form*/
<div className="remindersform-section">
<Form onSubmit={this.handleSubmit}>
<Form.Item
label={
<span>
Nickname
<Icon type="question-circle-o" />
</span>
}
>
{getFieldDecorator('nickname', {
rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
})(<Input />)}
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit">
Register
</Button>
</Form.Item>
</Form>
</div>
);
}
}
Because default form props are not assigned with their types, antd provided a solution for it, Try like below
import * as React from "react";
import { FormComponentProps } from "antd/es/form";
import { Input, Form, Icon, Button, } from "antd";
import 'antd/dist/antd.css';
interface UserFormProps extends FormComponentProps {
form: any;
}
class Registerform extends React.Component<UserFormProps> {
handleSubmit = (e:any) => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err:any, values:any) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
/* Start add bulk upload form*/
<div className="remindersform-section">
<Form onSubmit={this.handleSubmit}>
<Form.Item
label={
<span>
Nickname
<Icon type="question-circle-o" />
</span>
}
>
{getFieldDecorator('nickname', {
rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
})(<Input />)}
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit">
Register
</Button>
</Form.Item>
</Form>
</div>
);
}
}
const WrappedForm = Form.create<UserFormProps>({})(Registerform);
export default WrappedForm;
In iOS safari, OverlayTrigger with trigger="focus" isn't able to dismiss when tapping outside. Here is my code:
<OverlayTrigger
trigger="focus"
placement="right"
overlay={ <Popover id="popoverID" title="Popover Title">
What a popover...
</Popover> } >
<a bsStyle="default" className="btn btn-default btn-circle" role="Button" tabIndex={18}>
<div className="btn-circle-text">?</div>
</a>
</OverlayTrigger>
I know that this is a known bug for Bootstrap cuz this doesn't even work on their own website in iOS, but does anyone know any method to go around it? It would be the best if it is something that doesn't require jQuery, but jQuery solution is welcome. Thanks.
OK, since no one else gives me a work around, I worked on this problem with my co-worker together for 3 days, and we came up with this heavy solution:
THE PROBLEM:
With trigger="focus", Bootstrap Popover/Tooltip can be dismissed when CLICKING outside the Popover/Tooltip, but not TOUCHING. Android browsers apparently changes touches to clicks automatically, so things are fine on Android. But iOS safari and browsers that is based on iOS safari (iOS chrome, iOS firefox, etc...) don't do that.
THE FIX:
We found out that in React Bootstrap, the Overlay component actually lets you customize when to show the Popover/Tooltip, so we built this component InfoOverlay based on Overlay. And to handle clicking outside the component, we need to add event listeners for both the Popover/Tooltip and window to handle both 'mousedown' and 'touchstart'. Also, this method would make the Popover have its smallest width all the time because of the padding-right of the component is initially 0px, and we make based on the width of some parent component so that it is responsive based on the parent component. And the code looks like this:
import React, { Component, PropTypes as PT } from 'react';
import {Popover, Overlay} from 'react-bootstrap';
export default class InfoOverlay extends Component {
static propTypes = {
PopoverId: PT.string,
PopoverTitle: PT.string,
PopoverContent: PT.node,
// You need to add this prop and pass it some numbers
// if you need to customize the arrowOffsetTop, it's sketchy...
arrowOffsetTop: PT.number,
// This is to be able to select the parent component
componentId: PT.string
}
constructor(props) {
super(props);
this.state = {
showPopover: false,
popoverClicked: false
};
}
componentDidMount() {
// Here are the event listeners and an algorithm
// so that clicking popover would not dismiss itself
const popover = document.getElementById('popoverTrigger');
if (popover) {
popover.addEventListener('mousedown', () => {
this.setState({
popoverClicked: true
});
});
popover.addEventListener('touchstart', () => {
this.setState({
popoverClicked: true
});
});
}
window.addEventListener('mousedown', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
window.addEventListener('touchstart', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
// this is to resize padding-right when window resizes
window.onresize = ()=>{
this.setState({});
};
}
// This function sets the style and more importantly, padding-right
getStyle() {
if (document.getElementById(this.props.componentId) && document.getElementById('popoverTrigger')) {
const offsetRight = document.getElementById(this.props.componentId).offsetWidth - document.getElementById('popoverTrigger').offsetLeft - 15;
return (
{display: 'inline-block', position: 'absolute', 'paddingRight': offsetRight + 'px'}
);
}
return (
{display: 'inline-block', position: 'absolute'}
);
}
overlayOnClick() {
this.setState({
showPopover: !(this.state.showPopover)
});
}
render() {
const customPopover = (props) => {
return (
{/* The reason why Popover is wrapped by another
invisible Popover is so that we can customize
the arrowOffsetTop, it's sketchy... */}
<div id="customPopover">
<Popover style={{'visibility': 'hidden', 'width': '100%'}}>
<Popover {...props} arrowOffsetTop={props.arrowOffsetTop + 30} id={this.props.PopoverId} title={this.props.PopoverTitle} style={{'marginLeft': '25px', 'marginTop': '-25px', 'visibility': 'visible'}}>
{this.props.PopoverContent}
</Popover>
</Popover>
</div>
);
};
return (
<div id="popoverTrigger" style={this.getStyle()}>
<a bsStyle="default" className="btn btn-default btn-circle" onClick={this.overlayOnClick.bind(this)} role="Button" tabIndex={13}>
<div id="info-button" className="btn-circle-text">?</div>
</a>
<Overlay
show={this.state.showPopover}
placement="right"
onHide={()=>{this.setState({showPopover: false});}}
container={this}>
{customPopover(this.props)}
</Overlay>
</div>
);
}
}
In the end, this is a heavy work around because it is a big amount of code for a fix, and you can probably feel your site is slowed down by a tiny bit because of the 4 event listeners. And the best solution is just tell Bootstrap to fix this problem...