0

My app retrieve data from Firebase Realtime database and trying to load it in 'state' but get error 'Cannot read property setState of undefined'.

I tried to add bind in constructor but it doesn't work

import React from 'react';

import { View } from '@vkontakte/vkui';

import * as firebase from "firebase/app";
import {config} from './dbinit' // import firebase config
import "firebase/database";


import Home from './panels/Home';



class App extends React.Component {
    constructor(props) {
        super(props);
        this.setState = this.setState.bind(this);
        this.state = {
            activePanel: 'home',
            db: null,
            loading: true,
        };
        console.log("init");
    }

    componentDidMount = () => {
        let ref = firebase
         .initializeApp(config)
         .database()
         .ref();

        ref.once("value").then(function onSuccess(res) {
            console.log("success", res.val())


            this.setState({db: res.val(), loading: false})
            // ERROR GOES HERE 'Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined'


        });
    }

    go = (e) => {
        this.setState({activePanel: e.currentTarget.dataset.to})
    };


    render() {
        const { loading, db } = this.state;
        return loading ? (
            <div>loading...</div>
        ) : (
            <View activePanel={this.state.activePanel}>
                <div>loaded</div>
            </View>

        );
    }
}

export default App;

I expect correct work of setState but actual have error Cannot read property 'setState' of undefined'

ArtX
  • 3
  • 3
  • Consider reading this: https://www.freecodecamp.org/news/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb/ – Wyck Jun 24 '19 at 17:04
  • 1
    This isn't about losing `this` in a custom Component class method, this is about losing the context due to the callback `function`. A quick workaround is to put `var appThis = this;` before the `ref.once()` call, then use `appThis.setState(...);` inside the callback. –  Jun 24 '19 at 17:21

3 Answers3

2

You lost context here since anon. function used:

    ref.once("value").then(function onSuccess(res) {
        console.log("success", res.val())


        this.setState({db: res.val(), loading: false})
        // ERROR GOES HERE 'Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined'


    });

Use arrow function lilke this:

ref.once("value").then((res) => {
    console.log("success", res.val())


    this.setState({db: res.val(), loading: false})
    // ERROR GOES HERE 'Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined'


});
Viktor
  • 188
  • 7
0

From your use of arrow functions I see your Babel configuration is able to handle the newer (to be released) ES feature of class fields, so it may be worth it to get rid of your constructor altogether.

class App extends React.Component {
    state = {
        activePanel: 'home',
        db: null,
        loading: true,
    };

    componentDidMount = () => {
    ....

This doesn't answer the question, but thought I'd mention it :)

Viktor's answer mentions that your 'this' context got lost from not using an arrow function, which is correct.

rwest88
  • 9
  • 5
0

The reason is that because you are using regular functions which bind the this keyword dynamically based on the callee and not based on lexical scope... since this code is running in strict mode ( because it is in a class ) the this keyword is resolved as undefined.

if you wish to preserve context you either have to bind the function or use arrow functions ( which bind the this keyword based on the lexical scope)

convert this

ref.once("value").then(function onSuccess(res) {
        console.log("success", res.val())


        this.setState({db: res.val(), loading: false})
        // ERROR GOES HERE 'Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined'


});

to this

ref.once("value").then(res => this.setState({db: res.val(), loading: false}));
ehab
  • 7,162
  • 1
  • 25
  • 30