Search code examples
reactjs

Why is React Calendar not working inside parent component?


I am having issue with the MyCalendar component below which used the react-calendar. I have logged selectedValue for debugging purposes.

If I render the MyCalendar component on its own, then its internal state selectedValue tracks the selected date in the Calendar appropriately, and the Calendar highlights the selected date.

However, if I use the MyCalendar component within DatePicker, then the value inside the DatePicker box is correct, however the state within the child MyCalendar component is always null for some reason, and thus the selected date is not highlighted within the Calendar. It seems that the selectedValue state in MyCalendar is reinitialised to null when a new date is picked in DatePicker.

Would anyone be able to help me understand what the problem is here? I would like MyCalendar to have its own state rather than taking Calendar prop value from a parent component.

const MyCalendar: React.FC<PropsCalendar> = ({
  onDateSelect,
  selectRange = false,
}) => {
  const [selectedValue, setSelectedValue] = useState<Value>(null);
  console.log(selectedValue)

  const handleDateChange = (value: Value) => {
    setSelectedValue(value);

    if (onDateSelect) {
      onDateSelect(value);
    }
  };

  return (
    <Calendar
      onChange={handleDateChange}
      value={selectedValue}
      calendarType="gregory"
      selectRange={selectRange}
    />
  );
};


export const DatePicker: React.FC<PropsDatePicker> = ({
  selectRange = false,
}) => {
  const [isSelectingDates, setIsSelectingDates] = useState(false);
  const [value, setValue] = useState<Value>(null);

  const handleChange = (value: Value) => {
    setValue(value);

    if (Array.isArray(value)) {
      // selecting a date range
      if (value[0] && value[1]) setIsSelectingDates(false);
    } else {
      setIsSelectingDates(false);
    }
  };

  return (
    <>
      <div
        onClick={(event) =>setIsSelectingDates(!isSelectingDates)}}
      >{value}</div>

      {isSelectingDates && (
        <MyCalendar
          onDateSelect={handleChange}
          selectRange={selectRange}
        />
      )}
    </>
  );
};

Solution

  • your MyCalendar component is being conditionally rendered. so when it stops being rendered, it loses its state. you may need the state temporarily in the calendar if selecting a date range, but after the selection is made, only the parent component should hold the state.

    my solution was to give the MyCalendar component an initial state passed in from the parent and call the "onDateSelect" only when the user has completed the selection. if a range, when the value is a valid pair of dates. if not, onChange.

    this way, the final value lives in the parent component, which is always rendered and where state can remain.

    const MyDatePicker = ({ selectRange = false }) => {
    const [isSelectingDates, setIsSelectingDates] = useState(false);
    const [value, setValue] = useState(null);
    
    const handleChange = (val) => {
        setValue(val);
        setIsSelectingDates(false);
    };
    
    useEffect(() => { console.log('datepicker val: ', value); }, [value]);
    
    return (
        <>
            <div onClick={() => { setIsSelectingDates(!isSelectingDates); }}>
                {value ? 'value selected' : 'No Date Selected'}
            </div>
            {isSelectingDates &&
            <MyCalendar
                onDateSelect={handleChange}
                selectRange={selectRange}
                initialValue={value}
            />}
        </>);
    };
    
    const MyCalendar = ({ onDateSelect, initialValue, selectRange = false }) => {
        const [selectedValue, setSelectedValue] = useState(initialValue);
    
        const handleChange = (val) => {
        setSelectedValue(val);
        if (Array.isArray(val)) {
            if (val[0] && val[1]) { onDateSelect(val); }
        } else {
            onDateSelect(val);
        }
    };
    
    useEffect(() => { console.log('calendar: ', selectedValue); }, [selectedValue]);
    
    return (
        <Calendar
            onChange={handleChange}
            value={selectedValue}
            calendarType='gregory'
            selectRange={selectRange}
        />);
    };