-2

In my example, I am fetching a Product-Object from an API. The Product has ImageIds, referring to Images, which I also fetch from an API after the product is fetched.

class ProductDetails extends Component {
    state = {
        productId: this.props.match.params.id,
        product: null,
        imagePaths: [],
        currentImagePath: null,
        loading: true
    }
    constructor(props) {
        super(props);
        this.request = getDefaultRequest();
    }
    componentDidMount = () => {
        this.fetchProduct();
    }
    // Api calls
    fetchProduct = async () => {
        const response = await this.request.get(`http://localhost:8080/product/Id=${this.state.productId}`);
        let { product } = response.data;
        this.setState({
            product
        }, () => {
            this.fetchImages().then((images) => {
                this.setImagePaths(images);
            });
        });
    }

    fetchImages = async () => {
        let { product } = this.state;
        let images = [];
        for (let i = 0; i < product.ProductImageIds.length; i++) {
            let currentProducImageId = product.ProductImageIds[i];
            let response = await this.request.get(`http://localhost:8080/productImage/Id=${currentProducImageId}`);
            images.push(response.data.productImage);
        }
        return Promise.resolve(images);
    }
    setImagePaths = (images) => {
        let imagePaths = images.map(image => {
            return image.absolutePath.split("public")[1].replaceAll("\\", "/");
        });
        this.setState({
            imagePaths,
            currentImagePath: imagePaths[0],
            loading: false
        });
    }

    render() {
      let { product, loading } = this.state;
        if (!loading) {
            return (
                <div>
                    <h2 className="t-center">{product.name}</h2>
                    <div className="wrap-product-details">
                        <ProductQuickWatch
                            imagePaths={this.state.imagePaths}
                            currentImagePath={this.state.currentImagePath}
                            productName={this.state.product.name}
                            productId={this.state.product._id}
                            orientation="vertical">

                        </ProductQuickWatch>
                    </div>
                </div>
            );
        }
        return (
            <LoadingCircle
                usePortal={true}
                loading={true} />
        )
      }

But now I get "TypeError: Cannot read property 'state' of undefined"

I debugged this and realized that everything works fine until setting state of loading to false (in setImagePaths()).

If I omit setting loading to false, everything works fine. But I don't know why. And loading state is important if you look at render().

I researched about this and a possible solution is to bind the functions in the constructor. But usually if you use arrow-syntax "this" isn't a problem. And I also tried it and it didn't work :/.

The error:

TypeError: Cannot read property 'state' of undefined
new i
C:/Users/David/Documents/GitHub/React/ShoppingTemplate/src/Store/WithStore.jsx:22
  19 |   children: null,
  20 | };
  21 | 
> 22 | constructor(props, context) {
     | ^  23 |   super(props, context);
  24 |   this.state = mapStateToProps({ ...context.state });
  25 |   this.updateStateProps = this.updateStateProps.bind(this);
▶ 19 stack frames were collapsed.
ProductDetails.setImagePaths
C:/Users/David/Documents/GitHub/React/ShoppingTemplate/app/src/Components/Category/ProductDetails.jsx:47
  44 | let imagePaths = images.map(image => {
  45 |     return image.absolutePath.split("public")[1].replaceAll("\\", "/");
  46 | });
> 47 | this.setState({
     | ^  48 |     imagePaths,
  49 |     currentImagePath: imagePaths[0],
  50 |     loading: false
View compiled
(anonymous function)
C:/Users/David/Documents/GitHub/React/ShoppingTemplate/app/src/Components/Category/ProductDetails.jsx:28
  25 |         product
  26 |     }, () => {
  27 |         this.fetchImages().then((images) => {
> 28 |             this.setImagePaths(images);
     | ^  29 |         });
  30 |     });
  31 | }
Dyvd
  • 57
  • 6
  • Welcome to Stack Overflow! Please take the [tour] (you get a badge!), have a look around, and read through the [help], in particular [*How do I ask a good question?*](/help/how-to-ask) I also recommend Jon Skeet's [Writing the Perfect Question](https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/). What **statement** are you getting that error on? The browser will have told you exactly which `xyz.state` reference was the problematic one. Please pass that information on to *us*, too. :-) – T.J. Crowder Apr 24 '21 at 08:17
  • 2
    I don't see any `this.state` calls in `setImagePaths`. Where is the error saying the issue is? Can you provide the actual error and stacktrace in your question? – Drew Reese Apr 24 '21 at 08:17
  • Everything is an arrow function. So, `this.state` should not throw an error anywhere – adiga Apr 24 '21 at 08:17
  • Please update your question with a [mcve] demonstrating the problem, ideally a **runnable** one using Stack Snippets (the `[<>]` toolbar button). Stack Snippets support React, including JSX; [here's how to do one](http://meta.stackoverflow.com/questions/338537/). – T.J. Crowder Apr 24 '21 at 08:18
  • if you replace `setImagePaths = (images) => {` with `setImagePaths(images) {`, does it help? – Michael Rovinsky Apr 24 '21 at 08:18
  • @MichaelRovinsky - Why would that help for the problem the OP describes? It would make `setImagePaths` a method, meaning that `this` would vary depending on how it's called. The way the OP has it, `setImagePaths` is an instance-specific arrow function closing over `this` assigned to a property, so `this` will always be the component instance. (I don't recommend this pattern, but that's how it behaves.) – T.J. Crowder Apr 24 '21 at 08:20
  • I think you should set your State in the Constructor. or atleast place the constructor above the State Object – Eric. M Apr 24 '21 at 08:20
  • @Eric.M - Doesn't matter. Public property definitions always run as part of the constructor (just after `super()`, if any), regardless of where they appear in the `class` construct. (I do find it odd that the OP uses class property syntax for everything except `request`. :-) If they also used it for `request`, they could leave the constructor out entirely.) – T.J. Crowder Apr 24 '21 at 08:22
  • 1
    Issue is in a `WithStore` HOC, can you share that? – Drew Reese Apr 24 '21 at 08:28
  • 1
    @MichaelRovinsky that doesn't help, I'm using arrow-syntax to prevent any errors regarding "this". – Dyvd Apr 24 '21 at 08:34
  • Try `this.setImagePaths(images).bind(this)` – Michael Rovinsky Apr 24 '21 at 08:36
  • 1
    @MichaelRovinsky didn't work – Dyvd Apr 24 '21 at 08:40
  • @DrewReese, what exactly should I share about that? It isn't my component. – Dyvd Apr 24 '21 at 08:42
  • 1
    @MichaelRovinsky `setImagePaths` is an arrow function. There is no point in `bind(this)`. Moreover, `this.setImagePaths(images).bind(this)` executes the `setImagePaths` method and binds a `this` to whatever is returned by the method. In this case, it returns undefined. – adiga Apr 24 '21 at 08:50
  • `"src/Store/WithStore.jsx"` isn't your component? At a minimum you can view the source and see exactly what is accessing a `state` property and trace it back. – Drew Reese Apr 24 '21 at 08:55
  • @DrewReese no, it isn't my component. I also wondered why the path is refereing to my src-folder. – Dyvd Apr 24 '21 at 09:01

2 Answers2

0

Interesting discussion ;) I will put this as an answer, although it does not really answer. But it is too long and comments to cluttered. Two point:

  1. This part
    new i
    C:/Users/David/Documents/GitHub/React/ShoppingTemplate/src/Store/WithStore.jsx:22

is interesting, because it appears in the error context but not the code as far as I can see. So where is this coming from? Might be some missing information in your question.

  1. That is a rather complex chain of this and =>. I do not see the error but debugging by "eye" is just bad. Add a ton of console.logs(1),console.logs(2) and so on to see, where EXACTLY the code breaks. When you have the line, output this and this.state. There are only two options what could be wrong: wrong this or this.state is just not defined. With that information, fixing the error is relatively simple in most cases.
  • 1
    I tried it in a similar way like you said. I logged "this" to the console and looked at the state, but there was no problem. In the meanwhile I solved the problem. I found out that I was using the library a bit wrong and that caused the issue. The issue was in the ProductQuickWatch component. Somehow, I used the component before and it worked fine. Idk why it wasn't working in this case. – Dyvd Apr 24 '21 at 13:50
  • Glad to hear. Extreme logging helps in almost all cases in my experience. I wish someone had told me that when I started programming... –  Apr 24 '21 at 15:50
  • Yeah, but unfortunately, this didn't solve the problem in this case. – Dyvd Apr 24 '21 at 17:10
  • Although IMO console debugging is a legitimate method, you should still familiarize yourself with setting breakpoints and being able to methodically step through your code while it's executing via an attached debugger. Chrome (and likely most browsers with dev tools) will make the breakpoint and debugger trivial, the learning part is where to set the breakpoints and understanding the difference between stepping into vs over function calls as you step through the code. – Drew Reese Apr 24 '21 at 17:51
  • Well that is trivial, F5 vs F6... I used to debug by stepping through a lot in Java but have moved away from it. The need somewhat vanished and logging, to me, is more powerful and especially with large datasets stepping is just not feasible. Logging always is. So for JS I personally never bothered to set it up. If that is what you prefer, though, sure by all means. –  Apr 24 '21 at 19:57
0

I found out, that it has to do something with the library I am using in the ProductQuickWatch component calling "pure-react-carousel". Here is a similar error. When I removed it, I could set the state of loading to false. I have to research a bit more about that error. Thx for the answers though.

Dyvd
  • 57
  • 6