0

PLEASE NOTE that 'this' is not accessible from a static function: React Native : Access Component state inside a static function


I am trying to define a button in the screen's header that, when clicked, will affect rendering, and will be replaced with another icon.

It is an old app, still using react navigation 3.

I didn't know how to do the following things:

  • modify the component's state from a function that is activated when the button is pressed
  • modify the screen parameter from this function

What I managed to implement is the following lame and embarrassing way to do it:

  • When the button is clicked, a static function is executed, which modfies a static variable
  • periodic code is fired in componentDidMount that checks whether the static detailedDisplay variable has been modified. If it has been modified, this code sets a state variable that affects rendering. This periodic code also modifies the screen parameter which changes the icon in the header (because, as I wrote above, I also failed to set the parameter from the static function).

How can this be done in not-so-lame way?

Here is my code:

import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/Octicons';
...
export default class Messages extends Component {
...
var detailedDisplay = false;
...
  static navigationOptions = ({ navigation }) => {
    return {
      headerRight: //navigation.getParam('detailedDisplay', false) ?
        detailedDisplay ?
        <TouchableOpacity onPress={() => this.toggleDisplay(navigation)}>
          <Icon name={"check-circle"} />
        </TouchableOpacity> :
        <TouchableOpacity onPress={() => this.toggleDisplay()}>
          <Icon name={"comment"} />
        </TouchableOpacity>,
    };
  };

  static toggleDisplay(navigation) {
    detailedDisplay = !detailedDisplay;
    // the following statement gave the error "cannot read property 
    //    'setParams' of undefined", so I am setting it below.
    // navigation.setParams({ detailedDisplay });
  }

  constructor(props) {
    super(props);
    this.state = {
      detailedDisplay: false,
    };
  }

  componentDidMount() {
    setInterval(() => {
      if (detailedDisplay !== this.state.detailedDisplay) {
        this.props.navigation.setParams({ detailedDisplay });
        this.setState({ detailedDisplay });
      }
    }, 500);
  }
  ...
  return (
      <View>
        { this.state.detailedDisplay ?
          {this.renderConcise()} :
          {this.renderDetailed()}
        }
      </View>
    );
  }

  renderDetailed() {
    ...
  }

  renderConcise() {
    ...
  }
}

4 Answers 4

0

I think you can try to use your state inside your navigationOption method

static navigationOptions = ({ navigation }) => {
    return {
      headerRight: //navigation.getParam('detailedDisplay', false) ?
        this.state.detailedDisplay ?
        <TouchableOpacity onPress={() => this.toggleDisplay(navigation)}>
          <Icon name={"check-circle"} />
        </TouchableOpacity> :
        <TouchableOpacity onPress={() => this.toggleDisplay()}>
          <Icon name={"comment"} />
        </TouchableOpacity>,
    };
  };

And then change your state in the toggleDisplay method

static toggleDisplay(navigation) {
    this.setState(state=> detailedDisplay:!state.detailedDisplay)
    // the following statement gave the error "cannot read property 
    //    'setParams' of undefined", so I am setting it below.
    // navigation.setParams({ this.state.detailedDisplay });
  }
Sign up to request clarification or add additional context in comments.

1 Comment

0

You can try and keep the param and a state variable in sync. An example POC is here

Here, I have tried to keep the state toggleVariable and the param value in sync but updating them through a common setter, and on Component Mount it will just be the same as the parameter.

Comments

0

You cannot read properties of this because this in JS is dynamic, and when you pass function as callback this is lost. There is 2 solutions that i know:
One of them bind this using bind method:

constructor(props) {
    super(props);
    this.state = {
      detailedDisplay: false,
    };
    this.toggleDisplay = this.toggleDisplay.bind(this);
    this.navigationOptions = this.navigationOptions.bind(this) 
  }

Now you can use non-static methods and you can access react state and methods in them:

navigationOptions({ navigation }) {
     // somewhere in code <button onPress={this.toggleDisplay}><button>
     // this.props this.state this.setState is available here
  };

toggleDisplay(navigation) {
    // this.props this.state this.setState is available here
    // this.props.navigation.setParams() is available too 
  }

Finally, you can change the detailedDisplay state and screen params in toggleDisplay function. So when you'll press button state'll be changed and your commponent'll be re-rendered.



Additional
Second way to do it is to call method in a function:

onPress={() => this.toggleDiaplay()}

Here you must also use non-static methods, and this will work the same way as the first solution with bind.

Comments

-1

Try doing something like this:

UPDATED: removed static from the toggleDisplay and added Class reference in the static function.

import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/Octicons';
...
export default class Messages extends Component {
...
    state = {
        detailedDisplay: false,
    };
...
  static navigationOptions = ({ navigation }) => {
    return {
      headerRight: navigation.getParam('detailedDisplay', false) ?
        <TouchableOpacity onPress={() => Messages.toggleDisplay()}>
          <Icon name={"check-circle"} />
        </TouchableOpacity> :
        <TouchableOpacity onPress={() => Messages.toggleDisplay()}>
          <Icon name={"comment"} />
        </TouchableOpacity>,
    };
  };

  toggleDisplay() {
    this.setState({detailedDisplay: !this.state.detailedDisplay})
    this.props.navigation.setParams({detailedDisplay: true});
  }

  constructor(props) {
    super(props);
  }

  componentDidMount() {
    // setInterval(() => {
    //   if (detailedDisplay !== this.state.detailedDisplay) {
    //     this.props.navigation.setParams({ detailedDisplay });
    //     this.setState({ detailedDisplay });
    //   }
    // }, 500);
  }
  ...
  return (
      <View>
        { this.state.detailedDisplay ?
          {this.renderConcise()} :
          {this.renderDetailed()}
        }
      </View>
    );
  }

  renderDetailed() {
    ...
  }

  renderConcise() {
    ...
  }
}

3 Comments

@Yossi Updated my answer
I appreciate the effort! I am getting "Messages.toggleDisplay is not a function", which is understandable, since toggleDisplay is not defined as a static function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.