<div id="root">
@import "https://cdnjs.cloudflare.com/ajax/libs/react-datepicker/4.6.0/react-datepicker.css";

#root > div > button {
  margin-bottom: 1rem;
}
View Compiled
import React from "https://cdn.skypack.dev/[email protected]";
import ReactDOM from "https://cdn.skypack.dev/[email protected]";
import DatePicker from "https://cdn.skypack.dev/[email protected]";
import * as JsJodaCore from "https://cdn.skypack.dev/@js-joda/[email protected]";

const { ZonedDateTime, Instant, ZoneId } = JsJodaCore;

/**
 * You don't need this, but it helps to make the example more clear
 */
const FormattedErrorStack = (props) => {
  const { error } = props;
  if (!error) {
    throw new Error(`expected the error prop to be truthy`);
  }

  return (
    <div style={{ whiteSpace: "pre-line", color: "red" }}>
      {error.stack ?? error.toString()}
    </div>
  );
};

const CUSTOM_COMPONENT_NAME = "ZonedDateTimePicker";
const ZonedDateTimePicker = (props) => {
  const [error, setError] = React.useState(null);
  const { zone, onChange, selected, ...otherProps } = props;

  if (!!selected && !ZoneId.from(selected).equals(zone)) {
    throw new Error(
      `The provided date ("${selected}") was not in the expected ZoneId ("${zone}")`
    );
  }

  const convertToZonedDateTime = (d) => {
    try {
      var instant = Instant.parse(d.toISOString());
      var zdt = ZonedDateTime.ofInstant2(instant, zone);

      onChange(zdt);
    } catch (err) {
      setError(err);
    }
  };

  const prepareForDatePickerInput = (zdt) => {
    return new Date(zdt.toInstant().toString());
  };

  if (error) {
    console.error(error);
    return (
      <div>
        <h1>Error within {CUSTOM_COMPONENT_NAME}</h1>
        <div>
          If you are seeing this then that means that {CUSTOM_COMPONENT_NAME} is
          not doing the conversion properly and that means that this example
          needs to be updated. Exact error message:
        </div>
        <br />
        <FormattedErrorStack error={error} />
      </div>
    );
  }

  return (
    <DatePicker
      selected={selected ? prepareForDatePickerInput(selected) : null}
      onChange={convertToZonedDateTime}
      showTimeInput
      preventOpenOnFocus={true}
      timeInputLabel="Time:"
      dateFormat="MM/dd/yyyy h:mm aa"
      otherProps
    />
  );
};

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { error };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    console.error(error, errorInfo);
  }

  render() {
    if (!!this.state.error) {
      // You can render any custom fallback UI
      return (
        <div>
          <h1>Something went wrong.</h1>
          <FormattedErrorStack error={this.state.error} />
        </div>
      );
    }

    return this.props.children;
  }
}

const App = () => {
  const [date, setDate] = React.useState(null);

  const onChange = (updatedDate) => {
    setDate(updatedDate);
  };

  return (
    <ErrorBoundary>
      <ZonedDateTimePicker
        selected={date}
        zone={ZoneId.of("-04:00")}
        onChange={onChange}
      />
      {!date ? (
        <h2>Please select a date and time</h2>
      ) : (
        <div>
          <h2>Selected {CUSTOM_COMPONENT_NAME.replace("Picker", "")}</h2>
          <div>String format for sending over HTTP: {date.toString()}</div>
        </div>
      )}
    </ErrorBoundary>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.