GregH
GregH

Reputation: 5461

React _this3.deleteArticle is not a function

After reviewing a couple other somewhat similar posts, I'm still not sure why I am receiving the following error:

TypeError: _this3.deleteArticle is not a function

From what I gather, the issue is that deleteArticle is not included in state. However, I thought that the .bind(this) should bind it to the state thus resolving this issue. Why is this not working and what can I do to correct it?

The deleteArticle function is being called in the render method via:

<td onClick={() => this.deleteArticle(article.Id).bind(this) }>
    <Glyphicon glyph='trash' />
</td>

and looks as follows:

  deleteArticle(id) {
    fetch('https://localhost:44360/api/articles/' + id, {  
        method: 'DELETE'
    }).then((response) => response.json())  
        .then((responseJson) => {  
            var deletedId = responseJson.id;

     var index = this.state.articles.findIndex(function(o){
        return o.id === deletedId;
     })  
      if (index !== -1){
        this.state.articles.splice(index, 1);
      } 
    })  
  }

And the full component for the sake of being complete:

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Glyphicon } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

export class ArticlesIndex extends Component {
  displayName = ArticlesIndex.name

  constructor(props) {
    super(props);
    this.state = { articles: [], loading: true };

    fetch('https://localhost:44360/api/Articles/')
      .then(response => response.json())
      .then(data => {
        this.setState({ articles: data, loading: false });
      });
  }

  static renderArticlesTable(articles) {
    return (
      <table className='table'>
        <thead>
          <tr>
            <th>Id</th>
            <th>Title</th>
            <th>Description</th>
            <th>Edit</th>
            <th>Delete</th>
          </tr>
        </thead>
        <tbody>
          {articles.map(article =>
            <tr key={article.id}>
              <td>{article.id}</td>
              <td>{article.title}</td>
              <td dangerouslySetInnerHTML={{ __html: article.description }}></td>
              <td>
                <LinkContainer to={'/articles/edit/' + article.id}>
                    <Glyphicon glyph='edit' />
                </LinkContainer>
            </td>
            <td onClick={() => this.deleteArticle(article.Id).bind(this) }>
              <Glyphicon glyph='trash' />
            </td>
            </tr>
          )}
        </tbody>
      </table>
    );
  }

  render() {
    let contents = this.state.loading
      ? <p><em>Loading...</em></p>
      : ArticlesIndex.renderArticlesTable(this.state.articles);

    return (
      <div>
        <h1>Articles</h1>
        {contents}
      </div>
    );
  }

  deleteArticle(id) {
    fetch('https://localhost:44360/api/articles/' + id, {  
        method: 'DELETE'
    }).then((response) => response.json())  
        .then((responseJson) => {  
            var deletedId = responseJson.id;

     var index = this.state.articles.findIndex(function(o){
        return o.id === deletedId;
     })  
      if (index !== -1){
        this.state.articles.splice(index, 1);
      } 
    })  
  }
}

Upvotes: 2

Views: 593

Answers (1)

Treycos
Treycos

Reputation: 7492

A static method is not bound to an instance of a class and will have a different context from a typical component.

The consequence of this is that other functions/variables in your class will not be accessible via the this keyword, as their context is different.

Changing your function declaration :

static renderArticlesTable(articles)

To the following :

renderArticlesTable = articles => 

May solve your problem, as I don't see any reason for your function to be static. Also, making it an arrow function will automatically bind it to your class's context.

Your calls :

 ArticlesIndex.renderArticlesTable(this.state.articles)

Will now be :

this.renderArticlesTable(this.state.articles)

I would also recommend changing your deleteArticle function to be an arrow function that does not need to be bound :

deleteArticle = id => {

Also, do not set promise to trigger a setState in your constructor. If your fetch request sends out your data too early, you will set the state of an unmounted component. Use componentDidMount when fetching data :

constructor(props) {
    super(props);
    this.state = { articles: [], loading: true };
}

componentDidMount(){
    fetch('https://localhost:44360/api/Articles/')
        .then(response => response.json())
        .then(data => {
            this.setState({ articles: data, loading: false });
        });
}

While i'm at it, you can also put ternary conditions directly into your JSX :

render() {
    const { loading, articles } = this.state

    return (
        <div>
            <h1>Articles</h1>
            {loading ? 
                <p><em>Loading...</em></p> 
                : 
                this.renderArticlesTable(articles)
            }
        </div>
    );
}

I also noticed that you were attempting to modify your state directly in your deleteArticle function. You cannot modify your state without using setState.

To remove an item with a specific value, you can use filter to take the item with the corresponding id out of your previous state :

deleteArticle = id => {
    fetch('https://localhost:44360/api/articles/' + id, {
        method: 'DELETE'
    }).then(response => response.json())
        .then(({ id }) => { //Deconstructs you responseJson
            this.sestState(prev => ({
                articles: prev.articles.filter(article => article.id !== id)
            }))
        })
}

Upvotes: 3

Related Questions