-1

I have JS class (React component) which was created for displaying forecasts:

class WeatherList extends Component {
  convertDate(date) {
    return new Date(date).toLocaleTimeString(['en-US'],
      {
        hour: 'numeric',
        minute: 'numeric'
      }
    );
  }

  renderWeather(data) {
    const temps = data.list.map(weather => weather.main.temp);
    const labels = data.list.map(weather => this.convertDate(weather.dt_txt));
    const chartData = {
      datasets: [{
        data: temps
      }],
      labels: labels,
    }

    return (
      <tr key={data.city.id}>
        <td>
          {data.city.name}
        </td>
        <td>
          <Line data={chartData} width="600" height="250"/>
        </td>
      </tr>
    )
  }

  render() {
    return (
      <table className='table table-hover'>
        <thead>
          <tr>
            <th>
              City
            </th>
            <th>
              Temperature
            </th>
          </tr>
        </thead>
        <tbody>
          {this.props.weather.map(this.renderWeather)}
        </tbody>
      </table>
    )
  }
}

But recently i'v ran into error: this in this.convertDate(weather.dt_txt) is undefined in the renderWeather function (which renders table row with Chart component).

i'm using webpack to compose all my file and also a babel library.

Src
  • 3,905
  • 3
  • 22
  • 50

2 Answers2

2

Change:

    <tbody>
      {this.props.weather.map(this.renderWeather)}
    </tbody>

To:

    <tbody>
      {this.props.weather.map(this.renderWeather.bind(this))}
    </tbody>

That way the method renderWeather will be called with the right this instead of undefined.

Alternatively, you can do the binding by not passing the function reference, but passing an inline function (using arrow syntax so you have access to the same this):

    <tbody>
      {this.props.weather.map(data => this.renderWeather(data))}
    </tbody>

Reference: How does the this keyword work?

trincot
  • 211,288
  • 25
  • 175
  • 211
-1

You need to provide a context to the convertDate method.

There are a few ways to do this:

// approach 1 (my favorite)
// declare method as an ES6 Arrow Function
class WeatherList extends Component {
  convertDate = (date) => {
    // method impl
  }
}

// approach 2
// bind all class methods to instance in constructor
class WeatherList extends Component {
  constructor(props) {
    super(props)
    this.convertDate = this.convertDate.bind(this) // once per method
  }
}

// approach 3
// provide context at call site
return data.list.map(weather => this.convertDate(weather.dt_txt).bind(this));

I'm not actually sure why class methods are not, by default, invoked with the class instance as this, but they are not. You'll typically run into this problem when a class method is attached as an event listener (e.g. in an element's onClick), but you'll also see it when providing a class method to an iterator like Array.map.

As a general rule, always define your ES6 Component methods as arrows. There are zero drawbacks, and the only time you wouldn't need the method to have this refer to the instance would be if the method was a pure function (i.e. it doesn't access props or state); and in those cases, it doesn't hurt.

Tom
  • 4,835
  • 3
  • 35
  • 49
  • I'm not using `context` inside `convertDate` function at all – Src Jan 13 '18 at 20:54
  • I should have been clear: I don't mean React context. A lot of general-purpose JS documentation uses "context" to mean "the referent of `this` within a function or method." Perhaps `thisArg` is clearer? – Tom Jan 13 '18 at 20:59
  • I understood you, but i mean i was not using `this` inside that function. My mistake was that i haven't binded context even earlier, to the function which was calling `convertDate`. – Src Jan 13 '18 at 21:01
  • If both methods had been defined as arrows, the problem would not have occurred. – Tom Jan 13 '18 at 21:05
  • Well, the problem was only with first function, that you didn't even mentioned. Anyway, thanks for commenting. – Src Jan 13 '18 at 21:43