on clicking the text, i get an error saying "undefined is not an object (evaluating '_this2.categoryClicked.bind')"
I think the error is "onPress={()=>this.categoryClicked.bind(this)}" there must be a different way to call the categoryClicked function when the button is clicked. What is wrong in my code ?
class CategoriesView extends React.Component {
constructor(props){
super(props)
}
categoryClicked(){
this.props.categoryPressed(this.props.Category);
}
renderSubCategory(){
return(
this.props.Category.sub_category.map(function(subCategory, i){
return(
<View style={styles.abcd}>
<TouchableHighlight onPress={()=>this.categoryClicked.bind(this)}>
<Text>{subCategory.title}</Text>
</TouchableHighlight>
</View>
)
})
)
}
render(){
return(
<View style={{flex:1}}>
<View style={styles.avf}>
<Text>{this.props.Category.heading}</Text>
</View>
<View style={styles.ddd}>
{this.renderSubCategory()}
</View>
</View>
)
}
}
I believe what you want to do is onPress={this.categoryClicked.bind(this)} instead of an arrow function. .bind(this) returns a function with the context correctly binded to this, therefore, it does not get invoked.
Also, I suggest putting the binding in constructor, as you don't want the binding to happen every time the component re-renders.
e.g.
constructor(props) {
super(props);
this.categoryClicked = this.categoryClicked.bind(this);
}
Then just use onPress={this.categoryClicked}
If you want to pass down sub-category, you can do
constructor(props) {
super(props);
this.subcategoryClicked = props.Category.sub_categories.map(sub_category => this.categoryClicked.bind(this, sub_category));
}
then use like this in render:
this.props.Category.sub_category.map(function(subCategory, i) {
<View style={styles.abcd}>
<TouchableHighlight onPress={this.subcategoryClicked[i]}>
<Text>{subCategory.title}</Text>
</TouchableHighlight>
</View>
P.S, I am not sure if this is a good pattern to follow. Stick to this.categoryClicked(bind, subcategory) if you are not comfortable with doing this. This is one of those things that I am not sure if the optimization is worth it.
this in onPress={()=>this.categoryClicked.bind(this)}> points to sub_category.map function. It should instead point to the class. Can be done this way instead
return (
this.props.Category.sub_category.map((subCategory, i) => { // <--- this way context is preserved
// other lines
onPress={()=>this.categoryClicked.bind(this, subCategory)}>
// other lines
})
);
in categoryClicked method should be accessible
categoryClicked(category){
this.props.categoryPressed(category);
}
Related
I am trying to set up a React Native ref like here, only in a class component:
https://snack.expo.io/PrrDmZ4pk
Here's my code:
class DetailBody extends Component {
constructor() {
super();
this.myRefs = React.createRef([]);
}
clickText(index) {
this.myRefs.current[index].setNativeProps({ style: { backgroundColor: '#FF0000' } });
}
render() {
if (this.props.article.json.results.length === 0) {
return <Loading />;
}
return (
<View >
<View>
<View ref={this.props.highlight} nativeID="some-id" >
{this.props.article.json.results.map((content, index) => (
<TouchableOpacity onPress={() => this.clickText(index)}>
<View key={index} ref={el => this.myRefs.current[index] = el}>{content}</View>
</TouchableOpacity>
</View>
</View>
</View>
This should theoretically let me add a background colors when my ref is clicked, much like the snack I linked to above.
However what I actually see is this:
This seems to be related to .current inside my ref being null, despite passing a default value.
How do I fix this error?
If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element.
Haven't really used it this way but I think you might just need to do this in the constructor:
this.myRefs = React.createRef();
this.myRefs.current = [];
In my app, I have defined a default class A in module xyz.js that renders a page on my navigation stack. Depending on one of class A's state variables, the views that are rendered differ. For example, if the app is placed in an "edit mode", then an editing view is rendered in addition to the other standard views rendered when the app is not in the "edit mode". I can't figure out how to change that state variable from a different module abc.js and cause the views associated with the instantiated class A to re-render. In my module abc.js, I create the navigation stack and I have an onPress handler for a touchableHighlight button to place the app in "edit mode". In that handler, I attempt to call a function "Edit()" in class A. But the function does not seem to get invoked. It may have something to do with binding, but that concept is not something I fully understand.
Here is what I have:
Module abc.js:
import XYZ from './xyz';
import {Edit} from './xyz';
import { pencilEditButton } from './Images';
const App = createStackNavigator(
{
Home: {
screen: My App,
navigationOptions: ({ navigation }) => ({
title: 'myApp',
headerRight: (
<View>
<TouchableHighlight
onPress={() => Edit()}
underlayColor="gray">
<View>
<Image source={pencilEditButton} style={styles.navigationButtonImage} />
</View>
</TouchableHighlight>
</View>
),
}),
},
}
);
export default createAppContainer(App);
Module xyz.js:
export default class XYZ extends React.Component {
constructor(props) {
super(props);
this.state = {
editMode: false,
};
};
// Create a method to handle the press of the edit button on the navigation bar
Edit() {
console.log("editMode: ", editMode);
this.setstate({ editMode: true });
console.log("editMode: ", editMode);
alert('User wants to edit a mix!');
};
render() {
return (
<View style={styles.container}>
{ this.state.editMode === true ?
<TouchableHighlight
onPress={this._onXPressed}
underlayColor="white">
<View style={[styles.flowRight, styles.controlButton]}>
<Text style={styles.buttonText}>{'Edit Mode'}</Text>
</View>
</TouchableHighlight>
:
<TouchableHighlight
onPress={this._onYPressed}
underlayColor="white">
<View style={[styles.flowRight, styles.controlButton]}>
<Text style={styles.buttonText}>{'Non Edit Mode'}</Text>
</View>
</TouchableHighlight>
}
</View>
);
}
}
So as you can see, there is a function called "Edit()" after the constructor in class XYZ of module xyz.js. This function is called from module abc.js when the edit button is pressed. But when the edit button is pressed, the state is not updated, the alert view is not displayed, and the views are not re-rendered. How do I correctly call Edit() so that the state variable "editMode" is updated and the views are re-rendered?
If you want to follow the pattern you are using, it needs to be handles inside your 'My App' component which gets render in stack navigator. You have to use refs
Go through the following code example to find out how to call Edit function.
import XYZ from './xyz';
export default class MyApp extends React.Component {
static navigationOptions = ({ navigation }) => ({
title: 'myApp',
headerRight: navigation.state.params && navigation.state.params.edit ? (
<View>
<TouchableHighlight
onPress={() => navigation.state.params.edit()}
underlayColor="gray"
>
<View>
<Image source={pencilEditButton} style={styles.navigationButtonImage} />
</View>
</TouchableHighlight>
</View>
) : null,
})
constructor(props) {
super(props);
this.onEdit = this.onEdit.bind(this);
}
componentDidMount() {
this.props.navigation.setParams({ edit: this.onEdit });
}
onEdit() {
if (this.xyz_Ref) {
this.xyz_Ref.Edit();
}
}
render() {
return (
<View>
<XYZ ref={ref => this.xyz_Ref = ref} />
</View>
);
}
}
On pressing the button(ButtonComponent), the state of the button gets changed. Now I'm passing that state to the <FlatListItem> (a child component ) inside <FlatList>. Depending on that status, the each item in the <FlatList> should be re-arranged.
I just got to know about this extraData prop but not sure how to make use of it in the code.
It's not something new but it is like simple checkbox implementation.
Everything is working fine but when I press the select all button all the remaining select buttons are not getting toggled to selected.
class FlatListItem extends Component{
constructor(props){
super(props)
const{ isSelected }=this.props
this.state={
selectedStatus:isSelected,
}
}
changeSelectStatus=(key)=>{
this.setState({selectedStatus:!this.state.selectedStatus});
return key;
}
render(){
return(
<View style={{flex:1,
flexDirection:'row',
backgroundColor:'white'}}>
<View>
<Image
source={{uri:this.props.item.imageUri}}
style={{width:50, height:50, margin:5}}>
</Image>
</View>
<View>
<Text style={{color:'black', padding:10, fontSize:16}}>{this.props.item.name}</Text>
</View>
<View style={{flex:1, alignItems:'flex-end', paddingRight:-10}}>
{this.state.selectedStatus?
**<ButtonComponent buttonColor={"black"} buttonTextColor={"white"} fullRounded={true}
borderHighlight={true} buttonWidth={70} buttonHeight={30}
onPress={()=>this.props.showSelected(this.changeSelectStatus(this.props.item.key)) }>
Selected
</ButtonComponent>
:
<ButtonComponent buttonColor={"white"} buttonTextColor={"black"} fullRounded={true}
borderHighlight={true} buttonWidth={70} buttonHeight={30}
onPress={()=>this.props.showSelected(this.changeSelectStatus(this.props.item.key)) }>
Select
</ButtonComponent>
}
</View>
</View>
)
}
}
export default class SelectMembersBody extends Component {
constructor(props){
super(props)
this.state={
selectedButtons:[],
selectAllBtnStatus:false,
}
}
selectAllMembers=()=>{
let allMembers=[];
if(!this.state.selectAllBtnStatus){
membersData.forEach(element => {
if(!this.state.selectedButtons.includes(element.key))
allMembers.push(element.key)
});
this.setState({
selectAllBtnStatus:!this.state.selectAllBtnStatus,
selectedButtons:[...this.state.selectedButtons, allMembers]
})
}
else{
this.setState({
selectAllBtnStatus:!this.state.selectAllBtnStatus,
selectedButtons:[...allMembers]
})
}
}
showSelected=(callback)=>{
let val = callback;
if(!this.state.selectedButtons.includes(val))
this.setState({selectedButtons:[...this.state.selectedButtons, val]});
else{
let newMarkers=[...this.state.selectedButtons]
let index = newMarkers.indexOf(val);
if (index >= 0) {
newMarkers.splice( index, 1 );
}
this.setState({selectedButtons:newMarkers});
}
}
render(){
return(
<View style={{flex:1, }}>
<Text>{this.state.selectedButtons}</Text>
<View>
{this.state.selectAllBtnStatus?
<ButtonComponent buttonColor={"black"} buttonTextColor={"white"} fullRounded={true}
borderHighlight={true} buttonWidth={85} buttonHeight={30} onPress={this.selectAllMembers}>
Selected All
</ButtonComponent>
:
<ButtonComponent buttonColor={"white"} buttonTextColor={"black"} fullRounded={true}
borderHighlight={true} buttonWidth={85} buttonHeight={30} onPress={this.selectAllMembers}>
Select All
</ButtonComponent> }
</View>
<FlatList data={membersData} extraData={this.state}
renderItem={({item, index})=>{
return(
<View>
<FlatListItem item={item} index={index} isSelected={this.state.selectAllBtnStatus} showSelected={this.showSelected} ></FlatListItem>
</View>
)
}
}></FlatList>
</View>
)
}
}
I know that it's quite harder to step into someone's shoes. The code I provided in my question might not be that effective since I'm a learner. That's why it is uncomfortable to get on to the code flow. So, I decided to answer my own question after reading some articles, docs and similar questions on StackOverFlow.
So, my question is why the flatlist was not getting re-rendered on setState. To be more precise, if you've gone through the pictures above, on selecting the Select All button all the buttons in the items should be toggled to selected state.
To make the flatlist re-render, we need to add an additional prop "extraData"
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
You can get it's full documentation here.
In my case, I set extraData = {this.state} since the status of buttons in each of the flatlist item depends on the array which is present in the parent component.
So, I've sent it to child component via props.
Now the flatlist is working fine and re-rendering on every state update.
I know we can call the function like {this.props.onPress}, can we pass value using this callback too? Default object is passed but I want to pass a string value using this function. Is this possible, I'm posting my code to clear the situation.
import Cbuttons from './../utils/CustomButtons'
class MainScreen extends Component {
_onBtnClick = event => {
console.log("Click on Button");
}
render() {
return (
<View style={customStyles.mainContainer}>
<Cbuttons btnName="MainScreen" onPress={this._onBtnClick}/>
<Cbuttons btnName="ScreenTwo" onPress={this._onBtnClick}/>
<Cbuttons btnName="ScreenThree" onPress=
{this._onBtnClick}/>
</View>
);
}
}
export default MainScreen;
while inCustom Buttons clsss
class MyButtons extends Component {
constructor(props){
super(props);
this.state={btnName:this.props.btnName};
}
render(){
return(
<TouchableOpacity onPress={this.props.onPress} >
<Text style={yourStyles.buttonText}> {this.props.btnName}</Text>
</TouchableOpacity>
);
}
}
export default MyButtons;
I want to return btnName back via this.props.onPress
<Cbuttons btnName="MainScreen" onPress={ () => { this._onBtnClick(this.props.btnName)}/>
Turn that field into a function which calls the _onBtnClick function normally with a parameter.
Then you would reflect that in the _onBtnClick() method:
_onBtnClick = buttonName => {
console.log(`Click on Button ${buttonName}`);
}
This solved my problem, Thanks #mcpolo
class MyButtons extends Component {
render(){
return(
<TouchableOpacity onPress={()=>{this.props.onPress(this.props.btnName)}} >
<Text style={yourStyles.buttonText}> {this.props.btnName}</Text>
</TouchableOpacity>
);
}
}
In the ListItem.js file which is the renderRow from ListView I added a Navigator but it's not showing anything in the renderScene
const item = {};
export default class ListItem extends Component{
constructor(props){
super(props);
item = this.props.item;
}
renderScene(route, navigator){
return(
<Text style={{color:'#fff'}}>Test</Text>
<Text style={{color:'#fff'}}>{item.title}</Text>
)
}
render(){
return(
<Navigator
renderScene={this.renderScene}
navigator={this.props.navigator} />
);
}
}
When I put everything in the render() it works fine. If I console.log() my item object in the renderScene it logs the item, but it shows nothing.
I feel like I experienced a similar issue with abstracting renderScene into its own function, almost as if the Navigator can't handle the render. All the docs demo the render being inline. What you could do is offload the render in a separate function, but still call it inline, like this.
render(){
return(
<Navigator
renderScene={(route, navigator) => this.renderScene(route, navigator)}
navigator={this.props.navigator} />
);
}
Since you are using ES6 syntax, there is no autobinding. You can use fat arrow syntax and do something like this:
renderScene = (route, navigator) => {
return(
<View>
<Text style={{color:'#fff'}}>Test</Text>
<Text style={{color:'#fff'}}>{item.title}</Text>
</View>
);
}