Creating a react container component in typescript for a MuiPickersUtilsProvider component

1.7k Views Asked by At

I have a component that is responsible for a date time picker. I wish that another parent component call this child date picker component so I can have a component of "wrapper/container" and one just for the data picker. Moreover I am not having success on doing this on typescript.

Here is my code sandbox: https://codesandbox.io/s/date-picker-ubosl?file=/src/App.tsx

This is my code, The function handleChangeDateType do a console.log() in the end with the results that should be passed to the Parent component.

    import React, { useState } from "react";

import { DatePicker, MuiPickersUtilsProvider } from "@material-ui/pickers";

import ReactSelect, { ValueType } from "react-select";
import DateFnsUtils from "@date-io/date-fns";

export enum DateValueEnum {
  Today = "TODAY",
  Yesterday = "YESTERDAY",
  Custom = "CUSTOM"
}
type OptionType = {
  key: string;
  label: string;
};
const options: OptionType[] = [
  { key: DateValueEnum.Today, label: "Today" },
  { key: DateValueEnum.Yesterday, label: "Yesterday" },
  { key: DateValueEnum.Custom, label: "Custom" }
];

export default function App() {
  const [selectedDate, setSelectedDate] = React.useState<Date | null>(
    new Date()
  );

  const [selectedOption, setSelectedOption] = useState<ValueType<OptionType>>();

  const handleChangeDateType = (value: string) => {
    let startDate: Date | undefined = undefined;
    let endDate: Date | undefined = undefined;
    const startToday = new Date();
    switch (value) {
      case DateValueEnum.Today: {
        startDate = startToday;
        endDate = new Date(startDate.getTime());
        break;
      }
      case DateValueEnum.Yesterday: {
        startDate = new Date(startToday.getTime());
        startDate.setDate(startDate.getDate() - 1);
        endDate = new Date(startDate.getTime());
        break;
      }
      default: /**  nothing */
    }
    console.log(startDate);
    console.log(endDate);
  };

  const handleChange = (option: ValueType<OptionType>) => {
    setSelectedOption(option.key);
    handleChangeDateType(option.key);
  };

  return (
    <div className="App">
      <div>
        <ReactSelect
          value={selectedOption as ValueType<OptionType>}
          onChange={(option) => handleChange(option)}
          isMulti={false}
          options={options}
        />

        {selectedOption === DateValueEnum.Custom ? (
          <div style={{ display: "flex" }}>
            <div style={{ width: "50%", float: "left", paddingRight: "5px" }}>
              <MuiPickersUtilsProvider utils={DateFnsUtils}>
                <DatePicker
                  fullWidth
                  margin="normal"
                  required={true}
                  error={false}
                  invalidLabel={"Several values..."}
                  value={selectedDate}
                  onChange={(newDate) => setSelectedDate(newDate)}
                  format="MM/dd/yyyy"
                />
              </MuiPickersUtilsProvider>
            </div>
            <MuiPickersUtilsProvider utils={DateFnsUtils}>
              <DatePicker
                fullWidth
                margin="normal"
                required={true}
                error={false}
                invalidLabel={"Several values..."}
                value={selectedDate}
                onChange={(newDate) => setSelectedDate(newDate)}
                format="MM/dd/yyyy"
              />
            </MuiPickersUtilsProvider>
          </div>
        ) : null}
      </div>
    </div>
  );
}
1

There are 1 best solutions below

5
On BEST ANSWER

Edit: To answer the original question, what I would do is to extract the states out of the DateComponent and have them in the parent, so you can pass the dates and the onChange function from the parent to the date component.

// App.tsx
import React from "react";

import { MuiPickersUtilsProvider } from "@material-ui/pickers";

import DateFnsUtils from "@date-io/date-fns";
import DateComponent from "./DateComponent";

export type IDates = {
  startDate: Date | null;
  endDate: Date | null;
};

export type IDatesKeys = keyof IDates;

const formatDate = (date: Date) => date.toLocaleString();

export default function App() {
  const [dates, setDates] = React.useState<IDates>({
    startDate: null,
    endDate: null
  });

  const onChangeDate = (dates: IDates) => {
    setDates(dates);
  };

  // Remember to wrap everything with the MuiPickersUtilsProvider
  return (
    <MuiPickersUtilsProvider utils={DateFnsUtils}>
      <div className="App">
        <DateComponent dates={dates} onChange={onChangeDate} />
        {dates.startDate && <p>Start Date: {formatDate(dates.startDate)}</p>}
        {dates.endDate && <p>End Date: {formatDate(dates.endDate)}</p>}
      </div>
    </MuiPickersUtilsProvider>
  );
}

After that in your DateComponent you need to do this:

// DateComponent.tsx
import React from "react";

import { DatePicker } from "@material-ui/pickers";

import ReactSelect, { ValueType } from "react-select";

import { IDates, IDatesKeys } from "./App";

type IDateComponent = {
  dates: IDates;
  onChange: (dates: IDates) => void;
};

export enum DateValueEnum {
  Today = "TODAY",
  Yesterday = "YESTERDAY",
  Custom = "CUSTOM"
}
type OptionType = {
  key: DateValueEnum;
  label: string;
};

const options: OptionType[] = [
  { key: DateValueEnum.Today, label: "Today" },
  { key: DateValueEnum.Yesterday, label: "Yesterday" },
  { key: DateValueEnum.Custom, label: "Custom" }
];

export default function DateComponent({ dates, onChange }: IDateComponent) {
  const [selectedOption, setSelectedOption] = React.useState<
    ValueType<OptionType, false>
  >(null);

  const handleChangeDateType = (value: DateValueEnum) => {
    const today = new Date();
    let startDate = new Date();
    let endDate = new Date();
    switch (value) {
      case DateValueEnum.Today: {
        endDate.setDate(today.getDate() - 1);
        break;
      }
      // No need to do anything here because you will control the custom option with the other datepickers.
      case DateValueEnum.Custom: {
       return;
      }
      // No need for the yesterday case since there're only two cases, the custom will be handled with another function.
      default: {
        startDate.setDate(today.getDate() - 1);
        break;
      }
    }
    // Here you call the function that will update the parent state.
    onChange({ startDate, endDate });
  };

  React.useEffect(() => {
    if (selectedOption) {
      handleChangeDateType(selectedOption.key);
    }
  }, [selectedOption]);

  const handleChange = (option: ValueType<OptionType, false>) => {
    if (option) {
      setSelectedOption(option);
    }
  };

  // This function will update the custom logic, using the object keys to update the right date
  // You need to call it like this customOnChange("startDate") and this will return an onChange valid date function.
  const customOnChange = (key: IDatesKeys) => (date: Date | null) => {
    onChange({
      ...dates,
      [key]: date
    });
  };

  return (
    <div>
      <ReactSelect
        value={selectedOption}
        onChange={handleChange}
        options={options}
        isMulti={false}
        autoFocus
      />

      {selectedOption?.key === DateValueEnum.Custom && (
        <div style={{ display: "flex" }}>
          <div style={{ width: "50%", float: "left", paddingRight: "5px" }}>
            <DatePicker
              fullWidth
              margin="normal"
              required={true}
              error={false}
              invalidLabel={"Several values..."}
              value={dates.startDate}
              onChange={customOnChange("startDate")}
              format="MM/dd/yyyy"
            />
          </div>
          <DatePicker
            fullWidth
            margin="normal"
            required={true}
            error={false}
            invalidLabel={"Several values..."}
            value={dates.endDate}
            onChange={customOnChange("endDate")}
            format="MM/dd/yyyy"
          />
        </div>
      )}
    </div>
  );
}

Here's a forked sandbox if you want to take a look: https://codesandbox.io/s/date-picker-forked-fyt7t