Flight Booking
For a great booking experience a good date picker is essential. The best implementations are contextual and take the intent (what people want to do) and context (where and how are they doing it) into account. Learn how to use and customize the range picker and see how you can add your flavor that matches with the overall experience of your application.
Demo
When building flight booking systems you want to make sure that you set up min & max boundaries for the selectable days. This way you ensure that people can't select dates from the past.
The range picker can be set up on one or two inputs. If you are building a fully custom experience, you can embed it into your own modal.
jsxconst now = new Date();
const min = now;
const max = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate());
<Datepicker
controls={['calendar']}
select="range"
min={min}
max={max}
/>
Instead of a single input you can have two, one for Outbound and another for Return.
jsxconst [start, setStart] = React.useState(null);
const [end, setEnd] = React.useState(null);
const now = new Date();
const [min, setMin] = React.useState(now);
const [max, setMax] = React.useState(now.getFullYear(), now.getMonth() + 6, now.getDate());
<Datepicker
controls={['calendar']}
select="range"
startInput={start}
endInput={end}
min={min}
max={max}
/>
<Input ref={setStart} placeholder="Please Select...">Outbound</Input>
<Input ref={setEnd} placeholder="Please Select...">Return</Input>
Besides setting min & max boundaries, days without flights should be disabled. These could be single occurrence or recurring dates, like specific weekdays. Setting inRangeInvalid: true allows ranges to contain invalids, it only makes sure that the start and end dates are on valid days. If inRangeInvalid is false, no invalid dates are allowed inside a selection (useful for hotel booking).
jsxconst [start, setStart] = React.useState(null);
const [end, setEnd] = React.useState(null);
const now = new Date();
const [invalid, setInvalid] = React.useState([
{
recurring: {
repeat: 'weekly',
weekDays: 'TU,TH'
},
},
new Date(now.getFullYear(), now.getMonth(), 25)
]);
<Datepicker
controls={['calendar']}
select="range"
startInput={start}
endInput={end}
invalid={invalid}
inRangeInvalid={true}
/>
<Input ref={setStart} placeholder="Please Select...">Outbound</Input>
<Input ref={setEnd} placeholder="Please Select...">Return</Input>
You will probably need to give people the option to select a single date for one way trips. This can be easily accomplished by dynamically setting the select option to 'date'. Toggling with a radio button between 'date'/'range' will switch between single date selection and range selection.
jsxconst [bookingType, setBookingType] = React.useState('round');
const selectType = React.useMemo(() => bookingType === 'oneway' ? 'date' : 'range', [bookingType]);
const bookingTypeChange = (ev) => {
setBookingType(ev.target.value);
};
<RadioGroup>
<Radio value="round" checked={bookingType === 'round'} onChange={bookingTypeChange} label="Round trip" />
<Radio value="oneway" checked={bookingType === 'oneway'} onChange={bookingTypeChange} label="One way" />
</RadioGroup>
<Datepicker
controls={['calendar']}
select={selectType}
/>
If you don't want to add another control (like a radio button or segmented) to your UI, you can solve the "one way question" with a custom button on the dialog and a little custom logic. The idea is to track if at least one value is selected and dynamically enable the button.
jsxconst [inst, setInst] = React.useState(null);
const [val, setVal] = React.useState();
const valChange = (ev: any) => {
setVal(ev.value);
};
const [disabled, setDisabled] = React.useState(nrOfValues(inst) === 0);
const onTempChange = (ev) => {
setDisabled(nrOfValues(inst) < 1);
};
const buttons = React.useMemo(() => {
return ['set', 'cancel', {
disabled,
handler: () => {
const [start] = inst.getTempVal();
setVal([start, null]);
inst.close();
},
text: 'Oneway'
}];
}, [disabled, inst]);
function nrOfValues(instance) {
const tempVal = (instance && instance.getTempVal()) || [];
return tempVal.filter((v) => v).length;
};
<Datepicker
controls={['calendar']}
select="range"
ref={setInst}
buttons={buttons}
value={val}
onChange={valChange}
onTempChange={onTempChange}/>
import { Datepicker, Input, Page, Radio, RadioGroup, setOptions } from '@mobiscroll/react';
import { useCallback, useMemo, useState } from 'react';
setOptions({
locale: ,
theme: ' ',
themeVariant: ' '
});
const now = new Date();
const min = now;
const max = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate());
function nrOfValues(instance) {
const tempVal = (instance && instance.getTempVal()) || [];
return tempVal.filter((v) => v).length;
}
function App() {
const [start, setStart] = useState(null);
const [end, setEnd] = useState(null);
const [startInv, setStartInv] = useState(null);
const [endInv, setEndInv] = useState(null);
const [startBooking, setStartBooking] = useState(null);
const [endBooking, setEndBooking] = useState(null);
const [bookingType, setBookingType] = useState('round');
const [inst, setInst] = useState(null);
const [val, setVal] = useState();
const [disabled, setDisabled] = useState(nrOfValues(inst) === 0);
const invalid = useMemo(
() => [
{
recurring: {
repeat: 'weekly',
weekDays: 'TU,TH',
},
},
new Date(now.getFullYear(), now.getMonth(), 25),
],
[],
);
const selectType = useMemo(() => (bookingType === 'oneway' ? 'date' : 'range'), [bookingType]);
const bookingTypeChange = useCallback((ev) => {
setBookingType(ev.target.value);
}, []);
const valChange = useCallback((ev) => {
setVal(ev.value);
}, []);
const changeTripType = useCallback(() => {
setDisabled(nrOfValues(inst) < 1);
}, [inst]);
const buttons = useMemo(
() => [
'cancel',
{
disabled,
handler: () => {
const [start] = inst.getTempVal();
setVal([start, null]);
inst.close();
},
text: 'One way only',
},
'set',
],
[disabled, inst],
);
return (
<Page>
<div className="mbsc-grid mbsc-no-padding">
<div className="mbsc-row">
<div className="mbsc-col-sm-12">
<Datepicker
select="range"
min={min}
max={max}
pages={2}
label="Pick your flight"
inputStyle="outline"
labelStyle="stacked"
placeholder="Please select..."
/>
</div>
</div>
<div className="mbsc-row">
<div className="mbsc-col-6">
<Datepicker controls={['calendar']} select="range" startInput={start} endInput={end} min={min} max={max} pages={2} />
<Input ref={setStart} label="Outbound" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
</div>
<div className="mbsc-col-6">
<Input ref={setEnd} label="Return" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
</div>
</div>
<div className="mbsc-row">
<div className="mbsc-col-6">
<Datepicker
select="range"
startInput={startInv}
endInput={endInv}
min={min}
max={max}
pages={2}
invalid={invalid}
inRangeInvalid={true}
/>
<Input ref={setStartInv} label="Outbound" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
</div>
<div className="mbsc-col-6">
<Input ref={setEndInv} label="Return" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
</div>
</div>
<div className="mbsc-row">
<RadioGroup name="flight-booking-type" theme="material" themeVariant="light">
<Radio
value="round"
checked={bookingType === 'round'}
onChange={bookingTypeChange}
label="Round trip"
theme="material"
themeVariant="light"
/>
<Radio
value="oneway"
checked={bookingType === 'oneway'}
onChange={bookingTypeChange}
label="One way"
theme="material"
themeVariant="light"
/>
</RadioGroup>
</div>
<div className="mbsc-row">
<div className="mbsc-col-6">
<Datepicker
select={selectType}
startInput={startBooking}
endInput={endBooking}
min={min}
max={max}
pages={2}
label="Outbound"
inputStyle="outline"
labelStyle="stacked"
placeholder="Please Select..."
/>
{selectType === 'range' && (
<Input ref={setStartBooking} label="Outbound" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
)}
</div>
<div className="mbsc-col-6">
<Input
ref={setEndBooking}
label="Return"
inputStyle="outline"
labelStyle="stacked"
placeholder="Please Select..."
disabled={bookingType === 'oneway'}
/>
</div>
</div>
<div className="mbsc-row">
<div className="mbsc-col-12">
<Datepicker
select="range"
min={min}
max={max}
pages={2}
ref={setInst}
buttons={buttons}
value={val}
onChange={valChange}
onTempChange={changeTripType}
label="Pick your flight"
inputStyle="outline"
labelStyle="stacked"
placeholder="Please select..."
/>
</div>
</div>
</div>
</Page>
);
}
export default App;
import {
Datepicker,
Input,
MbscDatepickerChangeEvent,
MbscDateType,
MbscPopupButton,
Page,
Radio,
RadioGroup,
setOptions ,
} from '@mobiscroll/react';
import { ChangeEvent, FC, useCallback, useMemo, useState } from 'react';
setOptions({
locale: ,
theme: ' ',
themeVariant: ' '
});
const now = new Date();
const min = now;
const max = new Date(now.getFullYear(), now.getMonth() + 6, now.getDate());
function nrOfValues(instance: Datepicker | null) {
const tempVal = ((instance && instance.getTempVal()) as MbscDateType[]) || [];
return tempVal.filter((v) => v).length;
}
const App: FC = () => {
const [start, setStart] = useState<Input | null>(null);
const [end, setEnd] = useState<Input | null>(null);
const [startInv, setStartInv] = useState<Input | null>(null);
const [endInv, setEndInv] = useState<Input | null>(null);
const [startBooking, setStartBooking] = useState<Input | null>(null);
const [endBooking, setEndBooking] = useState<Input | null>(null);
const [bookingType, setBookingType] = useState<string>('round');
const [inst, setInst] = useState<Datepicker | null>(null);
const [val, setVal] = useState<(MbscDateType | null)[]>();
const [disabled, setDisabled] = useState(nrOfValues(inst) === 0);
const invalid = useMemo(
() => [
{
recurring: {
repeat: 'weekly',
weekDays: 'TU,TH',
},
},
new Date(now.getFullYear(), now.getMonth(), 25),
],
[],
);
const selectType = useMemo(() => (bookingType === 'oneway' ? 'date' : 'range'), [bookingType]);
const bookingTypeChange = (ev: ChangeEvent<HTMLInputElement>) => {
setBookingType(ev.target.value);
};
const valChange = useCallback((ev: MbscDatepickerChangeEvent) => {
setVal(ev.value as MbscDateType[]);
}, []);
const changeTripType = useCallback(() => {
setDisabled(nrOfValues(inst) < 1);
}, [inst]);
const buttons = useMemo<(string | MbscPopupButton)[]>(
() => [
'cancel',
{
disabled,
handler: () => {
const start = inst!.getTempVal() as MbscDateType[];
setVal([start[0], null]);
inst!.close();
},
text: 'One way only',
},
'set',
],
[disabled, inst],
);
return (
<Page>
<div className="mbsc-grid mbsc-no-padding">
<div className="mbsc-row">
<div className="mbsc-col-sm-12">
<Datepicker
select="range"
min={min}
max={max}
pages={2}
label="Pick your flight"
inputStyle="outline"
labelStyle="stacked"
placeholder="Please select..."
/>
</div>
</div>
<div className="mbsc-row">
<div className="mbsc-col-6">
<Datepicker controls={['calendar']} select="range" startInput={start} endInput={end} min={min} max={max} pages={2} />
<Input ref={setStart} label="Outbound" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
</div>
<div className="mbsc-col-6">
<Input ref={setEnd} label="Return" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
</div>
</div>
<div className="mbsc-row">
<div className="mbsc-col-6">
<Datepicker
select="range"
startInput={startInv}
endInput={endInv}
min={min}
max={max}
pages={2}
invalid={invalid}
inRangeInvalid={true}
/>
<Input ref={setStartInv} label="Outbound" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
</div>
<div className="mbsc-col-6">
<Input ref={setEndInv} label="Return" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
</div>
</div>
<div className="mbsc-row">
<RadioGroup name="flight-booking-type" theme="material" themeVariant="light">
<Radio
value="round"
checked={bookingType === 'round'}
onChange={bookingTypeChange}
label="Round trip"
theme="material"
themeVariant="light"
/>
<Radio
value="oneway"
checked={bookingType === 'oneway'}
onChange={bookingTypeChange}
label="One way"
theme="material"
themeVariant="light"
/>
</RadioGroup>
</div>
<div className="mbsc-row">
<div className="mbsc-col-6">
<Datepicker
select={selectType}
startInput={startBooking}
endInput={endBooking}
min={min}
max={max}
pages={2}
label="Outbound"
inputStyle="outline"
labelStyle="stacked"
placeholder="Please Select..."
/>
{selectType === 'range' && (
<Input ref={setStartBooking} label="Outbound" inputStyle="outline" labelStyle="stacked" placeholder="Please Select..." />
)}
</div>
<div className="mbsc-col-6">
<Input
ref={setEndBooking}
label="Return"
inputStyle="outline"
labelStyle="stacked"
placeholder="Please Select..."
disabled={bookingType === 'oneway'}
/>
</div>
</div>
<div className="mbsc-row">
<div className="mbsc-col-12">
<Datepicker
select="range"
min={min}
max={max}
pages={2}
ref={setInst}
buttons={buttons}
value={val}
onChange={valChange}
onTempChange={changeTripType}
label="Pick your flight"
inputStyle="outline"
labelStyle="stacked"
placeholder="Please select..."
/>
</div>
</div>
</div>
</Page>
);
};
export default App;