---
title: "React Range Flight Booking Example | Mobiscroll"
component: "Range"
framework: "React"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/react/range/flight-booking"
---

# Flight Booking - React Range demo

Build a flight booking date picker with the React range component — round-trip and one-way selection. For React and Next.JS.

- [Live demo](https://demo.mobiscroll.com/react/range/flight-booking)
- [View React JS code on GitHub](https://github.com/acidb/mobiscroll-demos-react/tree/main/src/demos/datepicker/range/flight-booking)
- [View React TS code on GitHub](https://github.com/acidb/mobiscroll-demos-react-ts/tree/main/src/demos/datepicker/range/flight-booking)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/range/flight-booking.md), [jQuery](https://demo.mobiscroll.com/jquery/range/flight-booking.md), [Angular](https://demo.mobiscroll.com/angular/range/flight-booking.md), [Vue](https://demo.mobiscroll.com/vue/range/flight-booking.md)

## Description

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.

## What this demo shows

- Shows five flight booking examples for selecting a date range with differently customized range pickers.
- **Single input** The first example uses a single input for selecting the outbound and return dates.
- **Two inputs** The second example uses two separate inputs, one for the Outbound date and another for the Return date.
- **Invalid days** The third example uses two inputs and displays invalid dates for days when no flights are available. These dates are disabled and cannot be selected.
- **One way or round trip** The fourth example uses separate Outbound and Return inputs together with Round trip and One way options. Round trip is selected by default. Switching to One way disables the Return input and allows selecting only the outbound date.
- **Custom button** The fifth example uses a single input and adds a custom `One way only` button to the footer of the range picker, next to the `Set` button. This provides an alternative way to book a one-way flight directly from the picker.
- **Input behavior** The range picker opens below the input over a darkened backdrop. Clicking outside the picker closes it.
- **Outbound and Return inputs** In the examples with two inputs, clickable Outbound and Return inputs appear above the calendar. The active input has a white background, while the inactive input has a gray background. A clear button appears after a date range is selected.
- **Month view** Every example displays two months at the same time, with past dates disabled. A fixed weekday header shows abbreviated day names from Sunday through Saturday, with dates arranged in a grid below.
- **Month navigation** Drag the calendar left or right to navigate between months.
- **Day cell states** Hovering over a day highlights its number with a gray background. The first selected day becomes the range start and the second becomes the range end. Selected dates and the dates between them are visually highlighted. The current date is highlighted by default.
- **Footer actions** The gray `Cancel` button discards the changes, while the blue `Set` button confirms the selected date range. Until a valid range is selected, the `Set` button remains inactive.
- **Input value** Confirming the selection with `Set` displays the selected date or date range in the corresponding input using a month, day, and year format.

## Best for

- **Airline booking forms** where travelers need to select outbound and return dates for round-trip flights.
- **One-way flight booking** where only a departure date is required and the return date can be disabled or skipped.
- **Flight availability calendars** where dates without available flights need to be disabled and clearly distinguished from selectable dates.
- **Travel search interfaces** that need separate Outbound and Return inputs while keeping the date selection inside a single range picker.
- **Flexible booking experiences** where users can switch between round-trip and one-way travel directly from the date picker.

## Tutorial

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.

### Setting it up on a single input

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](https://demo.mobiscroll.com/react/range/mobile-desktop-display).

```jsx
const 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}
/>
```

### Using two inputs

Instead of a single input you can have two, one for **Outbound** and another for **Return**.

```jsx
const [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>
```

### Not all days have flights

Besides setting min & max boundaries, days without flights should be [disabled](https://demo.mobiscroll.com/react/range/disabled-invalid-values). These could be single occurrence or [recurring dates](https://demo.mobiscroll.com/react/range/recurring-values), 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).

```jsx
const [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>
```

### One way or roundtrip

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**.

```jsx
const [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}
/>
```

### Alternative to radio buttons

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.

```jsx
const [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}/>
```

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/react/datepicker/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/react/datepicker/api#opt-themeVariant) - option
- [`disabled`](https://mobiscroll.com/docs/react/datepicker/api#opt-disabled) - option
- [`select`](https://mobiscroll.com/docs/react/datepicker/api#opt-select) - option
- [`min`](https://mobiscroll.com/docs/react/datepicker/api#opt-min) - option
- [`max`](https://mobiscroll.com/docs/react/datepicker/api#opt-max) - option
- [`pages`](https://mobiscroll.com/docs/react/datepicker/api#opt-pages) - option
- [`inputStyle`](https://mobiscroll.com/docs/react/datepicker/api#opt-inputStyle) - option
- [`labelStyle`](https://mobiscroll.com/docs/react/datepicker/api#opt-labelStyle) - option
- [`controls`](https://mobiscroll.com/docs/react/datepicker/api#opt-controls) - option
- [`startInput`](https://mobiscroll.com/docs/react/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/react/datepicker/api#opt-endInput) - option
- [`invalid`](https://mobiscroll.com/docs/react/datepicker/api#opt-invalid) - option
- [`inRangeInvalid`](https://mobiscroll.com/docs/react/datepicker/api#opt-inRangeInvalid) - option
- [`onChange`](https://mobiscroll.com/docs/react/forms/radio#event-onChange) - event
- [`theme`](https://mobiscroll.com/docs/react/forms/radio#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/react/forms/radio#opt-themeVariant) - option
- [`disabled`](https://mobiscroll.com/docs/react/forms/input#opt-disabled) - option
- [`buttons`](https://mobiscroll.com/docs/react/datepicker/api#opt-buttons) - option
- [`onChange`](https://mobiscroll.com/docs/react/datepicker/api#event-onChange) - event
- [`onTempChange`](https://mobiscroll.com/docs/react/datepicker/api#event-onTempChange) - event

## Code

### JSX

```jsx
import { Datepicker, Input, Page, Radio, RadioGroup, setOptions } from '@mobiscroll/react';
import { useCallback, useMemo, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

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;
```

### TSX

```tsx
import {
  Datepicker,
  Input,
  MbscDatepickerChangeEvent,
  MbscDateType,
  MbscPopupButton,
  Page,
  Radio,
  RadioGroup,
  setOptions,
} from '@mobiscroll/react';
import { ChangeEvent, FC, useCallback, useMemo, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

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;
```

## Implementation notes

- Set `select: 'range'` and `controls: ['calendar']` on `Datepicker`; `min`/`max` bound the selectable window (e.g. `min: <today>`, `max: <today + 6 months>`) to prevent picking past dates.
- `pages: 2` shows two months side by side across all five examples.
- `showRangeLabels: true` together with `rangeStartLabel`/`rangeEndLabel` (e.g. `'Outbound'`/`'Return'`) renames the built-in Start/End range labels shown above the calendar.
- Single-input mode needs no extra wiring beyond `select: 'range'`; two-input mode binds `startInput`/`endInput` to separate Outbound/Return inputs - string selectors in JS/jQuery (`startInput: '#start', endInput: '#end'`), refs in React/Vue, template reference variables in Angular.
- Unavailable flight dates are disabled via `invalid`, which accepts both single dates and recurring rules (e.g. `{ recurring: { repeat: 'weekly', weekDays: 'TU,TH' } }` alongside an exact `Date`).
- `inRangeInvalid: true` allows a selected range to pass through invalid dates in the middle while still keeping the range's start/end endpoints on valid days; when `inRangeInvalid` is `false` (or omitted), no invalid date may fall anywhere inside the selected range - useful when a booking must be fully contiguous (e.g. hotel stays).
- Toggling one-way vs. round-trip is done by changing the `select` option at runtime between `'range'` and `'date'` (e.g. via `.setOptions({ select: 'date' })` in JS/jQuery, or updating a bound `select` prop/binding in React/Vue/Angular) based on a radio control's value; the paired Return input can be disabled in step with it (e.g. `.setOptions({ disabled: true })` on that input's instance).
- The footer buttons can be customized through the `buttons` option, including a custom action button object (`{ text, disabled, handler }`); an `onTempChange` handler (React: `onTempChange`; Angular: `(onTempChange)`; Vue: `@temp-change`; JS/jQuery: `onTempChange`) fires while the user is still choosing dates and can re-evaluate the custom button's `disabled` state via `inst.getTempVal()` (returns the in-progress `[start, end]` selection) and rebuild the `buttons` array through `setOptions`.
- The custom button's handler reads the in-progress start date with `getTempVal()`, commits it as a one-way selection via `setVal([start, null])`, and closes the picker with `close()`.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-react.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config react
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config react --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/react/getting-started/installation

## Related demos

- Previous: [Date Filter with Preset Ranges](https://demo.mobiscroll.com/react/range/date-filtering-with-predefined-ranges.md) - Build a date filtering UI with the React date range picker — choose from custom or preset ranges (Today, Last 7 days, Last 30 days).
- Next: [Rental Availability](https://demo.mobiscroll.com/react/range/book-rental-months-ahead.md) - Build a rental availability calendar with the React date range picker — multi-month view with seasonal pricing display and booking validation.
