React Native using setState with setParams - javascript

I'm working on a react native app where I'd like to be able to change the state from the static navigationOptions. I was following this: https://github.com/react-navigation/react-navigation/issues/1789
And have implemented one of the solutions as follows:
static navigationOptions = ({navigation}) => {
const { state } = navigation;
const {params = {}} = navigation.state;
navigation.params = {title: "", description: "", points: 0, image: {}, saveImage: true};
return {
headerRight: ( <Button transparent onPress={()=> params.create()}><Icon name="ios-add" style={{color: 'white'}}/></Button>)
};
};
componentDidMount() {
this.props.navigation.setParams({
create: this.openCreateModal
});
}
openCreateModal() {
this.setState({createModalVisible:true});
}
Unfortunately when I call the openCreateModal function by pressing the button, I get the error this.setState is not a function.
I'd appreciate any help with this.

Your openCreateModal() method is not bound. Look further down in that issue and someone points out that mistake.
To fix this, you either need to explicitly bind it so it has access to the correct this context, for example in your constructor:
constructor(props) {
super(props);
this.openCreateModal = this.openCreateModal.bind(this);
}
Or you can convert it to an ES6 arrow function which would auto-bind it to the class's context like so:
openCreateModal = () => {
this.setState({createModalVisible:true});
}

Related

Is there anyway to get rid of binding `this` to `Component` functions in react?

This is what I do when I want to make this of my class functions bind to the class (component) instance. Is there any easier/more proper way to do this in React?
class App extends Component {
state = {
currentSection: 1,
message :{text:''}
};
constructor(props) {
super(props);
this.prevSection = this.prevSection.bind(this);
this.nextSection = this.nextSection.bind(this);
this.mobileChanged = this.mobileChanged.bind(this);
}
}
If I recall correctly, if you change:
function nextSection() {...}
to
const nextSection = () => {...}
After this change, you can remove this and the bind
Please let me know if your component will remain as functional like it was before. I'm not sure if it this will change the behaviour.
You could use arrow function instead of class method
With arrow function, there will be no this context so you won't have to bind it
class App extends Component {
state = {
currentSection: 1,
message: { text: '' },
};
prevSection = () => {}
nextSection = () => {}
mobileChanged = () => {}
}
Live example:

set multiple states, and push to state of array in one onClick function

I'm running into a recurring issue in my code where I want to grab multiple pieces of data from a component to set as states, and push those into an array which is having its own state updated. The way I am doing it currently isn't working and I think it's because I do not understand the order of the way things happen in js and react.
Here's an example of something I'm doing that doesn't work: jsfiddle here or code below.
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
}
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
setCategoryStates = (categoryTitle, categorySubtitle) => {
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
render() {
return (
<CategoryComponent
setCategoryStates={this.setCategoryStates}
categoryTitle={'Category Title Text'}
categorySubtitle={'Category Subtitle Text'}
/>
);
}
}
class CategoryComponent extends Component {
render() {
var categoryTitle = this.props.categoryTitle;
var categorySubtitle = this.props.categorySubtitle;
return (
<div onClick={() => (this.props.setCategoryStates(
categoryTitle,
categorySubtitle,
))}
>
<h1>{categoryTitle}</h1>
<h2>{categorySubtitle}</h2>
</div>
);
}
}
I can see in the console that I am grabbing the categoryTitle and categorySubtitle that I want, but they get pushed as null into this.state.categoryArray. Is this a scenario where I need to be using promises? Taking another approach?
This occurs because setState is asynchronous (https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly).
Here's the problem
//State has categoryTitle as null and categorySubtitle as null.
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
//This gets the correct values in the parameters
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
//This method is using the state, which as can be seen from the constructor is null and hence you're pushing null into your array.
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
Solution to your problem: pass callback to setState
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
}, () => {
/*
Add state to the array
This callback will be called once the async state update has succeeded
So accessing state in this variable will be correct.
*/
this.pushToCategoryArray()
})
}
and change
pushToCategoryArray = () => {
//You don't need state, you can simply make these regular JavaScript variables
this.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
I think React doesn't re-render because of the pushToCategoryArray that directly change state. Need to assign new array in this.setState function.
// this.state.categoryArray.push({...})
const prevCategoryArray = this.state.categoryArray
this.setState({
categoryArray: [ newObject, ...prevCategoryArray],
)}

react without constructor declaring state in class's body

I saw below code somewhere and I'm curious. It looks cleaned but unusual to me. Why state = {} is declared without an constructor?
and load declared without a function keyword? As I know for there are ways to write a function
function async load() {}
or
const async load = ()=>{}
And what ...args does? is it spread arguments?
import View from './View';
import loadData from './loadData';
export default class extends Component {
state = {};
load = this.load.bind(this);
async load(...args) {
try {
this.setState({ loading: true, error: false });
const data = await loadData(...args);
this.setState({ loading: false, data });
} catch (ex) {
this.setState({ loading: false, error: true });
}
}
render() {
return (
<View {...this.props} {...this.state} onLoad={this.load} />
);
}
}
The state = {} declaration is a class property, which is currently not part of the JavaScript language. Certain utilities such as Babel will compile this into legal JavaScript code.
However, the lack of a function keyword within classes, as well as the ... operator are part of ECMAScript 6, which has been officially implemented into the language (though some browsers do not recognize it yet).
Class Definition
Spread Operator
Yes, you can initialize state without a constructor for a React class component:
class Counter extends Component {
state = { value: 0 };
handleIncrement = () => {
this.setState(prevState => ({
value: prevState.value + 1
}));
};
handleDecrement = () => {
this.setState(prevState => ({
value: prevState.value - 1
}));
};
render() {
return (
<div>
{this.state.value}
<button onClick={this.handleIncrement}>+</button>
<button onClick={this.handleDecrement}>-</button>
</div>
)
}
}
It uses class field declarations which are not part of the language yet but enabled with Babel. You can checkout a sample application over here.
You can also use state in React function components (without a constructor), but using higher-order components or render prop components. You can find out more about it here.

Custom function inside header button react native

I am trying to call a custom function inside a custom button in my react navigation header. I've looked around several ways to do this, and the best result I've found is making the function static, that is:
export class MyClass extends React.Component{
static navigationOptions = ({navigation}) => ({
headerRight: (<Button title='press me' onPress={()=> MyClass.SomeFunction() } ></Button>)
});
static SomeFunction(){
/*Some code here*/
}
/*Some extra code here*/
}
My issue is, however, that I need to access some state properties within SomeFunction() and, as you may know, you cannot acces this within a static function.
Is there any way I can access the state of a component within a static, or is there a better way to implement a custom function within a button in the header????
As an alternative solution you might set the navigator state to set and get values.
If you use an AppWithNavigation state parent as a root of your navigation structure you should be pass a navigation prop to children elements like below:
render() {
const { dispatch, nav, } = this.props
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: dispatch,
state: nav,
})}
/>
)
}
If so, just set your values by using the following line:
this.props.navigation.setParams({someValue: 'Value'})
Then get your set value whenever you want like the below:
this.props.navigation.state.someValue
Or
const { someValue } = this.props.navigation.state
But keep in mind, when first rendering the component state may be null or undefined. So you need to check its existing before try to get:
if (!this.props.navigation.state) {
return null
}
const someValue = this.navigation.state.someValue
if (someValue) {
/* you can use your someValue here! */
}
Note to that every route has its own state object. When your screen is changed, the state of your this.props.navigation.state object is changed. If you need a global solution, I think, you might use Redux.
after some time messing around with the code, I found a solution that better fits my needs. I post it below in case it helps anyone. Thank you all for your contributions :D
export class MyClass extends React.Component{
static navigationOption = ({navigation}) => ({
headerRight: (<Button title='Press Me!' onPress={() => MyClass.SomeFunc() })
})
//The function
static SomeFun(){
alert(MyClass.SomeState.abc)
}
//Static functioning as state
static SomeState = {
abc: 'def'
}
}
Here is an approach straight from their documentation https://reactnavigation.org/docs/en/header-buttons.html
There is also an npm module to make this a bit easier. https://www.npmjs.com/package/react-navigation-underscore
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
const params = navigation.state.params || {};
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button onPress={params.increaseCount} title="+1" color="#fff" />
),
};
};
componentWillMount() {
this.props.navigation.setParams({ increaseCount: this._increaseCount });
}
state = {
count: 0,
};
_increaseCount = () => {
this.setState({ count: this.state.count + 1 });
};
/* later in the render function we display the count */
}

Why this.state is undefined in react native?

I am a complete newbie in react native, react.js, and javascript. I am Android developer so would like to give RN a try.
Basically, the difference is in onPress;
This code shows 'undefined' when toggle() runs:
class LoaderBtn extends Component {
constructor(props) {
super(props);
this.state = { loading: false };
}
toggle() {
console.log(this.state);
// let state = this.state.loading;
console.log("Clicked!")
// this.setState({ loading: !state })
}
render() {
return (
<Button style={{ backgroundColor: '#468938' }} onPress={this.toggle}>
<Text>{this.props.text}</Text>
</Button>
);
}
}
but this code works:
class LoaderBtn extends Component {
constructor(props) {
super(props);
this.state = { loading: false };
}
toggle() {
console.log(this.state);
// let state = this.state.loading;
console.log("Clicked!")
// this.setState({ loading: !state })
}
render() {
return (
<Button style={{ backgroundColor: '#468938' }} onPress={() => {this.toggle()}}>
<Text>{this.props.text}</Text>
</Button>
);
}
}
Can you explain me the difference, please?
In Java / Kotlin we have method references, basically it passes the function if signatures are the same, like onPress = () => {} and toggle = () => {}
But in JS it doesn't work :(
The issue is that in the first example toggle() is not bound to the correct this.
You can either bind it in the constructor:
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
...
Or use an instance function (OK under some circumstances):
toggle = () => {
...
}
This approach requires build changes via stage-2 or transform-class-properties.
The caveat with instance property functions is that there's a function created per-component. This is okay if there aren't many of them on the page, but it's something to keep in mind. Some mocking libraries also don't deal with arrow functions particularly well (i.e., arrow functions aren't on the prototype, but on the instance).
This is basic JS; this article regarding React Binding Patterns may help.
I think what is happening is a matter of scope. When you use onPress={this.toggle} this is not what you are expecting in your toggle function. However, arrow functions exhibit different behavior and automatically bind to this. You can also use onPress={this.toggle.bind(this)}.
Further reading -
ES6 Arrow Functions
.bind()
What is happening in this first example is that you have lost scope of "this". Generally what I do is to define all my functions in the constructor like so:
constructor(props) {
super(props);
this.state = { loading: false };
this.toggle = this.toggle.bind(this);
}
In the second example, you are using ES6 syntax which will automatically bind this (which is why this works).
Then inside of you onPress function, you need to call the function that you built. So it would look something like this,
onPress={this.toggle}

Categories

Resources