How to refer 'this' keyword in React Stateless Component

user12133234 picture user12133234 · Sep 28, 2019 · Viewed 7.7k times · Source

Am trying to create a React Stateless Components for input range day picker using library day-picker.

If I try to convert the Stateful Component to Stateless, the this keyword is not accessible, since Stateless components doesn't have the scope for this.

Am very new to React and Hooks and gave my best to resolve, but somehow failed to resolve the problem, this is what I was trying.

Problem - Day picker range input is not working as expected. Always the calendar show start with current month. But somehow it is starting from last year.

Actual Code

import React, { useState } from 'react';
import DayPickerInput from 'react-day-picker/DayPickerInput';
import moment from 'moment';
import { formatDate, parseDate } from 'react-day-picker/moment';

import 'react-day-picker/lib/style.css';

const DayPickerRange = () => {
  const [days, setDays] = useState({
    from: new Date(),
    to: new Date(today.getTime() + 24 * 60 * 60 * 1000)
  });

  function showFromMonth() {
    const { from, to } = days;
    if (!from) {
      return;
    }
    if (moment(to).diff(moment(from), 'months') < 2) {
      // this.to.getDayPicker().showMonth(from);
      to.getDayPicker().showMonth(from);
    }
  }

  const handleFromChange = from => {
    // Change the from date and focus the "to" input field
    setDays({ from, to }, showFromMonth);
  };

  const handleToChange = to => {
    setDays({ from, to });
  };

  const { from, to } = days;

  const modifiers = {
    start: from,
    end: to
  };

  return (
    <div className="InputFromTo">
      <DayPickerInput
        value={from}
        placeholder="From"
        format="LL"
        formatDate={formatDate}
        parseDate={parseDate}
        dayPickerProps={{
          utc: true,
          selectedDays: [from, { from, to }],
          disabledDays: [{ before: new Date() }],
          toMonth: to,
          month: to,
          modifiers,
          numberOfMonths: 12,
          onDayClick: () => to.getInput().focus()
        }}
        onDayChange={handleFromChange}
      />
      <span className="InputFromTo-to">
        <DayPickerInput
          ref={el => {
            days.to = el;
          }}
          value={to}
          placeholder="To"
          format="LL"
          formatDate={formatDate}
          parseDate={parseDate}
          dayPickerProps={{
            selectedDays: [from, { from, to }],
            disabledDays: [{ before: new Date() }],
            modifiers,
            month: from,
            fromMonth: from,
            numberOfMonths: 12,
            utc: true
          }}
          onDayChange={handleToChange}
        />
      </span>
    </div>
  );
};

export default DayPickerRange;

Answer

ravibagul91 picture ravibagul91 · Sep 28, 2019

There are multiple things, you need to figure out / take into consideration while converting class based component to functional component.

Don't initialize your state with new Date(),

const [days, setDays] = useState({
  from: new Date(),
  to: new Date(today.getTime() + 24 * 60 * 60 * 1000)
});

Date format of new Date() and date format of your DayPickerInput is not same. So you need to keep it as undefined / convert the new Date() to format your DayPickerInput understands.

const [days, setDays] = useState({
  from: undefined,
  to: undefined
});

Another thing is, setState in class based component and functional component work a bit differently. setState in functional component don't have callback.

This setState is a bit wrong,

const handleFromChange = from => {
  // Change the from date and focus the "to" input field
  setDays({ from, to }, showFromMonth);
};

const handleToChange = to => {
  setDays({ from, to });
};

Here showFromMonth as callback will not work. You need a separate useEffect hook which will listen to state change and run side-effect / callback accordingly,

const handleFromChange = from => {
  // Change the from date and focus the "to" input field
  //This is functional setState which will only update `from` value
  setDays(days => ({
     ...days,
     from
  }));
};

const handleToChange = to => {
  //This is functional setState which will only update `to` value
  setDays(days => ({
    ...days,
    to
  }));
};

//This is useEffect hook which will run only when `to` value changes
useEffect(()=>{
  showFromMonth();
},[days.to, showFromMonth])

You have provided ref to your second date picker,

ref={el => {
    days.to = el;
}}

You should create a ref variable separately and not directly use state as ref.

let toInput = React.createRef();


ref={el => {
   toInput = el;
}}

I have made some modifications to your code according to actual code you provided in question.

Demo