0

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>
  );
}
Catarina Nogueira
  • 1,024
  • 2
  • 12
  • 28

1 Answers1

1

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

jean182
  • 3,213
  • 2
  • 16
  • 27
  • Hi Jean, Thank you for your tips I will work on it! Moreover this do not solve my issue of the question that was to have a second component (ex: DateContainer) that calls this data Component and have the state actualize. Can you help me with that? – Catarina Nogueira Sep 28 '21 at 07:45
  • Also I've just realized that the "custom date" is not working, it shows always the same date on both date pickers :( – Catarina Nogueira Sep 28 '21 at 12:59
  • 1
    @CatarinaNogueira Do you want to have the date values in the parent container? And you should add some logic in the default switch statement for the custom logic, also you should probably add a second state to handle the second custom input in your component. – jean182 Sep 28 '21 at 14:00
  • 1
    Hi Jean, I would like to have a parent component that will have all the states and will be just:
    , that was what I was trying to do but I could not manage to do correctly. About the switch I will try and about the second state I will try too. So going back to the question of the post was to have a parent component with the states that will be with
    . Do you have any idea how can I do this?
    – Catarina Nogueira Sep 28 '21 at 14:15
  • Hey @CatarinaNogueira just updated the answer with my idea on how to do that from the parent. – jean182 Sep 28 '21 at 22:45